MQL4与MQL5代码迁移完全指南:语法差异与EA适配实战要点
MQL4与MQL5代码迁移完全指南:语法差异与EA适配实战要点
把一套跑了三年的MQL4 EA往MQL5上搬,结果编译报错47个,改完发现回测结果和原来差了将近一倍——这是我最近帮一个朋友处理迁移时遇到的真实情况。问题不在于代码写得多烂,而是MQL4和MQL5在语法层面的差异远比想象中琐碎。很多人以为改改函数名就行,实际上从数据类型到订单处理逻辑,几乎每一层都有坑。

为什么同样的策略,MQL5回测结果会和MQL4不一样?
最直接的原因:MQL4默认使用即时报价(Instant Execution)模型,而MQL5的Strategy Tester默认按每笔Tick生成报价。如果你的EA逻辑依赖Bid/Ask的固定点差假设,迁移后点差变成浮动,开仓价格就会偏移。
我在测试一个基于均线交叉的EA时发现,MQL4下盈利因子1.42,直接转MQL5后掉到0.87。排查后发现是OrderSend()的填充模式没设置。MQL5要求指定ORDER_FILLING_FOK或ORDER_FILLING_IOC,不设置的话回测引擎会用默认值,而不同经纪商的默认填充模式不一样。
XM的MT5服务器对市价单默认用IOC,Exness则部分品种强制FOK。这就导致同一份代码在两家跑出来的结果不同。解决办法是在MqlTradeRequest里显式声明:
- request.type_filling = ORDER_FILLING_IOC; // 适用于XM多数外汇品种
- request.type_filling = ORDER_FILLING_FOK; // Exness部分CFD品种必须用这个
注意:如果填错,回测可能正常但实盘直接报错Unsupported filling mode。这是新手最容易踩的坑。
订单处理函数的对应关系不是一对一
MQL4里一个OrderSend()能搞定开仓、平仓、修改止损止盈。MQL5把这些拆成了OrderSend()发请求、OrderCheck()预检查、PositionModify()改仓位。很多人迁移时直接把OrderClose()替换成OrderSend()加DEAL_TYPE_SELL,结果发现平仓变成了反向开仓。
正确的平仓逻辑要分两步:先找到持仓订单的ticket,然后发送反向交易请求。代码片段如下:
if(PositionSelect(_Symbol)) {
ulong ticket = PositionGetInteger(POSITION_TICKET);
double volume = PositionGetDouble(POSITION_VOLUME);
ENUM_POSITION_TYPE type = (ENUM_POSITION_TYPE)PositionGetInteger(POSITION_TYPE);
MqlTradeRequest req; MqlTradeResult res;
req.action = TRADE_ACTION_DEAL;
req.position = ticket;
req.volume = volume;
req.type = (type == POSITION_TYPE_BUY) ? ORDER_TYPE_SELL : ORDER_TYPE_BUY;
OrderSend(req, res);
}
常见错误:忘记设置req.position,导致系统开新仓而不是平旧仓。我在第一次迁移时就这么干过,回测里持仓数量翻倍,保证金直接爆掉。
指标句柄:从直接调用到异步获取
MQL4里iMA()直接返回均线值,MQL5里iMA()返回的是句柄(handle),真正的值要用CopyBuffer()去取。这个改动影响巨大——如果你在OnTick()里每次tick都调用iMA()而不释放句柄,内存会持续增长。
我的做法是在OnInit()里一次性创建所有句柄:
int maHandle = iMA(_Symbol, PERIOD_CURRENT, 20, 0, MODE_SMA, PRICE_CLOSE);
然后在OnTick()里用CopyBuffer(maHandle, 0, 0, 3, maValues)取最近三根K线的值。注意CopyBuffer的第三个参数是起始位置,0表示当前K线,1表示上一根。很多人写成CopyBuffer(handle, 0, 1, 3, arr)以为是从第1根开始取3根,实际上是从第1根往更早的方向取,顺序是反的。
实测数据:修正句柄创建位置后,一个多指标EA的内存占用从持续增长的120MB稳定在28MB左右,连续跑72小时没有出现内存告警。
数据类型和结构体的隐式转换陷阱
MQL4里datetime可以直接和int比较,MQL5里必须显式转换。更麻烦的是OrderSend()的返回值:MQL4返回int类型的ticket,MQL5返回bool,真正的ticket在MqlTradeResult结构体的order字段里。
我见过有人写int ticket = OrderSend(...),编译不报错但ticket永远是1(bool的true转int)。正确写法:
MqlTradeResult result;
if(OrderSend(request, result)) {
ulong ticket = result.order;
}
另外,MQL5的SymbolInfoDouble()替代了MQL4的MarketInfo()。点值获取方式变了:MQL4用MarketInfo(_Symbol, MODE_TICKVALUE),MQL5用SymbolInfoDouble(_Symbol, SYMBOL_TRADE_TICK_VALUE)。XM的黄金品种在MT4里tick value是1.0,MT5里因为合约规模定义不同,需要乘以SYMBOL_TRADE_CONTRACT_SIZE才能得到相同的每点价值。
回测参数配置的隐蔽差异
MQL5的Strategy Tester里有一个「随机延迟」选项,默认是关闭的。但MQL4的测试器默认会模拟滑点。迁移后如果不手动打开延迟模拟,回测结果会偏乐观。我的习惯是把延迟设为100-300毫秒,更接近VPS实盘环境。
还有一个参数:MQL5的「每笔Tick生成」模式会大幅增加回测时间。对于均线类策略,用「1分钟OHLC」模式就够了,回测速度能快5-8倍。但如果是剥头皮策略,必须用每笔Tick,否则会漏掉关键价格跳动。
迁移完成后,我通常跑三组回测做对比:MQL4原版、MQL5默认参数、MQL5优化参数。最近一个案例的数据是:原版盈利因子1.38,MQL5默认0.91,调整填充模式和延迟后回到1.26。差距主要来自点差处理和订单执行逻辑。
新手最容易忽略的是OnDeinit()里的句柄释放。MQL5不会自动回收指标句柄,如果EA被反复加载卸载而不释放,终端会逐渐卡顿。记得在OnDeinit()里加IndicatorRelease(handle)。这个细节不写不影响回测,但实盘跑一周后终端响应会明显变慢。
你在迁移过程中遇到过哪些奇怪的编译错误或回测偏差?留言交流。
免责声明:本文仅代表作者观点,不构成任何投资建议。市场有风险,交易需谨慎。
