上周帮一个朋友调他的网格EA,发现他在MQL5里用了个老习惯——直接在OnTick里写了一大串if-else判断开仓条件,结果回测时滑点大得离谱。后来一查,问题出在他压根没用结构体管理订单状态,全凭全局变量硬扛。这玩意儿在MQL4时代还能凑合,但到了MQL5的多线程环境下,变量污染和时序错乱能把人整疯。今天就从这儿说起,聊聊MQL5基础语法里那些容易踩的坑,尤其是从MQL4转过来的朋友。
先说说最基础的事件驱动模型。MQL5里没有start()函数了,取而代之的是OnTick()、OnTrade()、OnTimer()这些。新手最容易犯的错是把所有逻辑全塞进OnTick,觉得省事。但实际跑起来你会发现,OnTick只在报价变动时触发,如果行情瞬间跳空或者流动性枯竭,你的订单状态可能还没更新就被下一根tick覆盖了。我自己的做法是拆成三层:OnTick只负责收集报价和调用信号模块,真正的开平仓逻辑放在OnTrade里,用TradeTransaction事件去监听订单状态变化。这样至少能避免80%的重复下单问题。
再聊动态数组和内存管理。MQL5的数组默认是静态的,但很多人写EA时喜欢用ArrayResize()动态扩容,结果在循环里频繁调用,导致内存碎片化严重。我吃过一次亏,跑欧美15分钟策略,回测到2018年那段波动大的行情时,EA直接崩了。后来改成预分配缓冲区,比如用ArrayResize(priceBuffer, 1000, 1000)预留空间,再配合ArraySetAsSeries()处理时间序列,性能直接提升30%。另外注意,MQL5的数组索引是从0开始,但如果你用CopyRates()拉数据,默认返回的是按时间正序排列,记得要用ArraySetAsSeries(true)反转,不然你取最新价会取到最旧的那根。
第三点,结构体和类的封装。很多MQL4老手习惯用全局变量存持仓信息,比如MagicNumber、手数、开仓价。但MQL5里订单池是动态的,你根本没法用一个固定变量去对应多个订单。我现在的做法是定义一个CTrade类,每个实例绑定一个魔术数字,内部用CPositionInfo去遍历持仓。比如这样:
踩过的坑是,CTrade的Buy()函数默认使用市价单,如果你没设置deviation参数,遇到点差突然扩大时会被拒单。我一般会显式设置trade.SetDeviationInPoints(20),尤其是跑黄金和镑美这种点差容易跳的品种。
最后说下自定义指标和EA的交互。很多人写EA时喜欢直接在OnInit里用iCustom()调用指标句柄,但MQL5的指标句柄是异步更新的,你必须在OnTick里用CopyBuffer()主动拉数据。我见过有人用iMA()然后直接拿Buffer[0]当最新值,结果经常滞后好几根K线。正确做法是:
这个细节我花了整整两天才调明白,因为回测时看起来没问题,但实盘跑起来信号经常晚半拍。
对了,还有个小技巧,用MQL5的OnDeinit清理资源,尤其是你开了文件句柄或者全局指标句柄,不释放的话长时间挂VPS会内存泄漏。我自己的EA每次跑完都会用IndicatorRelease()释放指标,再用EventSetTimer(0)关掉定时器。
说真的,MQL5比MQL4严谨太多,但只要你把事件模型和对象生命周期搞懂,写出来的EA稳定性完全不是一个级别。有没有人遇到过OrderSend()返回true但实际订单没成交的情况?我在模拟账户上碰到过两次,后来发现是没检查ResultRetcode()和ResultDeal(),光看返回值不够,得确认deal ID不为0。你们怎么处理的?欢迎评论区交流。
先说说最基础的事件驱动模型。MQL5里没有start()函数了,取而代之的是OnTick()、OnTrade()、OnTimer()这些。新手最容易犯的错是把所有逻辑全塞进OnTick,觉得省事。但实际跑起来你会发现,OnTick只在报价变动时触发,如果行情瞬间跳空或者流动性枯竭,你的订单状态可能还没更新就被下一根tick覆盖了。我自己的做法是拆成三层:OnTick只负责收集报价和调用信号模块,真正的开平仓逻辑放在OnTrade里,用TradeTransaction事件去监听订单状态变化。这样至少能避免80%的重复下单问题。
再聊动态数组和内存管理。MQL5的数组默认是静态的,但很多人写EA时喜欢用ArrayResize()动态扩容,结果在循环里频繁调用,导致内存碎片化严重。我吃过一次亏,跑欧美15分钟策略,回测到2018年那段波动大的行情时,EA直接崩了。后来改成预分配缓冲区,比如用ArrayResize(priceBuffer, 1000, 1000)预留空间,再配合ArraySetAsSeries()处理时间序列,性能直接提升30%。另外注意,MQL5的数组索引是从0开始,但如果你用CopyRates()拉数据,默认返回的是按时间正序排列,记得要用ArraySetAsSeries(true)反转,不然你取最新价会取到最旧的那根。
第三点,结构体和类的封装。很多MQL4老手习惯用全局变量存持仓信息,比如MagicNumber、手数、开仓价。但MQL5里订单池是动态的,你根本没法用一个固定变量去对应多个订单。我现在的做法是定义一个CTrade类,每个实例绑定一个魔术数字,内部用CPositionInfo去遍历持仓。比如这样:
- CTrade trade; trade.SetExpertMagicNumber(20240601);
- if(trade.Buy(0.1, _Symbol, 0, 0, 0, "网格加仓")) { Print("开仓成功,订单号", trade.ResultOrder()); }
踩过的坑是,CTrade的Buy()函数默认使用市价单,如果你没设置deviation参数,遇到点差突然扩大时会被拒单。我一般会显式设置trade.SetDeviationInPoints(20),尤其是跑黄金和镑美这种点差容易跳的品种。
最后说下自定义指标和EA的交互。很多人写EA时喜欢直接在OnInit里用iCustom()调用指标句柄,但MQL5的指标句柄是异步更新的,你必须在OnTick里用CopyBuffer()主动拉数据。我见过有人用iMA()然后直接拿Buffer[0]当最新值,结果经常滞后好几根K线。正确做法是:
- 在OnInit里创建句柄:int maHandle = iMA(_Symbol, _Period, 14, 0, MODE_EMA, PRICE_CLOSE);
- 在OnTick里用CopyBuffer(maHandle, 0, 0, 1, maBuffer);
- 判断maBuffer[0]和价格的关系,注意maBuffer[0]是当前未收盘K线的值,如果你想等确认,得用maBuffer[1]。
这个细节我花了整整两天才调明白,因为回测时看起来没问题,但实盘跑起来信号经常晚半拍。
对了,还有个小技巧,用MQL5的OnDeinit清理资源,尤其是你开了文件句柄或者全局指标句柄,不释放的话长时间挂VPS会内存泄漏。我自己的EA每次跑完都会用IndicatorRelease()释放指标,再用EventSetTimer(0)关掉定时器。
说真的,MQL5比MQL4严谨太多,但只要你把事件模型和对象生命周期搞懂,写出来的EA稳定性完全不是一个级别。有没有人遇到过OrderSend()返回true但实际订单没成交的情况?我在模拟账户上碰到过两次,后来发现是没检查ResultRetcode()和ResultDeal(),光看返回值不够,得确认deal ID不为0。你们怎么处理的?欢迎评论区交流。
