交易机器人在市场发布前必须经过的检验(基础篇)
「机器人上线前的必要检验环节」
在 MT5 里跑一个交易机器人,别急着挂真仓。发布到社区或自用前,至少要过一遍历史回测与策略测试器的多周期验证。 以 2016 年 9 月 6 日某公开示例机器人为例,其帖文在平台获得 9 745 次浏览、277 条讨论,说明未经检验的 EA 一旦流出,用户反馈会直接暴露逻辑漏洞。 外汇与贵金属杠杆高、滑点随机,EA 在历史表现稳定也可能在实盘失效。建议先在策略测试器用 99% 建模质量跑至少 3 个月 tick 数据,再考虑是否上模拟盘。
◍ 上架前先过这几道质量关
EA 或指标里一个不起眼的逻辑错误,就可能在实盘里直接吃掉账户本金。MQL5 市场因此设了强制预检,开发者在发布前必须自查一批硬约束,否则审核会被打回重改。 以下几类是最常见的拦截点:保证金不足仍发单、交易量无效、挂单数量超限、品种手数超限;TP/SL 设在 SYMBOL_TRADE_STOPS_LEVEL 最小止损级以内,或在 SYMBOL_TRADE_FREEZE_LEVEL 冻结区内改单;对无历史报价的品种下单报错;还有数组越界、除零、改价未变、引入 EX4/EX5 与 DLL、用 iCustom() 调自定义指标、传无效参数、访问冲突、CPU 与内存占用异常等运行时问题。 外汇与贵金属杠杆高、滑点突变频繁,这类 bug 在回测里不显形,真上架后极易引发连锁亏损。审核员一旦在检验中抓到任一错误,开发者须全部修完才能过审,没有讨价还价余地。
用策略测试器抓EA逻辑漏洞
MT5 自带的策略测试器不只是跑回测,它把 EA 在测试中所有交易操作消息和算法错误都打到测试器日志(Journal)里。配合右键菜单调出的日志阅读器,能把杂乱输出梳理清楚。 测试完 EA 后打开阅读器并勾选“只显示错误(Error only)”,有 bug 会立刻列出来。第一次没报错不代表干净,换交易品种、周期、输入参数,甚至改初始存款重跑几轮,能挖出绝大多数隐藏问题。按官方经验,这套做法覆盖约 99% 的开发期错误。 真要定位某条错误的原因,切到 MetaEditor 的“在历史数据上调试”。可视化测试模式下,价格、指标和每一刻的变量值同屏可见,不用挂实盘苦等,就能把策略逻辑顺一遍。外汇和贵金属波动剧烈,EA 未充分调试就上实盘属高风险行为。
「下单前先卡死保证金缺口」
很多 EA 在发单前只判断信号,不查账户余额,结果挂单触发时直接报『保证金不足』被券商拒绝,这种疏忽在实盘里会漏掉关键入场。要注意,挂单虽然没成交,但平台通常会预占保证金,所以即便单子还挂着,可用资金也可能已经不够后续建仓。 建议用 1 美元或 1 欧元的极小入金先跑机器人,这样一旦资金逻辑有漏洞,损失可控,也容易暴露保证金计算错误。若检查显示自由保证金 cover 不住这笔单,应该打印错误日志并跳过 OrderSend(),而不是硬发。 下面 MQL5 的 CheckMoneyForTrade() 是标准做法:先取 tick 的 ask/bid 作为建仓价,再用 OrderCalcMargin() 算所需保证金,最后拿 ACCOUNT_MARGIN_FREE 比大小。 [CODE] bool CheckMoneyForTrade(string symb,double lots,ENUM_ORDER_TYPE type) { //--- 取得建仓价格 MqlTick mqltick; SymbolInfoTick(symb,mqltick); double price=mqltick.ask; if(type==ORDER_TYPE_SELL) price=mqltick.bid; //--- 所需以及可用保证金的数值 double margin,free_margin=AccountInfoDouble(ACCOUNT_MARGIN_FREE); //--- 调用检验函数 if(!OrderCalcMargin(type,symb,lots,price,margin)) { //--- 出错了,发送报告并返回 false Print("有错误出现在 ",__FUNCTION__," 编号=" ,GetLastError()); return(false); } //--- 如果资金不够进行操作 if(margin>free_margin) { //--- 报告错误并返回 false Print("资金不足以进行 ",EnumToString(type)," ",lots," ",symb," 错误编号=" ,GetLastError()); return(false); } //--- 检验成功 return(true); } [/CODE] 逐行看:MqlTick 结构接实时报价;卖单用 bid、买单用 ask;OrderCalcMargin 失败就直接返 false 并打错误码;margin>free_margin 这一句才是真正的资金闸门。外汇和贵金属杠杆高,自由保证金随浮亏实时缩水,跑 EA 前务必在 MT5 用策略测试器接这段函数验一遍。
class="type">bool CheckMoneyForTrade(class="type">class="kw">string symb,class="type">class="kw">double lots,ENUM_ORDER_TYPE type) { class=class="str">"cmt">//--- 取得建仓价格 class="type">MqlTick mqltick; SymbolInfoTick(symb,mqltick); class="type">class="kw">double price=mqltick.ask; if(type==ORDER_TYPE_SELL) price=mqltick.bid; class=class="str">"cmt">//--- 所需以及可用保证金的数值 class="type">class="kw">double margin,free_margin=AccountInfoDouble(ACCOUNT_MARGIN_FREE); class=class="str">"cmt">//--- 调用检验函数 if(!OrderCalcMargin(type,symb,lots,price,margin)) { class=class="str">"cmt">//--- 出错了,发送报告并返回 class="kw">false Print("有错误出现在 ",__FUNCTION__," 编号=" ,GetLastError()); class="kw">return(class="kw">false); } class=class="str">"cmt">//--- 如果资金不够进行操作 if(margin>free_margin) { class=class="str">"cmt">//--- 报告错误并返回 class="kw">false Print("资金不足以进行 ",EnumToString(type)," ",lots," ",symb," 错误编号=" ,GetLastError()); class="kw">return(class="kw">false); } class=class="str">"cmt">//--- 检验成功 class="kw">return(true); }
◍ 下单前先卡死手数边界
EA 在调用 OrderSend() 之前,必须确认委托手数落在品种规则内,否则订单会直接被服务器拒掉。每个品种的最小交易量、最大交易量、步长都写在合约规格里,MT5 中用 SymbolInfoDouble() 配合 SYMBOL_VOLUME_MIN、SYMBOL_VOLUME_MAX、SYMBOL_VOLUME_STEP 三个枚举值实时读取,而不是写死在代码里。 下面这段检查函数就是干这个的:先读最小量,手数不够就返回 false 并带出描述;再读最大量,超了同样拒掉;最后取步长,用 MathRound(volume/step) 反推整数倍,偏差大于 1e-7 就判定不在网格上。 实盘里黄金 XAUUSD 常见 min=0.01、max=100、step=0.01,但部分平台原油或指数 step 可能是 0.1 甚至 1.0。若 EA 按 0.05 下单而 step=0.1,就会被判定为无效交易量。外汇与贵金属杠杆高,手数错配可能触发意外敞口,务必在发单前拦截。
class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| 检查订单交易量的正确性 | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">bool CheckVolumeValue(class="type">class="kw">double volume,class="type">class="kw">string &description) { class=class="str">"cmt">//--- 交易操作允许的最小交易量 class="type">class="kw">double min_volume=SymbolInfoDouble(Symbol(),SYMBOL_VOLUME_MIN); if(volume<min_volume) { description=StringFormat("交易量小于允许的最小交易量,SYMBOL_VOLUME_MIN=%.2f",min_volume); class="kw">return(class="kw">false); } class=class="str">"cmt">//--- 交易操作允许的最大交易量 class="type">class="kw">double max_volume=SymbolInfoDouble(Symbol(),SYMBOL_VOLUME_MAX); if(volume>max_volume) { description=StringFormat("交易量大于允许的最大交易量,SYMBOL_VOLUME_MAX=%.2f",max_volume); class="kw">return(class="kw">false); } class=class="str">"cmt">//--- 取得交易量变化的最小步长 class="type">class="kw">double volume_step=SymbolInfoDouble(Symbol(),SYMBOL_VOLUME_STEP); class="type">int ratio=(class="type">int)MathRound(volume/volume_step); if(MathAbs(ratio*volume_step-volume)>class="num">0.0000001) { description=StringFormat("交易量不是最小交易步长的整数倍,SYMBOL_VOLUME_STEP=%.2f, 最接近的正确交易量是 %.2f", volume_step,ratio*volume_step); class="kw">return(class="kw">false); } description="正确的交易量数值"; class="kw">return(true); }
挂单数量上限的检查盲区
MT5 账户对同时活动的挂单数常有硬上限,用 ACCOUNT_LIMIT_ORDERS 读取到的就是这个值。下面这段判断逻辑能直接抄进 EA:先取上限赋给 max_allowed_orders,若为 0 表示经纪商不限制,直接放行;否则拿 OrdersTotal() 的当前挂单数去比大小,未超才返回 true。 [CODE] //+------------------------------------------------------------------+
| // | 检查是否还允许设置订单 |
|---|
//+------------------------------------------------------------------+ bool IsNewOrderAllowed() { //--- 取得账户中允许设置的挂单数量 int max_allowed_orders=(int)AccountInfoInteger(ACCOUNT_LIMIT_ORDERS); //--- 如果没有限制,返回 true; 您可以发送一个订单 if(max_allowed_orders==0) return(true); //--- 如果我们达到这一行,说明有限制; 找出已经设置了多少挂单 int orders=OrdersTotal(); //--- 返回比较结果 return(orders<max_allowed_orders); } [/CODE] 逐行看:第 4 行用 AccountInfoInteger 拿账户级订单限制;第 6 行遇 0 即无限制,立刻可发单;第 9 行 OrdersTotal 统计当前挂单;第 11 行用小于号判断余量。 这个函数只防了“总数”这一道关。它没算品种层面的敞口上限与单品种挂单配额,实盘里可能碰到某品种挂不进、但总数还没满的情况。外汇与贵金属杠杆高,这类隐性限制会让你策略在波动期漏单,上 MT5 用账户窗口和品种规格自查一遍更稳。
class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| 检查是否还允许设置订单 | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">bool IsNewOrderAllowed() { class=class="str">"cmt">//--- 取得账户中允许设置的挂单数量 class="type">int max_allowed_orders=(class="type">int)AccountInfoInteger(ACCOUNT_LIMIT_ORDERS); class=class="str">"cmt">//--- 如果没有限制,返回 true; 您可以发送一个订单 if(max_allowed_orders==class="num">0) class="kw">return(true); class=class="str">"cmt">//--- 如果我们达到这一行,说明有限制; 找出已经设置了多少挂单 class="type">int orders=OrdersTotal(); class=class="str">"cmt">//--- 返回比较结果 class="kw">return(orders<max_allowed_orders); }