昨天一个汇友发了个EA给我看,说跑着跑着就报错,订单开不出来,日志里一堆“invalid stops”和“order modify error 1”。我让他把日志和代码贴过来,一看就知道问题出在哪了——止损设置没处理小数点精度。这种事其实很常见,尤其是新手写EA时容易踩坑,今天干脆整理一下排查实录,算是给自己留个记录,也给遇到类似问题的朋友一个参考。
先说第一个案例:止损位超出允许范围。他用的EURUSD,止损设了20点,但当前价格波动大,点差到了30点,EA直接用了MarketInfo(Symbol(), MODE_STOPLEVEL)返回值,没考虑动态点差。排查步骤是这样的:第一步,先检查日志里的错误代码,比如138就是止损位太近,130是无效参数。第二步,在代码里加个打印,把止损计算值、当前点差、最小允许距离都输出来。第三步,对比后发现他的止损值比最小允许距离还小,所以报错。解决办法很简单:要么在设置止损前加一个判断,如果目标止损小于最小允许距离,就自动调整到最小距离;要么在策略里引入动态点差处理,比如用点差乘以一个系数。
第二个案例更隐蔽:数组越界导致循环崩溃。他用了双均线交叉策略,在OnTick里循环处理多品种订单,结果数组索引直接用了OrderSelect的返回值,没检查是否成功。日志里出现“array out of range”错误,然后EA就停了。排查步骤:第一步,定位到报错的代码行,通常是OrderSelect后直接用了OrderTicket()或OrderLots()。第二步,在OrderSelect前加个if(!OrderSelect(index, SELECT_BY_POS)) continue; 跳过未选中的订单。第三步,检查数组长度是否与订单总数匹配,比如用了OrderHistoryTotal()和OrdersTotal()时,要确保循环次数动态更新,不要硬编码。这个案例让我想起以前自己写的一个多币种EA,就是因为循环里没处理订单选择失败,导致回测时经常中断。
第三个案例是最近才遇到的:时间戳溢出。他用的MQL5写的EA,在回测时发现订单开仓时间显示异常,导致止损逻辑错乱。排查步骤:第一步,打印出当前时间和订单开仓时间,发现时间戳是负数。第二步,检查代码里是否用了TimeCurrent()和OrderOpenTime()做减法,但没考虑夏令时或服务器时间差。第三步,发现他用了GetTickCount()来获取毫秒级时间戳,但MQL5的GetTickCount返回的是从系统启动开始的毫秒数,不是UNIX时间戳。解决办法:改用TimeCurrent()获取服务器时间,或者用TimeTradeServer()。如果非要毫秒级,可以用DateTimeToStruct()转换。
总结一下,EA报错排查的核心思路是:先看日志,定位错误代码;再打印关键变量,缩小范围;最后对比规则文档,比如Broker的止损限制、时间格式要求。推荐大家在代码里加个宏定义,比如#define DEBUG_PRINT 1,平时调试时开启,运行时关闭,免得日志刷屏。另外,建议用Try-Catch结构(MQL5支持)或OrderSelect的返回值判断来捕获异常,避免EA直接崩溃。
最后分享一个我常用的检查函数模板,专门用来处理止损调整:
double AdjustStopLoss(double sl, double bid, double ask, int slippage) {
double minDist = MarketInfo(Symbol(), MODE_STOPLEVEL) * Point;
double spread = (ask - bid) / Point;
double adjustedSL = sl;
if (adjustedSL < minDist + spread) {
adjustedSL = minDist + spread;
}
return NormalizeDouble(adjustedSL, Digits);
}
这个函数先获取最小距离,再考虑点差,最后规范化到小数点位数。用之前记得把止损值传进来,比如OrderModify时直接调用。如果遇到“invalid stops”,八成就是这里没处理好。
今天就写到这,有问题跟帖,我看到就回。
先说第一个案例:止损位超出允许范围。他用的EURUSD,止损设了20点,但当前价格波动大,点差到了30点,EA直接用了MarketInfo(Symbol(), MODE_STOPLEVEL)返回值,没考虑动态点差。排查步骤是这样的:第一步,先检查日志里的错误代码,比如138就是止损位太近,130是无效参数。第二步,在代码里加个打印,把止损计算值、当前点差、最小允许距离都输出来。第三步,对比后发现他的止损值比最小允许距离还小,所以报错。解决办法很简单:要么在设置止损前加一个判断,如果目标止损小于最小允许距离,就自动调整到最小距离;要么在策略里引入动态点差处理,比如用点差乘以一个系数。
第二个案例更隐蔽:数组越界导致循环崩溃。他用了双均线交叉策略,在OnTick里循环处理多品种订单,结果数组索引直接用了OrderSelect的返回值,没检查是否成功。日志里出现“array out of range”错误,然后EA就停了。排查步骤:第一步,定位到报错的代码行,通常是OrderSelect后直接用了OrderTicket()或OrderLots()。第二步,在OrderSelect前加个if(!OrderSelect(index, SELECT_BY_POS)) continue; 跳过未选中的订单。第三步,检查数组长度是否与订单总数匹配,比如用了OrderHistoryTotal()和OrdersTotal()时,要确保循环次数动态更新,不要硬编码。这个案例让我想起以前自己写的一个多币种EA,就是因为循环里没处理订单选择失败,导致回测时经常中断。
第三个案例是最近才遇到的:时间戳溢出。他用的MQL5写的EA,在回测时发现订单开仓时间显示异常,导致止损逻辑错乱。排查步骤:第一步,打印出当前时间和订单开仓时间,发现时间戳是负数。第二步,检查代码里是否用了TimeCurrent()和OrderOpenTime()做减法,但没考虑夏令时或服务器时间差。第三步,发现他用了GetTickCount()来获取毫秒级时间戳,但MQL5的GetTickCount返回的是从系统启动开始的毫秒数,不是UNIX时间戳。解决办法:改用TimeCurrent()获取服务器时间,或者用TimeTradeServer()。如果非要毫秒级,可以用DateTimeToStruct()转换。
总结一下,EA报错排查的核心思路是:先看日志,定位错误代码;再打印关键变量,缩小范围;最后对比规则文档,比如Broker的止损限制、时间格式要求。推荐大家在代码里加个宏定义,比如#define DEBUG_PRINT 1,平时调试时开启,运行时关闭,免得日志刷屏。另外,建议用Try-Catch结构(MQL5支持)或OrderSelect的返回值判断来捕获异常,避免EA直接崩溃。
最后分享一个我常用的检查函数模板,专门用来处理止损调整:
double AdjustStopLoss(double sl, double bid, double ask, int slippage) {
double minDist = MarketInfo(Symbol(), MODE_STOPLEVEL) * Point;
double spread = (ask - bid) / Point;
double adjustedSL = sl;
if (adjustedSL < minDist + spread) {
adjustedSL = minDist + spread;
}
return NormalizeDouble(adjustedSL, Digits);
}
这个函数先获取最小距离,再考虑点差,最后规范化到小数点位数。用之前记得把止损值传进来,比如OrderModify时直接调用。如果遇到“invalid stops”,八成就是这里没处理好。
今天就写到这,有问题跟帖,我看到就回。
专注交易策略编程实现,分享MQL开发技巧与代码优化方案