使用 SMA 和 EMA 自动优化止盈和指标参数的示例·综合运用
(3/3)·当机器学习碰上双均线,EA 如何在 tick 级动态重写止损止盈与周期参数
手数合规与保证金前置校验
在 MT5 下发订单前,先把手数卡在品种约束内,否则 OrderSend 会直接返回 4756(无效交易量)。下面这段逻辑先取 SYMBOL_VOLUME_MAX,若传入 volume 超过上限直接 return(false);再取 SYMBOL_VOLUME_STEP,用 MathRound(volume/volume_step) 反算步长倍数,偏差容限压到 1e-7,不匹配也拒单。 保证金侧用 OrderCalcMargin 按市价算占用,而不是拍脑袋估。CheckMoneyForTrade 里先 SymbolInfoTick 拿 ask/bid,再比 margin 和 ACCOUNT_MARGIN_FREE,不足就 Print 报错并 return(false)。外汇与贵金属杠杆波动大,自由保证金被蚕食时这种前置拦截能避免被动强平。 动态手数函数 get_lot 给了两种模式:固定手数走 normalize_lot 原样规整;风险百分比模式则用 (余额 × 风险%/100) ÷ 单手保证金 得出手数。OrderCalcMargin 算 1.0 手保证金若失败,兜底退回 inp_lot_fix,保证机器人不会在行情乱流中算不出仓位就崩。
class="type">class="kw">double max_volume=SymbolInfoDouble(Symbol(),SYMBOL_VOLUME_MAX); if(volume>max_volume) { class=class="str">"cmt">//description=StringFormat("Volume is greater than the maximal allowed SYMBOL_VOLUME_MAX=%.2f",max_volume); class="kw">return(false); } class=class="str">"cmt">//--- get minimal step of volume changing 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) { class=class="str">"cmt">//description=StringFormat("Volume is not a multiple of the minimal step SYMBOL_VOLUME_STEP=%.2f, the closest correct volume is %.2f", class=class="str">"cmt">//volume_step,ratio*volume_step); class="kw">return(false); } class=class="str">"cmt">//description="Correct volume value"; class="kw">return(true); } class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">bool CheckMoneyForTrade(class="type">class="kw">string symb,class="type">class="kw">double lots,ENUM_ORDER_TYPE type) { class=class="str">"cmt">//--- Getting the opening price 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">//--- values of the required and free margin class="type">class="kw">double margin,free_margin=AccountInfoDouble(ACCOUNT_MARGIN_FREE); class=class="str">"cmt">//--- call of the checking function if(!OrderCalcMargin(type,symb,lots,price,margin)) { class=class="str">"cmt">//--- something went wrong, report and class="kw">return false Print("Error in ",__FUNCTION__," code=",GetLastError()); class="kw">return(false); } class=class="str">"cmt">//--- if there are insufficient funds to perform the operation if(margin>free_margin) { class=class="str">"cmt">//--- report the error and class="kw">return false Print("Not enough money for ",EnumToString(type)," ",lots," ",symb," Error code=",GetLastError()); class="kw">return(false); } class=class="str">"cmt">//--- checking successful class="kw">return(true); } class="type">class="kw">double get_lot(class="type">class="kw">double price) { if(inp_lot_type==LOT_TYPE_FIX) class="kw">return(normalize_lot(inp_lot_fix)); class="type">class="kw">double one_lot_margin; if(!OrderCalcMargin(ORDER_TYPE_BUY,_Symbol,class="num">1.0,price,one_lot_margin)) class="kw">return(inp_lot_fix); class="kw">return(normalize_lot((AccountInfoDouble(ACCOUNT_BALANCE)*(inp_lot_risk/class="num">100))/ one_lot_margin)); } class=class="str">"cmt">//<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< class="type">class="kw">double normalize_lot(class="type">class="kw">double lt) {
「手数落地的三步取整」
MT5 里算出来的理论仓位经常不能直接下单,因为经纪商对每品种都有最小手数和步进限制。上面这段收尾逻辑就是把手数 lt 真正「驯服」成可成交值。 先取 SYMBOL_VOLUME_STEP,这是该品种手数最小变动单位,比如欧美常见 0.01。MathFloor(lt / lot_step) * lot_step 把理论值向下截断到步进整数倍,避免下出 0.123 这种拒单手数。 再取 SYMBOL_VOLUME_MIN 做 MathMax 兜底,保证不低于最小开仓量(常见 0.01)。返回前 lt 已是合规手数,直接喂给 OrderSend 或 CTrade 就不会因精度被踢单。 实测在 XAUUSD 上若忽略 step 取整,用 0.015 手下单,多数平台会直接返回 4756 无效交易量错误;加上这段后回测与实盘一致性明显提升。外汇与贵金属杠杆高,手数错配会放大滑点与强平风险,参数务必用 SymbolInfoDouble 实时取而非写死。
class="type">class="kw">double lot_step = SymbolInfoDouble(_Symbol,SYMBOL_VOLUME_STEP); lt = MathFloor(lt / lot_step) * lot_step; class="type">class="kw">double lot_minimum = SymbolInfoDouble(_Symbol,SYMBOL_VOLUME_MIN); lt = MathMax(lt, lot_minimum); class="kw">return(lt); }
◍ AAPL 智能 EA 的模块拆解与回测真相
这套面向 #AAPL 的 EA 把 ONNX 模型塞进 MT5,用近期行情跑前向预测,再和 SMA、EMA 双均线信号叠加生成进出场指令。它不是写死参数的老式网格,而是把 ML 输出当成「概率前置层」接在技术指标后面。 初始化阶段加载 ONNX 权重与均线句柄;主循环里模型先吐出价格倾向,技术指标做二次确认,两者同向才触发。优化函数按周期重算均线周期与交易手数,ATR 动态给出止损止盈距离,属于随波动伸缩的风控。 回测挂在 AAPL 历史数据上,夏普比率 6.21,风险调整收益看起来很硬;但回撤绝对值偏高,说明暴冲行情里平均止损可能被打穿。外汇与贵金属之外,单股 CFD 同样高风险,MT5 里用真实点差重跑一遍再信。
画得少,看得清
这套 EA 把 ONNX 模型塞进 MT5,用 D1 周期的训练文件覆盖 AAPL 与 EURUSD(模型文件均约 884 KB),靠动态手数、ATR 止损和多种追踪止损去控回撤。回测里利润曲线能看,但高回撤那段说明风控还有缝,真要上实盘得先在策略测试器里把 ATR 倍数和手数系数自己跑一遍。 它原本为股票写,但模型替换成本不高,换掉 onnx 就能挪到别的品种,这也是它值得当模板的原因。外汇和贵金属波动更野,高杠杆下回撤可能来得更快,动手前务必用模拟账户验过再谈真钱。 作者留了 SE_v9.mq5 的源码包,想改的人直接读 OnInit 里的模型加载和 OnTick 里的仓位判断就行。工具再聪明也得人盯着,调一次参数、看一眼回测,比信任何「前景广阔」的形容词都实在。