
轻松快捷开发 MetaTrader 程序的函数库(第九部分):与 MQL4 的兼容性 - 准备数据
内容
在本系列文章的前几部分中,我们为 MetaTrader 5 和 MetaTrader 4 跨平台函数库准备了以下工具:
- 用于创建用户用例功能,支持从程序快速访问对冲和净持结算账户当中任何订单和持仓的任何数据,
- 用于跟踪订单和持仓发生的事件 — 下单、移除和激活挂单,以及开/平仓和修改持仓和订单。
现在是时候实现函数库与 MQL4 的兼容性了,因为我们要开发交易类,并且函数库应该在 MQL5 和 MQL4 中都能正常工作。
在本文中,我们将开始改进函数库,以便实现其跨平台性。
MQL4 对比 MQL5
将整个函数库文件夹复制到相应的 MetaTrader 4 目录 \MQL4\Include\DoEasy。 我们将从包含 MQL5 EA 的相应文件夹中得到测试 EA,并以 *.mq4 作为扩展名保存到 \MQL4\Experts\TestDoEasy EA 目录(与文章编号对应的文件夹中,在此情况是 Part09)。
在编辑器的导航栏中找到函数库目录 \MQL4\Include\DoEasy,右键单击它并选择“编译”。
这会编译所有函数库文件,导致超过两千个编译错误:
如果我们分析得到的错误,我们会看到它们绝大多数与 MQL5 常量和枚举相关,而 MQL4 对此一无所知。 这意味着我们需要令 MQL4 知晓函数库中用到的常量。 当然还存在不同性质的错误,例如缺少某些函数,这意味着我们将利用 MQL4 函数实现其操作逻辑。
此外,MQL4 和 MQL5 的订单系统差异很大。 我们不得不为 MQL4 实现一个单独的事件处理程序,与 MQL5 中的实现不同,因为 MQL4 中的历史订单列表提供的订单数据少得多(并且没有成交数据),这意味着我们无法直接从终端的列表里获取订单和成交数据。 在此,我们必须在逻辑上比较在场和历史订单列表中发生的事件,并依据比较结果定义发生的事件。改进函数库
在 DoEasy 函数库的根文件夹中,创建新的 ToMQL4.mqh 包含文件。 在此我们将论述 MQL4 的所有必要常量和枚举。 在 Defines.mqh 列表的最开头,包含 用于 MQL4 编译的 Defines.mqh 文件:
//+------------------------------------------------------------------+ //| Defines.mqh | //| Copyright 2018, MetaQuotes Software Corp. | //| https://mql5.com/en/users/artmedia70 | //+------------------------------------------------------------------+ #property copyright "Copyright 2018, MetaQuotes Software Corp." #property link "https://mql5.com/en/users/artmedia70" //+------------------------------------------------------------------+ //| Include files | //+------------------------------------------------------------------+ #ifdef __MQL4__ #include "ToMQL4.mqh" #endif //+------------------------------------------------------------------+
之后,整个 MQL4 函数库就能够在编译期间查看 ToMQL4.mqh 文件中写入的内容。
我们转到编辑器工具箱的错误选项卡,按下 NumPad Home 或简单地向上滚动到错误列表的最开头。 双击最先的第一个错误:
编辑器将我们移动到 Defines.mqh 文件中的出错字符串:
//+------------------------------------------------------------------+ //| List of possible trading events on the account | //+------------------------------------------------------------------+ enum ENUM_TRADE_EVENT { TRADE_EVENT_NO_EVENT = 0, // No trading event TRADE_EVENT_PENDING_ORDER_PLASED, // Pending order placed TRADE_EVENT_PENDING_ORDER_REMOVED, // Pending order removed //--- enumeration members matching the ENUM_DEAL_TYPE enumeration members //--- (constant order below should not be changed, no constants should be added/deleted) TRADE_EVENT_ACCOUNT_CREDIT = DEAL_TYPE_CREDIT, // Charging credit (3) TRADE_EVENT_ACCOUNT_CHARGE, // Additional charges
自然而然地,MQL4 对成交及其类型一无所知。 这应该加以修复。 只需打开 MQL5 参考,并在成交属性里使用 DEAL_TYPE_CREDIT 查询搜索数据:
ID |
说明 |
类型 |
DEAL_TICKET |
成交票据。 为每笔成交分配的独有编号 |
long |
DEAL_ORDER |
成交 订单号 |
long |
DEAL_TIME |
成交时间 |
datetime |
DEAL_TIME_MSC |
执行成交的时刻,自 1970 年 1 月 1 日起以毫秒为单位的时间 |
long |
DEAL_TYPE |
成交类型 |
|
DEAL_ENTRY |
交易方向 - 入场,离场或逆转 |
|
DEAL_MAGIC |
成交的魔幻数字 (参阅 ORDER_MAGIC) |
long |
DEAL_REASON |
成交执行的原因或来源 |
|
DEAL_POSITION_ID |
成交开立,修改或平仓的仓位 ID。 每笔仓位都有一个唯一的 ID,分配给在持仓生存周期内该品种上执行的所有成交。 |
long |
在表中,我们最感兴趣的是 ENUM_DEAL_TYPE。
点击链接并获取所有成交类型的列表:
ID |
说明 |
DEAL_TYPE_BUY |
买入 |
DEAL_TYPE_SELL |
卖出 |
DEAL_TYPE_BALANCE |
余额 |
DEAL_TYPE_CREDIT |
信贷 |
DEAL_TYPE_CHARGE |
额外收费 |
DEAL_TYPE_CORRECTION |
调整 |
DEAL_TYPE_BONUS |
奖金 |
DEAL_TYPE_COMMISSION |
额外佣金 |
DEAL_TYPE_COMMISSION_DAILY |
日支佣金 |
DEAL_TYPE_COMMISSION_MONTHLY |
月支佣金 |
DEAL_TYPE_COMMISSION_AGENT_DAILY |
日支代理佣金 |
DEAL_TYPE_COMMISSION_AGENT_MONTHLY |
月支代理佣金 |
DEAL_TYPE_INTEREST |
利率 |
DEAL_TYPE_BUY_CANCELED |
取消的买入成交。 可能存在先前执行的买入成交被取消的情况。 在此情况下,之前执行的成交类型 (DEAL_TYPE_BUY) 被修改为 DEAL_TYPE_BUY_CANCELED,且其盈利/亏损清零。 先前获得的利润/亏损由单独的余额操作收取/提取 |
DEAL_TYPE_SELL_CANCELED |
取消的卖出成交。 可能存在先前执行的卖出成交被取消的情况。 在此情况下,之前执行的成交类型 (DEAL_TYPE_SELL) 被修改为 DEAL_TYPE_SELL_CANCELED,且其盈利/亏损清零。 先前获得的利润/亏损由单独的余额操作收取/提取 |
DEAL_DIVIDEND |
股息操作 |
DEAL_DIVIDEND_FRANKED |
分红(非应税)股息操作 |
DEAL_TAX |
税费 |
将 ENUM_DEAL_TYPE 枚举中的成交类型添加到 ToMQL4.mqh 文件中:
//+------------------------------------------------------------------+ //| ToMQL4.mqh | //| Copyright 2017, Artem A. Trishkin, Skype artmedia70 | //| https://www.mql5.com/en/users/artmedia70 | //+------------------------------------------------------------------+ #property copyright "Copyright 2017, Artem A. Trishkin, Skype artmedia70" #property link "https://www.mql5.com/en/users/artmedia70" #property strict #ifdef __MQL4__ //+------------------------------------------------------------------+ //| MQL5 deal types | //+------------------------------------------------------------------+ enum ENUM_DEAL_TYPE { DEAL_TYPE_BUY, DEAL_TYPE_SELL, DEAL_TYPE_BALANCE, DEAL_TYPE_CREDIT, DEAL_TYPE_CHARGE, DEAL_TYPE_CORRECTION, DEAL_TYPE_BONUS, DEAL_TYPE_COMMISSION, DEAL_TYPE_COMMISSION_DAILY, DEAL_TYPE_COMMISSION_MONTHLY, DEAL_TYPE_COMMISSION_AGENT_DAILY, DEAL_TYPE_COMMISSION_AGENT_MONTHLY, DEAL_TYPE_INTEREST, DEAL_TYPE_BUY_CANCELED, DEAL_TYPE_SELL_CANCELED, DEAL_DIVIDEND, DEAL_DIVIDEND_FRANKED, DEAL_TAX }; //+------------------------------------------------------------------+ #endif
保存文件并再次编译所有函数库文件。 现在的错误较少了:
再次移至出错列表的开头,然后单击第一个错误。 现在它是 ENUM_POSITION_TYPE,所以我们添加:
//+------------------------------------------------------------------+ //| ToMQL4.mqh | //| Copyright 2017, Artem A. Trishkin, Skype artmedia70 | //| https://www.mql5.com/en/users/artmedia70 | //+------------------------------------------------------------------+ #property copyright "Copyright 2017, Artem A. Trishkin, Skype artmedia70" #property link "https://www.mql5.com/en/users/artmedia70" #property strict #ifdef __MQL4__ //+------------------------------------------------------------------+ //| MQL5 deal type | //+------------------------------------------------------------------+ enum ENUM_DEAL_TYPE { DEAL_TYPE_BUY, DEAL_TYPE_SELL, DEAL_TYPE_BALANCE, DEAL_TYPE_CREDIT, DEAL_TYPE_CHARGE, DEAL_TYPE_CORRECTION, DEAL_TYPE_BONUS, DEAL_TYPE_COMMISSION, DEAL_TYPE_COMMISSION_DAILY, DEAL_TYPE_COMMISSION_MONTHLY, DEAL_TYPE_COMMISSION_AGENT_DAILY, DEAL_TYPE_COMMISSION_AGENT_MONTHLY, DEAL_TYPE_INTEREST, DEAL_TYPE_BUY_CANCELED, DEAL_TYPE_SELL_CANCELED, DEAL_DIVIDEND, DEAL_DIVIDEND_FRANKED, DEAL_TAX }; //+------------------------------------------------------------------+ //| Open position direction | //+------------------------------------------------------------------+ enum ENUM_POSITION_TYPE { POSITION_TYPE_BUY, POSITION_TYPE_SELL }; //+------------------------------------------------------------------+ #endif
编译后,我们得到的错误更少了。 移至列表中的第一个错误,定义原因并添加以下枚举:
//+------------------------------------------------------------------+ //| ToMQL4.mqh | //| Copyright 2017, Artem A. Trishkin, Skype artmedia70 | //| https://www.mql5.com/en/users/artmedia70 | //+------------------------------------------------------------------+ #property copyright "Copyright 2017, Artem A. Trishkin, Skype artmedia70" #property link "https://www.mql5.com/en/users/artmedia70" #property strict #ifdef __MQL4__ //+------------------------------------------------------------------+ //| MQL5 deal types | //+------------------------------------------------------------------+ enum ENUM_DEAL_TYPE { DEAL_TYPE_BUY, DEAL_TYPE_SELL, DEAL_TYPE_BALANCE, DEAL_TYPE_CREDIT, DEAL_TYPE_CHARGE, DEAL_TYPE_CORRECTION, DEAL_TYPE_BONUS, DEAL_TYPE_COMMISSION, DEAL_TYPE_COMMISSION_DAILY, DEAL_TYPE_COMMISSION_MONTHLY, DEAL_TYPE_COMMISSION_AGENT_DAILY, DEAL_TYPE_COMMISSION_AGENT_MONTHLY, DEAL_TYPE_INTEREST, DEAL_TYPE_BUY_CANCELED, DEAL_TYPE_SELL_CANCELED, DEAL_DIVIDEND, DEAL_DIVIDEND_FRANKED, DEAL_TAX }; //+------------------------------------------------------------------+ //| Open position direction | //+------------------------------------------------------------------+ enum ENUM_POSITION_TYPE { POSITION_TYPE_BUY, POSITION_TYPE_SELL }; //+------------------------------------------------------------------+ //| Order status | //+------------------------------------------------------------------+ enum ENUM_ORDER_STATE { ORDER_STATE_STARTED, ORDER_STATE_PLACED, ORDER_STATE_CANCELED, ORDER_STATE_PARTIAL, ORDER_STATE_FILLED, ORDER_STATE_REJECTED, ORDER_STATE_EXPIRED, ORDER_STATE_REQUEST_ADD, ORDER_STATE_REQUEST_MODIFY, ORDER_STATE_REQUEST_CANCEL }; //+------------------------------------------------------------------+ #endif
在下一次编译期间,我们得到了错误的订单类型 ORDER_TYPE_BUY_STOP_LIMIT。
MQL4 已经具有 ENUM_ORDER_TYPE枚举。
我们不能向其添加新的常量。 因此,将它们添加为宏替换。
在 MQL5 中,ENUM_ORDER_TYPE 枚举中的 ORDER_TYPE_BUY_STOP_LIMIT 常量设置为 6,而在 MQL4 中,这个订单类型已存在。 此为余额操作(如 MQL5 中的 ORDER_TYPE_SELL_STOP_LIMIT)设置为 7,而在 MQL4 中,此订单类型是信贷操作。
因此,在 MQL5 中为它们设置超过 ORDER_TYPE_CLOSE_BY 平单常量的值:ORDER_TYPE_CLOSE_BY+1 和 ORDER_TYPE_CLOSE_BY+2:
//+------------------------------------------------------------------+ //| ToMQL4.mqh | //| Copyright 2017, Artem A. Trishkin, Skype artmedia70 | //| https://www.mql5.com/en/users/artmedia70 | //+------------------------------------------------------------------+ #property copyright "Copyright 2017, Artem A. Trishkin, Skype artmedia70" #property link "https://www.mql5.com/en/users/artmedia70" #property strict #ifdef __MQL4__ //+------------------------------------------------------------------+ //| MQL5 deal types | //+------------------------------------------------------------------+ enum ENUM_DEAL_TYPE { DEAL_TYPE_BUY, DEAL_TYPE_SELL, DEAL_TYPE_BALANCE, DEAL_TYPE_CREDIT, DEAL_TYPE_CHARGE, DEAL_TYPE_CORRECTION, DEAL_TYPE_BONUS, DEAL_TYPE_COMMISSION, DEAL_TYPE_COMMISSION_DAILY, DEAL_TYPE_COMMISSION_MONTHLY, DEAL_TYPE_COMMISSION_AGENT_DAILY, DEAL_TYPE_COMMISSION_AGENT_MONTHLY, DEAL_TYPE_INTEREST, DEAL_TYPE_BUY_CANCELED, DEAL_TYPE_SELL_CANCELED, DEAL_DIVIDEND, DEAL_DIVIDEND_FRANKED, DEAL_TAX }; //+------------------------------------------------------------------+ //| Open position direction | //+------------------------------------------------------------------+ enum ENUM_POSITION_TYPE { POSITION_TYPE_BUY, POSITION_TYPE_SELL }; //+------------------------------------------------------------------+ //| Order status | //+------------------------------------------------------------------+ enum ENUM_ORDER_STATE { ORDER_STATE_STARTED, ORDER_STATE_PLACED, ORDER_STATE_CANCELED, ORDER_STATE_PARTIAL, ORDER_STATE_FILLED, ORDER_STATE_REJECTED, ORDER_STATE_EXPIRED, ORDER_STATE_REQUEST_ADD, ORDER_STATE_REQUEST_MODIFY, ORDER_STATE_REQUEST_CANCEL }; //+------------------------------------------------------------------+ //| Order types | //+------------------------------------------------------------------+ #define ORDER_TYPE_CLOSE_BY (8) #define ORDER_TYPE_BUY_STOP_LIMIT (9) #define ORDER_TYPE_SELL_STOP_LIMIT (10) //+------------------------------------------------------------------+ #endif
编译整个函数库。 在实现 StopLimit 订单类型的宏替换后,出错指示函数返回正确的订单下单价格,即 ENUM_ORDER_TYPE 枚举没有值 9 和 10,因为我们在 switch 运算符后面使用 ENUM_ORDER_TYPE 枚举类型,即订单类型的值:
//+------------------------------------------------------------------+ //| Return the correct order placement price | //| relative to StopLevel | //+------------------------------------------------------------------+ double CorrectPricePending(const string symbol_name,const ENUM_ORDER_TYPE order_type,const double price_set,const double price=0,const int spread_multiplier=2) { double pt=SymbolInfoDouble(symbol_name,SYMBOL_POINT),pp=0; int lv=StopLevel(symbol_name,spread_multiplier), dg=(int)SymbolInfoInteger(symbol_name,SYMBOL_DIGITS); switch(order_type) { case ORDER_TYPE_BUY_LIMIT : pp=(price==0 ? SymbolInfoDouble(symbol_name,SYMBOL_ASK) : price); return NormalizeDouble(fmin(pp-lv*pt,price_set),dg); case ORDER_TYPE_BUY_STOP : case ORDER_TYPE_BUY_STOP_LIMIT : pp=(price==0 ? SymbolInfoDouble(symbol_name,SYMBOL_ASK) : price); return NormalizeDouble(fmax(pp+lv*pt,price_set),dg); case ORDER_TYPE_SELL_LIMIT : pp=(price==0 ? SymbolInfoDouble(symbol_name,SYMBOL_BID) : price); return NormalizeDouble(fmax(pp+lv*pt,price_set),dg); case ORDER_TYPE_SELL_STOP : case ORDER_TYPE_SELL_STOP_LIMIT : pp=(price==0 ? SymbolInfoDouble(symbol_name,SYMBOL_BID) : price); return NormalizeDouble(fmin(pp-lv*pt,price_set),dg); default : Print(DFUN,TextByLanguage("Неправильный тип ордера: ","Invalid order type: "),EnumToString(order_type)); return 0; } } //+------------------------------------------------------------------+ //| Return the correct order placement price | //| relative to StopLevel | //+------------------------------------------------------------------+ double CorrectPricePending(const string symbol_name,const ENUM_ORDER_TYPE order_type,const int distance_set,const double price=0,const int spread_multiplier=2) { double pt=SymbolInfoDouble(symbol_name,SYMBOL_POINT),pp=0; int lv=StopLevel(symbol_name,spread_multiplier), dg=(int)SymbolInfoInteger(symbol_name,SYMBOL_DIGITS); switch(order_type) { case ORDER_TYPE_BUY_LIMIT : pp=(price==0 ? SymbolInfoDouble(symbol_name,SYMBOL_ASK) : price); return NormalizeDouble(fmin(pp-lv*pt,pp-distance_set*pt),dg); case ORDER_TYPE_BUY_STOP : case ORDER_TYPE_BUY_STOP_LIMIT : pp=(price==0 ? SymbolInfoDouble(symbol_name,SYMBOL_ASK) : price); return NormalizeDouble(fmax(pp+lv*pt,pp+distance_set*pt),dg); case ORDER_TYPE_SELL_LIMIT : pp=(price==0 ? SymbolInfoDouble(symbol_name,SYMBOL_BID) : price); return NormalizeDouble(fmax(pp+lv*pt,pp+distance_set*pt),dg); case ORDER_TYPE_SELL_STOP : case ORDER_TYPE_SELL_STOP_LIMIT : pp=(price==0 ? SymbolInfoDouble(symbol_name,SYMBOL_BID) : price); return NormalizeDouble(fmin(pp-lv*pt,pp-distance_set*pt),dg); default : Print(DFUN,TextByLanguage("Неправильный тип ордера: ","Invalid order type: "),EnumToString(order_type)); return 0; } } //+------------------------------------------------------------------+
解决方案很简单 — switch 中的 order_type 转换为整数类型:
//+------------------------------------------------------------------+ //| Return the correct order placement price | //| relative to StopLevel | //+------------------------------------------------------------------+ double CorrectPricePending(const string symbol_name,const ENUM_ORDER_TYPE order_type,const double price_set,const double price=0,const int spread_multiplier=2) { double pt=SymbolInfoDouble(symbol_name,SYMBOL_POINT),pp=0; int lv=StopLevel(symbol_name,spread_multiplier), dg=(int)SymbolInfoInteger(symbol_name,SYMBOL_DIGITS); switch((int)order_type) { case ORDER_TYPE_BUY_LIMIT : pp=(price==0 ? SymbolInfoDouble(symbol_name,SYMBOL_ASK) : price); return NormalizeDouble(fmin(pp-lv*pt,price_set),dg); case ORDER_TYPE_BUY_STOP : case ORDER_TYPE_BUY_STOP_LIMIT : pp=(price==0 ? SymbolInfoDouble(symbol_name,SYMBOL_ASK) : price); return NormalizeDouble(fmax(pp+lv*pt,price_set),dg); case ORDER_TYPE_SELL_LIMIT : pp=(price==0 ? SymbolInfoDouble(symbol_name,SYMBOL_BID) : price); return NormalizeDouble(fmax(pp+lv*pt,price_set),dg); case ORDER_TYPE_SELL_STOP : case ORDER_TYPE_SELL_STOP_LIMIT : pp=(price==0 ? SymbolInfoDouble(symbol_name,SYMBOL_BID) : price); return NormalizeDouble(fmin(pp-lv*pt,price_set),dg); default : Print(DFUN,TextByLanguage("Неправильный тип ордера: ","Invalid order type: "),EnumToString(order_type)); return 0; } } //+------------------------------------------------------------------+ //| Return the correct order placement price | //| relative to StopLevel | //+------------------------------------------------------------------+ double CorrectPricePending(const string symbol_name,const ENUM_ORDER_TYPE order_type,const int distance_set,const double price=0,const int spread_multiplier=2) { double pt=SymbolInfoDouble(symbol_name,SYMBOL_POINT),pp=0; int lv=StopLevel(symbol_name,spread_multiplier), dg=(int)SymbolInfoInteger(symbol_name,SYMBOL_DIGITS); switch((int)order_type) { case ORDER_TYPE_BUY_LIMIT : pp=(price==0 ? SymbolInfoDouble(symbol_name,SYMBOL_ASK) : price); return NormalizeDouble(fmin(pp-lv*pt,pp-distance_set*pt),dg); case ORDER_TYPE_BUY_STOP : case ORDER_TYPE_BUY_STOP_LIMIT : pp=(price==0 ? SymbolInfoDouble(symbol_name,SYMBOL_ASK) : price); return NormalizeDouble(fmax(pp+lv*pt,pp+distance_set*pt),dg); case ORDER_TYPE_SELL_LIMIT : pp=(price==0 ? SymbolInfoDouble(symbol_name,SYMBOL_BID) : price); return NormalizeDouble(fmax(pp+lv*pt,pp+distance_set*pt),dg); case ORDER_TYPE_SELL_STOP : case ORDER_TYPE_SELL_STOP_LIMIT : pp=(price==0 ? SymbolInfoDouble(symbol_name,SYMBOL_BID) : price); return NormalizeDouble(fmin(pp-lv*pt,pp-distance_set*pt),dg); default : Print(DFUN,TextByLanguage("Неправильный тип ордера: ","Invalid order type: "),EnumToString(order_type)); return 0; } } //+------------------------------------------------------------------+
我们进行编译。 现在 Order.mqh 文件中存在错误 — MQL4 不知晓以下值 ORDER_FILLING_RETURN,
ORDER_TIME_GTC,
ORDER_REASON_SL,
ORDER_REASON_TP 和 ORDER_REASON_EXPERT
常量。
//+------------------------------------------------------------------+ //| Return execution type by residue | //+------------------------------------------------------------------+ long COrder::OrderTypeFilling(void) const { #ifdef __MQL4__ return (long)ORDER_FILLING_RETURN; #else long res=0; switch((ENUM_ORDER_STATUS)this.GetProperty(ORDER_PROP_STATUS)) { case ORDER_STATUS_MARKET_ORDER : case ORDER_STATUS_MARKET_PENDING : res=::OrderGetInteger(ORDER_TYPE_FILLING); break; case ORDER_STATUS_HISTORY_PENDING : case ORDER_STATUS_HISTORY_ORDER : res=::HistoryOrderGetInteger(m_ticket,ORDER_TYPE_FILLING);break; default : res=0; break; } return res; #endif } //+------------------------------------------------------------------+ //| Return order lifetime | //+------------------------------------------------------------------+ long COrder::OrderTypeTime(void) const { #ifdef __MQL4__ return (long)ORDER_TIME_GTC; #else long res=0; switch((ENUM_ORDER_STATUS)this.GetProperty(ORDER_PROP_STATUS)) { case ORDER_STATUS_MARKET_ORDER : case ORDER_STATUS_MARKET_PENDING : res=::OrderGetInteger(ORDER_TYPE_TIME); break; case ORDER_STATUS_HISTORY_PENDING : case ORDER_STATUS_HISTORY_ORDER : res=::HistoryOrderGetInteger(m_ticket,ORDER_TYPE_TIME);break; default : res=0; break; } return res; #endif } //+------------------------------------------------------------------+ //| Order reason or source | //+------------------------------------------------------------------+ long COrder::OrderReason(void) const { #ifdef __MQL4__ return ( this.OrderCloseByStopLoss() ? ORDER_REASON_SL : this.OrderCloseByTakeProfit() ? ORDER_REASON_TP : this.OrderMagicNumber()!=0 ? ORDER_REASON_EXPERT : WRONG_VALUE ); #else long res=WRONG_VALUE; switch((ENUM_ORDER_STATUS)this.GetProperty(ORDER_PROP_STATUS)) { case ORDER_STATUS_MARKET_POSITION : res=::PositionGetInteger(POSITION_REASON); break; case ORDER_STATUS_MARKET_ORDER : case ORDER_STATUS_MARKET_PENDING : res=::OrderGetInteger(ORDER_REASON); break; case ORDER_STATUS_HISTORY_PENDING : case ORDER_STATUS_HISTORY_ORDER : res=::HistoryOrderGetInteger(m_ticket,ORDER_REASON);break; case ORDER_STATUS_DEAL : res=::HistoryDealGetInteger(m_ticket,DEAL_REASON); break; default : res=WRONG_VALUE; break; } return res; #endif } //+------------------------------------------------------------------+
我们在 ToMQL4.mqh 文件的末尾添加宏替换(为节省空间,我不会在此给出完整的清单):
//+------------------------------------------------------------------+ //| Order types, execution policy, lifetime, reasons | //+------------------------------------------------------------------+ #define ORDER_TYPE_CLOSE_BY (8) #define ORDER_TYPE_BUY_STOP_LIMIT (9) #define ORDER_TYPE_SELL_STOP_LIMIT (10) #define ORDER_FILLING_RETURN (2) #define ORDER_TIME_GTC (0) #define ORDER_REASON_EXPERT (3) #define ORDER_REASON_SL (4) #define ORDER_REASON_TP (5) //+------------------------------------------------------------------+ #endif
另一次编译导致我们在 CHistoryCollection::OrderSearch() 方法的 HistoryCollection.mqh 文件中缺少 MQL5 函数 HistoryOrderGetTicket()。 代码分析建议在此处应用条件编译指令。 我们为方法加以补充:
//+------------------------------------------------------------------+ //| Return the "lost" order's type and ticket | //+------------------------------------------------------------------+ ulong CHistoryCollection::OrderSearch(const int start,ENUM_ORDER_TYPE &order_type) { ulong order_ticket=0; #ifdef __MQL5__ for(int i=start-1;i>=0;i--) { ulong ticket=::HistoryOrderGetTicket(i); if(ticket==0) continue; ENUM_ORDER_TYPE type=(ENUM_ORDER_TYPE)::HistoryOrderGetInteger(ticket,ORDER_TYPE); if(this.IsPresentOrderInList(ticket,type)) continue; order_ticket=ticket; order_type=type; } #else for(int i=start-1;i>=0;i--) { if(!::OrderSelect(i,SELECT_BY_POS,MODE_HISTORY)) continue; ENUM_ORDER_TYPE type=(ENUM_ORDER_TYPE)::OrderType(); ulong ticket=::OrderTicket(); if(ticket==0 || type<ORDER_TYPE_BUY_LIMIT || type>ORDER_TYPE_SELL_STOP) continue; if(this.IsPresentOrderInList(ticket,type)) continue; order_ticket=ticket; order_type=type; } #endif return order_ticket; } //+------------------------------------------------------------------+
所有用于 MQL5 的内容都由支路 #ifdef __MQL5 __ 指令构成。 在 #else 指令之后,添加 MQL4 代码直至 #endif。
下一个错误位于 CEvent 类构造函数中。 使用相同的条件编译指令补充代码:
//+------------------------------------------------------------------+ //| Constructor | //+------------------------------------------------------------------+ CEvent::CEvent(const ENUM_EVENT_STATUS event_status,const int event_code,const ulong ticket) : m_event_code(event_code),m_digits(0) { this.m_long_prop[EVENT_PROP_STATUS_EVENT] = event_status; this.m_long_prop[EVENT_PROP_TICKET_ORDER_EVENT] = (long)ticket; this.m_is_hedge=#ifdef __MQL4__ true #else bool(::AccountInfoInteger(ACCOUNT_MARGIN_MODE)==ACCOUNT_MARGIN_MODE_RETAIL_HEDGING) #endif; this.m_digits_acc=#ifdef __MQL4__ 2 #else (int)::AccountInfoInteger(ACCOUNT_CURRENCY_DIGITS) #endif; this.m_chart_id=::ChartID(); } //+------------------------------------------------------------------+
当检查“对冲”类型帐户时,我们面临缺少常量错误,因此只需立即返回
true,因为所有帐户在 MetaTrader 4 中都是对冲类型。
此外,当收到账户货币的小数位数时,返回
2,因为 MQL4 无法获得此值。
下一次编译将引导我们进入 CEventsCollection::NewDealEventHedge() 方法 — 接收 MetaTrader 5 对冲账户的事件。 它应对 MQL4 中缺少的成交。 通过在条件编译支路中包含所有方法代码来暂时禁用该方法:
在方法的开头插入指令
//+------------------------------------------------------------------+ //| Create a hedging account event | //+------------------------------------------------------------------+ void CEventsCollection::NewDealEventHedge(COrder* deal,CArrayObj* list_history,CArrayObj* list_market) { #ifdef __MQL5__ double ask=::SymbolInfoDouble(deal.Symbol(),SYMBOL_ASK); double bid=::SymbolInfoDouble(deal.Symbol(),SYMBOL_BID); //--- Market entry
以及在方法的最后
#endif } //+------------------------------------------------------------------+
接下来,我们最后遇到 CEventsCollection::NewDealEventNetto() 方法中的错误 — 为净持结算帐户创建一个事件。 解决方案与前一种情况相同 — 使用条件编译指令支路构建整个 NewDealEventNetto() 方法代码。
编译并面对 CEventsCollection::GetListAllDealsInByPosID() 方法中的 DEAL_ENTRY_IN 未知常量错误。 将必要的枚举添加到 ToMQL4.mqh 文件中(我们可以再次使用条件编译来禁用代码,但稍后我们可能需要此枚举):
//+------------------------------------------------------------------+ //| MQL5 deal types | //+------------------------------------------------------------------+ enum ENUM_DEAL_TYPE { DEAL_TYPE_BUY, DEAL_TYPE_SELL, DEAL_TYPE_BALANCE, DEAL_TYPE_CREDIT, DEAL_TYPE_CHARGE, DEAL_TYPE_CORRECTION, DEAL_TYPE_BONUS, DEAL_TYPE_COMMISSION, DEAL_TYPE_COMMISSION_DAILY, DEAL_TYPE_COMMISSION_MONTHLY, DEAL_TYPE_COMMISSION_AGENT_DAILY, DEAL_TYPE_COMMISSION_AGENT_MONTHLY, DEAL_TYPE_INTEREST, DEAL_TYPE_BUY_CANCELED, DEAL_TYPE_SELL_CANCELED, DEAL_DIVIDEND, DEAL_DIVIDEND_FRANKED, DEAL_TAX }; //+------------------------------------------------------------------+ //| Position change method | //+------------------------------------------------------------------+ enum ENUM_DEAL_ENTRY { DEAL_ENTRY_IN, DEAL_ENTRY_OUT, DEAL_ENTRY_INOUT, DEAL_ENTRY_OUT_BY }; //+------------------------------------------------------------------+ //| Open position direction | //+------------------------------------------------------------------+ enum ENUM_POSITION_TYPE { POSITION_TYPE_BUY, POSITION_TYPE_SELL }; //+------------------------------------------------------------------+
接下来,我们最后遇到了已经熟悉的错误,即检查“对冲”类型帐户,但现在它位于事件集合类构造函数中。 我们来修复它:
//+------------------------------------------------------------------+ //| Constructor | //+------------------------------------------------------------------+ CEventsCollection::CEventsCollection(void) : m_trade_event(TRADE_EVENT_NO_EVENT),m_trade_event_code(TRADE_EVENT_FLAG_NO_EVENT) { this.m_list_events.Clear(); this.m_list_events.Sort(SORT_BY_EVENT_TIME_EVENT); this.m_list_events.Type(COLLECTION_EVENTS_ID); this.m_is_hedge=#ifdef __MQL4__ true #else bool(::AccountInfoInteger(ACCOUNT_MARGIN_MODE)==ACCOUNT_MARGIN_MODE_RETAIL_HEDGING) #endif; this.m_chart_id=::ChartID(); ::ZeroMemory(this.m_tick); } //+------------------------------------------------------------------+
接下来,对 CEngine 类构造函数执行相同的更正:
//+------------------------------------------------------------------+ //| CEngine constructor | //+------------------------------------------------------------------+ CEngine::CEngine() : m_first_start(true),m_acc_trade_event(TRADE_EVENT_NO_EVENT) { ::ResetLastError(); if(!::EventSetMillisecondTimer(TIMER_FREQUENCY)) Print(DFUN,"Не удалось создать таймер. Ошибка: ","Could not create timer. Error: ",(string)::GetLastError()); this.m_list_counters.Sort(); this.m_list_counters.Clear(); this.CreateCounter(COLLECTION_COUNTER_ID,COLLECTION_COUNTER_STEP,COLLECTION_PAUSE); this.m_is_hedge=#ifdef __MQL4__ true #else bool(::AccountInfoInteger(ACCOUNT_MARGIN_MODE)==ACCOUNT_MARGIN_MODE_RETAIL_HEDGING) #endif; } //+------------------------------------------------------------------+
一切都准备好了。 现在编译整个函数库都没有出错。 但这只是第一阶段。 现在我们需要启动它。 由于我们使用条件编译禁用了一些方法,因此我们需要开发它们,以便能够在 MetaTrader 4 中工作。
在 MQL5 中,余额操作是成交。 它们可以在历史订单和成交列表中找到。 在 MQL4 中,余额操作是 ORDER_TYPE_BALANCE(6) 和 ORDER_TYPE_CREDIT(7) 类型的订单。 因此,我为 MQL4 创建了一个单独的余额操作对象类,存储在历史订单和仓位列表中。
在 \MQL4\Include\DoEasy\Objects\Orders 下的 HistoryBalance.mqh 文件中创建新的 CHistoryBalance 类。 COrder 应作为一个基类:
//+------------------------------------------------------------------+ //| HistoryBalance.mqh | //| Copyright 2018, MetaQuotes Software Corp. | //| https://mql5.com/en/users/artmedia70 | //+------------------------------------------------------------------+ #property copyright "Copyright 2018, MetaQuotes Software Corp." #property link "https://mql5.com/en/users/artmedia70" #property version "1.00" //+------------------------------------------------------------------+ //| Include files | //+------------------------------------------------------------------+ #include "Order.mqh" //+------------------------------------------------------------------+ //| Historical balance operation | //+------------------------------------------------------------------+ class CHistoryBalance : public COrder { public: //--- Constructor CHistoryBalance(const ulong ticket) : COrder(ORDER_STATUS_BALANCE,ticket) {} //--- Supported deal properties (1) real, (2) integer virtual bool SupportProperty(ENUM_ORDER_PROP_INTEGER property); virtual bool SupportProperty(ENUM_ORDER_PROP_DOUBLE property); virtual bool SupportProperty(ENUM_ORDER_PROP_STRING property); }; //+------------------------------------------------------------------+ //| Return 'true' if an order supports a passed | //| integer property, otherwise return 'false' | //+------------------------------------------------------------------+ bool CHistoryBalance::SupportProperty(ENUM_ORDER_PROP_INTEGER property) { if(property==ORDER_PROP_TICKET || property==ORDER_PROP_TIME_OPEN || property==ORDER_PROP_STATUS || property==ORDER_PROP_TYPE || property==ORDER_PROP_REASON ) return true; return false; } //+------------------------------------------------------------------+ //| Return 'true' if an order supports a passed | //| real property, otherwise return 'false' | //+------------------------------------------------------------------+ bool CHistoryBalance::SupportProperty(ENUM_ORDER_PROP_DOUBLE property) { return(property==ORDER_PROP_PROFIT ? true : false); } //+------------------------------------------------------------------+ //| Return 'true' if an order supports a passed | //| string property, otherwise return 'false' | //+------------------------------------------------------------------+ bool CHistoryBalance::SupportProperty(ENUM_ORDER_PROP_STRING property) { if(property==ORDER_PROP_SYMBOL || property==ORDER_PROP_EXT_ID) return false; return true; } //+------------------------------------------------------------------+
该类对我们来说没有任何新内容。 我们已经查看了函数库论述的第二部分中的所有历史订单类。
我们已有两种类型的余额操作 — 余额和信贷操作。 相应地,它们的类型对应 6 和 7 的数值。 我们将针对两种类型使用单个余额操作类,并在 “reason” 订单属性中阐明某种类型。
在 ToMQL4.mqh 文件中添加两个缺失的订单“原因”:
//+------------------------------------------------------------------+ //| Order types, execution policy, lifetime, reasons | //+------------------------------------------------------------------+ #define ORDER_TYPE_CLOSE_BY (8) #define ORDER_TYPE_BUY_STOP_LIMIT (9) #define ORDER_TYPE_SELL_STOP_LIMIT (10) #define ORDER_FILLING_RETURN (2) #define ORDER_TIME_GTC (0) #define ORDER_REASON_EXPERT (3) #define ORDER_REASON_SL (4) #define ORDER_REASON_TP (5) #define ORDER_REASON_BALANCE (6) #define ORDER_REASON_CREDIT (7) //+------------------------------------------------------------------+
由于我们已有一个从抽象订单类派生的新类,我们需要在 COrder 中添加缺失的功能。
在 COrder::OrderPositionID() 方法中,针对 MQL4 替换返回魔幻数字
//+------------------------------------------------------------------+ //| Return position ID | //+------------------------------------------------------------------+ long COrder::OrderPositionID(void) const { #ifdef __MQL4__ return ::OrderMagicNumber(); #else
为 返回票据(稍后将针对 MQL4 仓位实现一种 PositionID):
//+------------------------------------------------------------------+ //| Return position ID | //+------------------------------------------------------------------+ long COrder::OrderPositionID(void) const { #ifdef __MQL4__ return ::OrderTicket(); #else
在 MQL4 中返回订单状态的方法始终返回来自 ENUM_ORDER_STATE
枚举的 ORDER_STATE_FILLED,但对于删除挂单则不然。 实现订单状态检查,如果这是删除挂单,则返回 ORDER_STATE_CANCELED。
//+------------------------------------------------------------------+ //| Return the order status | //+------------------------------------------------------------------+ long COrder::OrderState(void) const { #ifdef __MQL4__ return(this.Status()==ORDER_STATUS_HISTORY_ORDER ? ORDER_STATE_FILLED : ORDER_STATE_CANCELED); #else long res=0; switch((ENUM_ORDER_STATUS)this.GetProperty(ORDER_PROP_STATUS)) { case ORDER_STATUS_HISTORY_PENDING : case ORDER_STATUS_HISTORY_ORDER : res=::HistoryOrderGetInteger(m_ticket,ORDER_STATE); break; case ORDER_STATUS_MARKET_ORDER : case ORDER_STATUS_MARKET_PENDING : res=::OrderGetInteger(ORDER_STATE); break; case ORDER_STATUS_MARKET_POSITION : case ORDER_STATUS_DEAL : default : res=0; break; } return res; #endif } //+------------------------------------------------------------------+
针对 MQL4 返回的订单原因,将两个新添加的“原因”添加到方法中:
//+------------------------------------------------------------------+ //| Order reason or source | //+------------------------------------------------------------------+ long COrder::OrderReason(void) const { #ifdef __MQL4__ return ( this.TypeOrder()==ORDER_TYPE_BALANCE ? ORDER_REASON_BALANCE : this.TypeOrder()==ORDER_TYPE_CREDIT ? ORDER_REASON_CREDIT : this.OrderCloseByStopLoss() ? ORDER_REASON_SL : this.OrderCloseByTakeProfit() ? ORDER_REASON_TP : this.OrderMagicNumber()!=0 ? ORDER_REASON_EXPERT : WRONG_VALUE ); #else
在我们的例子中,返回 MQL4 未执行交易量的方法总是返回一个订单手数,这对于仓位是不正确的。 对于删除挂单,我们将返回订单手数,而对于仓位,我们将返回零:
//+------------------------------------------------------------------+ //| Return unexecuted volume | //+------------------------------------------------------------------+ double COrder::OrderVolumeCurrent(void) const { #ifdef __MQL4__ return(this.Status()==ORDER_STATUS_HISTORY_PENDING ? ::OrderLots() : 0); #else
在返回订单原因描述的方法中添加两个新“原因”的描述。 对于余额和信贷操作,检查利润。 如果超过零,则为入金,否则为出金:
//+------------------------------------------------------------------+ //| Reason description | //+------------------------------------------------------------------+ string COrder::GetReasonDescription(const long reason) const { #ifdef __MQL4__ return ( this.IsCloseByStopLoss() ? TextByLanguage("Срабатывание StopLoss","Due to StopLoss") : this.IsCloseByTakeProfit() ? TextByLanguage("Срабатывание TakeProfit","Due to TakeProfit") : this.Reason()==ORDER_REASON_EXPERT ? TextByLanguage("Выставлен из mql4-программы","Placed from mql4 program") : this.Comment()=="cancelled" ? TextByLanguage("Отменён","Cancelled") : this.Reason()==ORDER_REASON_BALANCE ? ( this.Profit()>0 ? TextByLanguage("Пополнение баланса","Deposit of funds on the account balance") : TextByLanguage("Снятие средств с баланса","Withdrawal from the balance") ) : this.Reason()==ORDER_REASON_CREDIT ? ( this.Profit()>0 ? TextByLanguage("Начисление кредитных средств","Received credit funds") : TextByLanguage("Изъятие кредитных средств","Withdrawal of credit") ) : TextByLanguage("Свойство не поддерживается в MQL4","Property not supported in MQL4") ); #else
此外,还进行了一些小的修订。 它们太微不足道了,不必在此详述。 它们主要与 MQL5/MQL4 日志中显示的文本相关。 所有编辑都可以在文章附带的函数库文件中找到。
现在我们来改进 HistoryCollection.mqh 文件中的历史集合类。
首先,包含新的类文件:
//+------------------------------------------------------------------+ //| Include files | //+------------------------------------------------------------------+ #include "ListObj.mqh" #include "..\Services\Select.mqh" #include "..\Objects\Orders\HistoryOrder.mqh" #include "..\Objects\Orders\HistoryPending.mqh" #include "..\Objects\Orders\HistoryDeal.mqh" #ifdef __MQL4__ #include "..\Objects\Orders\HistoryBalance.mqh" #endif //+------------------------------------------------------------------+
由于我们只需要为函数库的 MQL4 版本提供 CHistoryBalance 类,因此包含此类的文件包含在 MQL4 的条件编译指令中。
现在我们有了一个新的余额操作类。 为了开发并将其放入集合中,我们需要添加检查订单类型的余额和信用操作类型,并将它们添加到集合中的
Refresh() 方法中。 MQL4 的 CHistoryCollection 类:
//+------------------------------------------------------------------+ //| Update the list of orders and deals | //+------------------------------------------------------------------+ void CHistoryCollection::Refresh(void) { #ifdef __MQL4__ int total=::OrdersHistoryTotal(),i=m_index_order; for(; i<total; i++) { if(!::OrderSelect(i,SELECT_BY_POS,MODE_HISTORY)) continue; ENUM_ORDER_TYPE order_type=(ENUM_ORDER_TYPE)::OrderType(); //--- Closed positions if(order_type<ORDER_TYPE_BUY_LIMIT) { CHistoryOrder *order=new CHistoryOrder(::OrderTicket()); if(order==NULL) continue; if(!this.m_list_all_orders.InsertSort(order)) { ::Print(DFUN,TextByLanguage("Не удалось добавить ордер в список","Could not add order to the list")); delete order; } } //--- Balance/credit operations else if(order_type>ORDER_TYPE_SELL_STOP) { CHistoryBalance *order=new CHistoryBalance(::OrderTicket()); if(order==NULL) continue; if(!this.m_list_all_orders.InsertSort(order)) { ::Print(DFUN,TextByLanguage("Не удалось добавить ордер в список","Could not add order to the list")); delete order; } } else { //--- Removed pending orders CHistoryPending *order=new CHistoryPending(::OrderTicket()); if(order==NULL) continue; if(!this.m_list_all_orders.InsertSort(order)) { ::Print(DFUN,TextByLanguage("Не удалось добавить ордер в список","Could not add order to the list")); delete order; } } } //--- int delta_order=i-m_index_order; this.m_index_order=i; this.m_delta_order=delta_order; this.m_is_trade_event=(this.m_delta_order!=0 ? true : false); //--- __MQL5__ #else我们在历史订单类中进行一些修正:
//+------------------------------------------------------------------+ //| Return 'true' if an order supports the passed | //| real property, otherwise, return 'false' | //+------------------------------------------------------------------+ bool CHistoryOrder::SupportProperty(ENUM_ORDER_PROP_DOUBLE property) { if( #ifdef __MQL5__ property==ORDER_PROP_PROFIT || property==ORDER_PROP_PROFIT_FULL || property==ORDER_PROP_SWAP || property==ORDER_PROP_COMMISSION || property==ORDER_PROP_PRICE_CLOSE || ( property==ORDER_PROP_PRICE_STOP_LIMIT && ( this.TypeOrder()<ORDER_TYPE_BUY_STOP_LIMIT || this.TypeOrder()>ORDER_TYPE_SELL_STOP_LIMIT ) ) #else property==ORDER_PROP_PRICE_STOP_LIMIT && this.Status()==ORDER_STATUS_HISTORY_ORDER #endif ) return false; return true; } //+------------------------------------------------------------------+
以前,在 MQL5 中的 StopLimit 挂单价格未传递到日志。 所以,我实现了一个检查:如果已检查的属性是
StopLimit 订单价格,且如果订单类型不是 StopLimit 订单, 则属性未用到。 否则,这是一个
StopLimit 挂单,该属性是必需的。
在 MQL4 中,对于仓位,未使用
StopLimit 订单价格。
这样 MQL4 兼容性改进的第一阶段就完毕了。
测试
为了测试目的,从 \MQL5\Experts\TestDoEasy\Part03 得到 TestDoEasyPart03_1.mq5 EA,将其保存在 MQL4 EA 文件夹 \MQL4\Experts\TestDoEasy\Part09 中,并命名为 TestDoEasyPart09.mq4。
所编译的 EA 没有更改,但是如果我们查看代码,事实上它使用 MQL4 中缺失的成交列表:
//--- enums enum ENUM_TYPE_ORDERS { TYPE_ORDER_MARKET, // Market orders TYPE_ORDER_PENDING, // Pending orders TYPE_ORDER_DEAL // Deals }; //--- input parameters //+------------------------------------------------------------------+ //| Expert initialization function | //+------------------------------------------------------------------+ int OnInit() { //--- update history history.Refresh(); //--- get the collection list within the date range CArrayObj* list=history.GetListByTime(InpTimeBegin,InpTimeEnd,SELECT_BY_TIME_CLOSE); if(list==NULL) { Print("Could not get collection list"); return INIT_FAILED; } int total=list.Total(); for(int i=0;i<total;i++) { //--- get the order from the list COrder* order=list.At(i); if(order==NULL) continue; //--- if this is a deal if(order.Status()==ORDER_STATUS_DEAL && InpOrderType==TYPE_ORDER_DEAL) order.Print(); //--- if this is a historical market order if(order.Status()==ORDER_STATUS_HISTORY_ORDER && InpOrderType==TYPE_ORDER_MARKET) order.Print(); //--- if this is a removed pending order if(order.Status()==ORDER_STATUS_HISTORY_PENDING && InpOrderType==TYPE_ORDER_PENDING) order.Print(); } //--- return(INIT_SUCCEEDED); } //+------------------------------------------------------------------+
简单地将成交替换为余额操作。 在这种情况下,我们直接在 EA 中使用条件编译,这对于最终产品是不正确的,其中应向用户隐藏按语言版本划分的所有操作。 但在这种情况下,我们只是测试函数库的改进结果,所以这不算什么大问题。
我们添加对 EA 代码的微小更改,用 MQL4 余额操作替换 MQL5 的成交:
//+------------------------------------------------------------------+ //| TestDoEasyPart03_1.mq4 | //| Copyright 2018, MetaQuotes Software Corp. | //| https://mql5.com/en/users/artmedia70 | //+------------------------------------------------------------------+ #property copyright "Copyright 2018, MetaQuotes Software Corp." #property link "https://mql5.com/en/users/artmedia70" #property version "1.00" //--- includes #include <DoEasy\Collections\HistoryCollection.mqh> //--- enums enum ENUM_TYPE_ORDERS { TYPE_ORDER_MARKET, // Market orders TYPE_ORDER_PENDING, // Pending orders #ifdef __MQL5__ TYPE_ORDER_DEAL // Deals #else TYPE_ORDER_BALANCE // Balance/Credit #endif }; //--- input parameters input ENUM_TYPE_ORDERS InpOrderType = TYPE_ORDER_MARKET; // Show type: input datetime InpTimeBegin = 0; // Start date of required range input datetime InpTimeEnd = END_TIME; // End date of required range //--- global variables CHistoryCollection history; //+------------------------------------------------------------------+ //| Expert initialization function | //+------------------------------------------------------------------+ int OnInit() { //--- update history history.Refresh(); //--- get the collection list within the date range CArrayObj* list=history.GetListByTime(InpTimeBegin,InpTimeEnd,SELECT_BY_TIME_CLOSE); if(list==NULL) { Print("Could not get collection list"); return INIT_FAILED; } int total=list.Total(); for(int i=0;i<total;i++) { //--- get the order from the list COrder* order=list.At(i); if(order==NULL) continue; //--- if this is a deal #ifdef __MQL5__ if(order.Status()==ORDER_STATUS_DEAL && InpOrderType==TYPE_ORDER_DEAL) order.Print(); #else //--- if this is a balance/credit operation if(order.Status()==ORDER_STATUS_BALANCE && InpOrderType==TYPE_ORDER_BALANCE) order.Print(); #endif //--- if this is a historical market order if(order.Status()==ORDER_STATUS_HISTORY_ORDER && InpOrderType==TYPE_ORDER_MARKET) order.Print(); //--- if this is a removed pending order if(order.Status()==ORDER_STATUS_HISTORY_PENDING && InpOrderType==TYPE_ORDER_PENDING) order.Print(); } //--- return(INIT_SUCCEEDED); } //+------------------------------------------------------------------+ //| Expert deinitialization function | //+------------------------------------------------------------------+ void OnDeinit(const int reason) { //--- } //+------------------------------------------------------------------+ //| Expert tick function | //+------------------------------------------------------------------+ void OnTick() { //--- } //+------------------------------------------------------------------+
在终端中编译并启动 EA(来自第三篇文章的测试 EA 仅在 OnInit() 处理程序中工作,因此它将在启动后或更改列表后显示一次在设置中所需的历史集合列表)。
在启动 EA 之前,在终端的“帐户历史记录”选项卡的关联菜单中选择“所有历史记录”选项,因为在 MetaTrader 4 中,应用程序可用的历史记录数量取决于选项卡中选择的历史记录大小。
在设置中选择余额/信贷,并在日志中显示第一笔余额:
现在我们需要检查平仓的搜索和显示是否正确。 由于我最近才开设了 MetaTrader 4 账户,因此其中没有交易。 我开了卖单,设置了止损和止盈,然后离开去煮咖啡。 当我回来后,持仓被止损平仓,行情开始向空头方向移动。 对的,一切总是那样! :)
但现在有了一个平仓可进行测试。
在设置中选择“在场订单”:
现在我们检查已删除的挂单列表。 我设置了几对订单,然后将它们删除。
在设置中选择“挂单”:
还会显示已删除的挂单列表。
下一步是什么?
在下一篇文章中,我们将实现在 MQL4 中处理持仓和激活挂单的能力。
下面附有当前版本函数库的所有文件,以及测试 EA文件,供您测试和下载。
在评论中留下您的问题、意见和建议。
系列中的前几篇文章:
第一部分 概念,数据管理。
第二部分
历史订单和成交集合。
第三部分 在场订单和持仓集合,安排搜索。
第四部分
交易事件, 概念。
第五部分 交易事件的集合和类。 将事件发送到程序。
第六部分
净持帐户事件。
第七部分 StopLimit 挂单激活事件,为订单和持仓修改事件准备功能。
第八部分
订单和持仓修改事件。
本文由MetaQuotes Ltd译自俄文
原文地址: https://www.mql5.com/ru/articles/6651
注意: MetaQuotes Ltd.将保留所有关于这些材料的权利。全部或部分复制或者转载这些材料将被禁止。
This article was written by a user of the site and reflects their personal views. MetaQuotes Ltd is not responsible for the accuracy of the information presented, nor for any consequences resulting from the use of the solutions, strategies or recommendations described.




