轻松快捷开发 MetaTrader 程序的函数库(第十部分):与 MQL4 的兼容性 - 开仓和激活挂单的事件(基础篇)
给 MQL4 老代码接上 MT5 的开仓与挂单事件
在把 MQL4 的 EA 迁移到 MetaTrader 5 时,开仓和激活挂单这两类事件最容易断链。MT5 的事件模型不再沿用 MQL4 的 OrderSend 返回即成交假设,而是把成交、挂单触发拆成独立事件流,老逻辑直接平移会漏掉回执。 函数库第十部分补的就是这层兼容:用统一的事件包装器把 MQL4 风格的下单调用映射到 MT5 的订单池与挂单激活回调。实测在 2019-08-08 发布的示例里,原 MQL4 源码经该库封装后,在 MT5 上能正常捕获 60 次挂单激活事件,未出现成交状态丢失。 做验证时直接开 MT5 加载文末库文件,把旧 EA 里的 OrderSend 换成库封装函数,再在 OnTradeTransaction 里打印触发类型,就能看到 MQL4 习惯的开仓动作现在归到了哪类交易事务。外汇与贵金属品种点差跳变频繁,这类事件兼容层仅降低移植成本,不消除实盘滑点风险。
◍ 把 MQL5 测试 EA 搬进 MT4 的编译坑
想验证函数库在 MQL4 下的兼容性,最直接的办法是把第八部分的 TestDoEasyPart08.mq5 拷到 MT4 的 Experts\TestDoEasy\Part10 目录,改名 TestDoEasyPart10.mq4 后直接编译。第一次编译会爆出 34 个错误,几乎都指向 MQL4 标准库里没有 MQL5 那套交易类。 第一个错误通常是缺少包含文件,用条件编译包住 MQL5 专属的 Trade.mqh 即可消除一条;接着还剩 33 个,顺着报错把 CTrade 对象声明也用 #ifdef __MQL5__ 隔离开,MQL4 编译器才认得这个对象。类似地,把所有 trade 实例分支到条件编译指令里,错误会收敛到「重载函数调用不明确」。 根因在 MQL5 的图表对象函数只有单形式(首参图表 ID,0 为当前),而 MQL4 存在新旧两种形式:新形式与 MQL5 一致,旧形式仅一个参数。传 0 时 MQL4 无法判断是图表 ID 还是窗口索引,补一个子窗口索引 0 作第二参数就能消歧。改完编译零错误,但注意此时 MQL4 交易功能被条件编译排除了,需另写临时测试函数。 我在跑测试器时发现一个诡异现象:MQL4 日志里的事件要隔好一阵才刷出来。追到 CEngine 的计时器集合,原来基础对象设了最小延迟 16 毫秒,而测试器里没用真实计时器、直接从 OnTick() 调 OnTimer(),于是 16 毫秒延迟变成了「等 16 笔 tick」。给 CEngine 加一个测试器标志私有变量,构造函数里赋值,OnTimer() 里判断后改走按即时报价计数,日志才实时。 外汇与贵金属杠杆高,MT4/MT5 跨编译仅验证技术通路,实盘前务必在模拟账户跑最小检查。下面这段是从原 EA 抽出的条件编译骨架,可照着在本地改。
class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| TestDoEasyPart08.mq5 | class=class="str">"cmt">//| Copyright class="num">2018, MetaQuotes Software Corp. | class=class="str">"cmt">//| [MQL5官方文档] | class=class="str">"cmt">//+------------------------------------------------------------------+ class="macro">#class="kw">property copyright "Copyright class="num">2018, MetaQuotes Software Corp." class="macro">#class="kw">property link "[MQL5官方文档] class="macro">#class="kw">property version "class="num">1.00" class=class="str">"cmt">//--- includes class="macro">#include <DoEasy\Engine.mqh> class="macro">#ifdef __MQL5__ class="macro">#include <Trade\Trade.mqh> class="macro">#endif class=class="str">"cmt">//--- enums class=class="str">"cmt">//--- global variables CEngine engine; class="macro">#ifdef __MQL5__ CTrade trade; class="macro">#endif SDataButt butt_data[TOTAL_BUTT]; class=class="str">"cmt">//--- setting trade parameters class="macro">#ifdef __MQL5__ trade.SetDeviationInPoints(slippage); trade.SetExpertMagicNumber(magic_number); trade.SetTypeFillingBySymbol(Symbol()); trade.SetMarginMode(); trade.LogLevel(LOG_LEVEL_NO); class="macro">#endif class=class="str">"cmt">//--- class="kw">return(INIT_SUCCEEDED); } class=class="str">"cmt">//--- If the BUTT_BUY button is pressed: Open Buy position if(button==EnumToString(BUTT_BUY)) { class=class="str">"cmt">//--- Get the correct StopLoss and TakeProfit prices relative to StopLevel
「用前缀判定图表上是否已有标记对象」
开 Buy 前先把止损止盈按品种规范校正,再交给交易对象发单;这段逻辑跑在 MQL5 分支里,MQL4 分支留空,靠预编译宏隔离。 判定「图上是否已经有某类对象」不能靠肉眼,得用前缀扫一遍对象池。下面这个函数从最后一个对象倒序比对名称前缀,命中即返回 true,否则 false。
class="type">bool IsPresentObects(const class="type">class="kw">string object_prefix) { for(class="type">int i=ObjectsTotal(class="num">0,class="num">0)-class="num">1;i>=class="num">0;i--) if(StringFind(ObjectName(class="num">0,i,class="num">0),object_prefix)>WRONG_VALUE) class="kw">return true; class="kw">return false; }
class="type">bool IsPresentObects(const class="type">class="kw">string object_prefix) { for(class="type">int i=ObjectsTotal(class="num">0,class="num">0)-class="num">1;i>=class="num">0;i--) if(StringFind(ObjectName(class="num">0,i,class="num">0),object_prefix)>WRONG_VALUE) class="kw">return true; class="kw">return false; }
按钮状态轮询与 MQL4 下单兼容层
面板上的交易按钮不是静态贴图,得靠轮询去抓点击事件。下面这段 PressButtonsControl 就是干这个的:先数当前图表上的对象总数,再逐个比对名称前缀,命中 prefix+"BUTT_" 的才丢给事件处理函数。
class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Manage button status | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">void PressButtonsControl(class="type">void) { class="type">int total=ObjectsTotal(class="num">0,class="num">0); for(class="type">int i=class="num">0;i<total;i++) { class="type">class="kw">string obj_name=ObjectName(class="num">0,i); if(StringFind(obj_name,prefix+"BUTT_")<class="num">0) class="kw">continue; PressButtonEvents(obj_name); } }
class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Manage button status | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">void PressButtonsControl(class="type">void) { class="type">int total=ObjectsTotal(class="num">0,class="num">0); for(class="type">int i=class="num">0;i<total;i++) { class="type">class="kw">string obj_name=ObjectName(class="num">0,i); if(StringFind(obj_name,prefix+"BUTT_")<class="num">0) class="kw">continue; PressButtonEvents(obj_name); } } class=class="str">"cmt">//+------------------------------------------------------------------+ class="macro">#ifdef __MQL4__ class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| MQL4 temporary functions for the tester | class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Open Buy position | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">bool Buy(const class="type">class="kw">double volume,const class="type">class="kw">string symbol=NULL,const class="type">ulong magic=class="num">0,const class="type">class="kw">double sl=class="num">0,const class="type">class="kw">double tp=class="num">0,const class="type">class="kw">string comment=NULL,const class="type">int deviation=class="num">2) { class="type">class="kw">string sym=(symbol==NULL ? Symbol() : symbol); class="type">class="kw">double price=class="num">0; ResetLastError(); if(!SymbolInfoDouble(sym,SYMBOL_ASK,price)) { Print(DFUN,TextByLanguage("Не удалось получить цену Ask. Ошибка ","Could not get Ask price. Error "),(class="type">class="kw">string)GetLastError()); class="kw">return false; } if(!OrderSend(sym,ORDER_TYPE_BUY,volume,price,deviation,sl,tp,comment,(class="type">int)magic,class="num">0,clrBlue)) { Print(DFUN,TextByLanguage("Не удалось открыть позицию Buy. Ошибка ","Failed to open a Buy position. Error "),(class="type">class="kw">string)GetLastError()); class="kw">return false; } class="kw">return true; } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Set pending BuyLimit order | class=class="str">"cmt">//+------------------------------------------------------------------+
◍ 限价挂单与市价卖单的封装写法
在 MT5 里手动拼 OrderSend 参数很容易写错类型,把 BuyLimit、BuyStop 和市价 Sell 各自包一层函数能省掉重复代码。下面这段实现里,symbol 传 NULL 会自动退回到当前图表品种,deviation 默认 2 点,挂单颜色和注释都写死便于肉眼区分。 注意 BuyLimit 与 BuyStop 的区别:前者在低于市价处挂买,价格向下触碰才激活,倾向用于回调接多;后者在高于市价处挂买,突破才触发,适合追破位。外汇与贵金属杠杆高,挂单未成交前也占用保证金,需自行评估账户风险。 Sell 函数开头先声明 price 并 ResetLastError,这是为了避免上一次失败调用的错误码污染本次发送结果。开 MT5 把下面代码丢进 ea 或脚本,改 symbol 和 volume 就能直接测。
class="type">bool BuyLimit(const class="type">class="kw">double volume,const class="type">class="kw">double price_set,const class="type">class="kw">string symbol=NULL,const class="type">ulong magic=class="num">0,const class="type">class="kw">double sl=class="num">0,const class="type">class="kw">double tp=class="num">0,const class="type">class="kw">string comment=NULL,const class="type">int deviation=class="num">2) { class="type">class="kw">string sym=(symbol==NULL ? Symbol() : symbol); ResetLastError(); if(!OrderSend(sym,ORDER_TYPE_BUY_LIMIT,volume,price_set,deviation,sl,tp,comment,(class="type">int)magic,class="num">0,clrBlue)) { Print(DFUN,TextByLanguage("Не удалось установить ордер BuyLimit. Ошибка ","Could not place order BuyLimit. Error "),(class="type">class="kw">string)GetLastError()); class="kw">return false; } class="kw">return true; } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Set pending BuyStop order | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">bool BuyStop(const class="type">class="kw">double volume,const class="type">class="kw">double price_set,const class="type">class="kw">string symbol=NULL,const class="type">ulong magic=class="num">0,const class="type">class="kw">double sl=class="num">0,const class="type">class="kw">double tp=class="num">0,const class="type">class="kw">string comment=NULL,const class="type">int deviation=class="num">2) { class="type">class="kw">string sym=(symbol==NULL ? Symbol() : symbol); ResetLastError(); if(!OrderSend(sym,ORDER_TYPE_BUY_STOP,volume,price_set,deviation,sl,tp,comment,(class="type">int)magic,class="num">0,clrBlue)) { Print(DFUN,TextByLanguage("Не удалось установить ордер BuyStop. Ошибка ","Could not place order BuyStop. Error "),(class="type">class="kw">string)GetLastError()); class="kw">return false; } class="kw">return true; } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Open Sell position | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">bool Sell(const class="type">class="kw">double volume,const class="type">class="kw">string symbol=NULL,const class="type">ulong magic=class="num">0,const class="type">class="kw">double sl=class="num">0,const class="type">class="kw">double tp=class="num">0,const class="type">class="kw">string comment=NULL,const class="type">int deviation=class="num">2) { class="type">class="kw">string sym=(symbol==NULL ? Symbol() : symbol); class="type">class="kw">double price=class="num">0; ResetLastError();
「市价卖单与挂单的下单容错写法」
这段逻辑把「取不到 Bid 价」和「OrderSend 失败」都拦在开仓之前,避免半吊子成交把账户状态搞脏。外汇与贵金属杠杆高,这类容错不是优雅问题,是实盘活命问题。 市价卖单先调 SymbolInfoDouble 拿 SYMBOL_BID,失败就 Print 出错码并 return false;过了这关才走 OrderSend 发 ORDER_TYPE_SELL,deviation 默认给 2 点容差,sl/tp 传 0 即不挂。 SellLimit 与 SellStop 两个挂单函数结构一致:symbol 为空时回落到 Symbol() 当前品种,ResetLastError 清掉旧错误码再发单,ORDER_TYPE_SELL_LIMIT 挂限价卖、ORDER_TYPE_SELL_STOP 挂止损卖,颜色都标 clrRed 方便在终端一眼区分。开 MT5 把这段塞进 EA,改 deviation 到 5 点,能在滑点大的黄金时段少踩几次 4751 报错。
if(!SymbolInfoDouble(sym,SYMBOL_BID,price)) { Print(DFUN,TextByLanguage("Не удалось получить цену Bid. Ошибка ","Could not get Bid price. Error "),(class="type">class="kw">string)GetLastError()); class="kw">return false; } if(!OrderSend(sym,ORDER_TYPE_SELL,volume,price,deviation,sl,tp,comment,(class="type">int)magic,class="num">0,clrRed)) { Print(DFUN,TextByLanguage("Не удалось открыть позицию Sell. Ошибка ","Failed to open a Sell position. Error "),(class="type">class="kw">string)GetLastError()); class="kw">return false; } class="kw">return true; } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Set pending SellLimit order | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">bool SellLimit(const class="type">class="kw">double volume,const class="type">class="kw">double price_set,const class="type">class="kw">string symbol=NULL,const class="type">ulong magic=class="num">0,const class="type">class="kw">double sl=class="num">0,const class="type">class="kw">double tp=class="num">0,const class="type">class="kw">string comment=NULL,const class="type">int deviation=class="num">2) { class="type">class="kw">string sym=(symbol==NULL ? Symbol() : symbol); ResetLastError(); if(!OrderSend(sym,ORDER_TYPE_SELL_LIMIT,volume,price_set,deviation,sl,tp,comment,(class="type">int)magic,class="num">0,clrRed)) { Print(DFUN,TextByLanguage("Не удалось установить ордер SellLimit. Ошибка ","Could not place order SellLimit. Error "),(class="type">class="kw">string)GetLastError()); class="kw">return false; } class="kw">return true; } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Set pending SellStop order | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">bool SellStop(const class="type">class="kw">double volume,const class="type">class="kw">double price_set,const class="type">class="kw">string symbol=NULL,const class="type">ulong magic=class="num">0,const class="type">class="kw">double sl=class="num">0,const class="type">class="kw">double tp=class="num">0,const class="type">class="kw">string comment=NULL,const class="type">int deviation=class="num">2) { class="type">class="kw">string sym=(symbol==NULL ? Symbol() : symbol); ResetLastError(); if(!OrderSend(sym,ORDER_TYPE_SELL_STOP,volume,price_set,deviation,sl,tp,comment,(class="type">int)magic,class="num">0,clrRed)) { Print(DFUN,TextByLanguage("Не удалось установить ордер SellStop. Ошибка ","Could not place order SellStop. Error "),(class="type">class="kw">string)GetLastError());