逐渐地,该库将会添加大量功能,从而可以非常轻松地按照自己的方式处理算法。这正是它的设计初衷。
现在,虽然没有这样的功能,但您可以看看测试的智能交易系统 是如何与 MQL5标准库中 的CTrade 交易类 一起工作的,并编写类似的结构来调用必要的交易功能。在那里(测试 EA 中)有一个 MQL4 的测试交易函数调用。
谢谢,我会研究的。
下午好。我喜欢您试用的 Expert Advisor。我想把它作为一个内核来使用,它可以接收来自各种指标、指标组合或通过按键手动控制的信号和过滤器。
您已经看到了第一个这样的智能交易系统,并在本论坛的邻近主题上帮助我为它注入了活力。
您能告诉我如何在您的这个试用版 EA 中以编程方式按下按钮吗?
有合适的功能吗?
或者,请告诉我最好的方法。
下午好!
谢尔盖,我支持你,因为我看到你也遇到了类似的情况。
是的,文章很好,但其中关于如何 使用书面代码的信息却很少。一般来说,库的价值在于隐藏实现,并为实际任务提供清晰的界面。https://docs.mql4.com/strings/stringsubstr 函数的帮助对其内部结构只字未提。对输入参数、处理结果和示例的描述。这是我希望看到的。
是的,Artem,你无疑是一位才华横溢的程序员,但应用工程师需要尽快开发出另一种算法,而不是花几个小时在别人的几百行代码中寻找启迪。到目前为止,这一系列文章更多的是理论性的。
这不是我第一次就这一主题发表文章)。我绝不想贬低这一系列文章的优点。恰恰相反,我希望,Artem,您能考虑到论坛成员的要求,并在 EA 中使用这些书面库,就像引用好电影一样热切。
下午好!
谢尔盖,我支持你,因为我看到你也遇到了类似的情况。
是的,文章很好,但其中关于如何 使用书面代码的信息却很少。一般来说,库的价值在于隐藏实现,并为实际任务提供清晰的界面。https://docs.mql4.com/strings/stringsubstr 函数的帮助对其内部结构只字未提。对输入参数、处理结果和示例的描述。这正是我希望看到的。
是的,Artem,你无疑是一位才华横溢的程序员,但应用工程师需要的是解决实际任务,而不是花几个小时去研究别人的几百行代码,以寻求启迪。到目前为止,这一系列文章更多的是理论性的。
这不是我第一次就这一主题发表文章)。我绝不想贬低这一系列文章的优点。恰恰相反,我希望,Artem,您能考虑到论坛成员的要求,并在 EA 中使用这些书面库,就像引用好电影一样热切。
我们的目标是引导读者从开始创建库到完成库。
您看--这些文章更多的是教育性质的,同时也有有用的实用目的,而且不止一个。代码的设计通俗易懂,不会为了曲折和 "酷 "而使用曲折和未记录的功能。但有一个不可否认的优点--已经发布了多少个测试版的终端,有多少人已经说过他们的代码停止工作了,而这个库--从一个构建到另一个构建,没有因为某些东西突然停止工作而被强制修复....。
该库目前只有一个入口点--CEngine 类(还会有第二个入口点,但要晚得多),而 EA 中的该类对象可以完全访问所有功能。
此外,创建这样一个对象并不困难,例如例如:CEngine lib;在代码中键入 lib 并加点(如下所示:lib.)--加点后,编辑器将显示一个窗口,其中包含该库所有可用方法的列表。大多数方法的名称都很有意义,只要稍加练习就能使用。所有方法都在文章中有所描述。每篇文章中都有一个测试程序示例,展示了一小部分可能的方法。
我同意--在没有参考资料的情况下,在众多文章中寻找所展示的方法及其应用是一项艰巨的任务....。但是,文章的循环是为了让读者和我一起阅读,然后在他的头脑中储存一些东西:)我提醒您,这样做的目的是为了教育。
会有参考资料。但在最后--建立图书馆的时候。当然,还有实例。
在此期间,您可以提出实际问题。请展示你的部分代码,我会给你提示。我就在这里,哪儿也不去--放弃我已经开始的工作不符合我的规则。
我们的目标是带领读者从图书馆的建立开始,一直到图书馆的完成。
你看--这些文章更多的是教育性质的,同时具有有用的实际目的,而且不止一个。代码的设计通俗易懂,不会为了曲折和 "酷 "而使用曲折和未记录的功能。但有一个不可否认的优点--已经发布了多少个测试版的终端,有多少人已经说过他们的代码停止工作了,而这个库--从一个构建到另一个构建,没有因为某些东西突然停止工作而被迫修正....。
目前,该库只有一个入口点--CEngine 类(还会有第二个入口点,但要晚得多),EA 中的该类对象可以完全访问所有功能。
此外,创建这样一个对象并不困难,例如例如:CEngine lib;在代码中键入 lib 并加点(像这样:lib.)--在加点后,编辑器将显示一个窗口,其中包含该库所有可用方法的列表。大多数方法的名称都很有意义,只要稍加练习就能使用。所有方法都在文章中有所描述。每篇文章都包含一个测试程序示例,只展示了一小部分可能性。
我同意--在没有参考资料的情况下,在众多文章中寻找所展示的方法及其应用是一项艰巨的任务....。但文章的循环是为了让读者和我一起阅读,然后在他的头脑中储存一些东西:)我提醒您,这样做的目的是为了教育。
会有参考资料。但在最后--建立图书馆的时候。当然,还有实例。
在此期间,您可以提出实际问题。请展示你的部分代码,我会给你提示。我就在这里,哪儿也不去--放弃我已经开始的工作不符合我的规则。
我知道你的意图是好的,而且你可能有很多空闲时间)。
我刚刚看到了您的"MakingSimple "系列 文章[ 用于方便快捷地创建 MetaTrader 程序的库],并认为在阅读 10-15 分钟后,我就能使用有用的代码了。我期望看到的是一篇经典文章,如https://www.mql5.com/zh/articles/272, 其中的逻辑是隐藏的,界面是开放的,其中的问题是有答案的:"为什么需要它"、"如何使用它 "以及示例。原来,我们的目标是培训,而不是 RAD(快速开发)。
好吧,我们期待看到你写出这样的文章!))
我知道你的出发点是好的,而且你可能有很多空闲时间 )
我刚刚看到您的 "MakingSimple " 系列 文章[M etaTrader 简易快速程序库],我以为阅读 10-15 分钟后就能使用有用的代码。我期望看到的是一篇经典文章,如https://www.mql5.com/zh/articles/272, 其中的逻辑是隐藏的,界面是开放的,其中的问题是有答案的:"为什么需要它"、"如何使用它 "以及示例。 结果发现,目标是培训,而不是 RAD(快速开发)。
好吧,我们期待看到你写出这样的文章!))
目标是学习 + 快速开发。关于快速开发,只有在你懒得看书,又没有实例参考资料的情况下,才值得提出实际应用问题。
标题的意思是 "让它变得简单"。(英语......,如果没有上下文,可以任意翻译)。