基于MQL5和Python的自优化EA(第六部分):利用深度双重下降算法·综合运用
「日线布林与双均线喂给 ONNX 的初始化链路」
把 AI 模型接进 MT5 实盘前,先要在 OnInit 里把特征工程所需的指标句柄和模型 IO 形状钉死。这里用的是日线周期:布林带周期 60、均线周期 14,两条 SMA 分别取最高价和最低价,而不是常见的收盘价,等于把通道的上下边界分开建模。 代码里 input 分组把 bb_period 和 ma_period 暴露给 EA 属性框,实盘时可以直接改 60 和 14 看特征分布变化,不用重新编译。iBands 与 iMA 都挂在 PERIOD_D1 上,意味着日内波动不会触发重算,只在每日收盘后换值。 ONNX 部分定义了 input_shape {1,4} 和 output_shape {1,1},说明模型吃 4 个特征、吐 1 个标量信号。OnnxCreateFromBuffer 从内存缓冲区加载,失败就 INIT_FAILED 并在图表上 Comment 报错;SetInputShape / SetOutputShape 若对不上,会用 GetLastError 和 OnnxGetInputCount 把实际维度打在屏幕上,方便你当场抓维度 mismatch。 OnDeinit 里四个 Release 不能省:模型句柄和三个指标句柄不释放,MT5 反复加载 EA 会漏句柄。外汇与贵金属杠杆高,这类日线级 AI 信号延迟天然偏大,仅适合做趋势过滤而非高频触发。
class="kw">input group "Technical Indicators" class="kw">input class="type">int bb_period = class="num">60; class="kw">input class="type">int ma_period = class="num">14; class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Expert initialization function | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">int OnInit() { class=class="str">"cmt">//--- Setup technical indicators bb_handler = iBands(Symbol(),PERIOD_D1,bb_period,class="num">0,class="num">1,PRICE_CLOSE); ma_h_handler = iMA(Symbol(),PERIOD_D1,ma_period,class="num">0,MODE_SMA,PRICE_HIGH); ma_l_handler = iMA(Symbol(),PERIOD_D1,ma_period,class="num">0,MODE_SMA,PRICE_LOW); class=class="str">"cmt">//--- Define our ONNX model class="type">class="kw">ulong input_shape [] = {class="num">1,class="num">4}; class="type">class="kw">ulong output_shape [] = {class="num">1,class="num">1}; class=class="str">"cmt">//--- Create the model onnx_model = OnnxCreateFromBuffer(onnx_buffer,ONNX_DEFAULT); if(onnx_model == INVALID_HANDLE) { Comment("[ERROR] Failed to load AI module correctly"); class="kw">return(INIT_FAILED); } class=class="str">"cmt">//--- Validate I/O if(!OnnxSetInputShape(onnx_model,class="num">0,input_shape)) { Comment("[ERROR] Failed to set class="kw">input shape correctly: ",GetLastError()," Actual shape: ",OnnxGetInputCount(onnx_model)); class="kw">return(INIT_FAILED); } if(!OnnxSetOutputShape(onnx_model,class="num">0,output_shape)) { Comment("[ERROR] Failed to load AI module correctly: ",GetLastError()," Actual shape: ",OnnxGetOutputCount(onnx_model)); class="kw">return(INIT_FAILED); } class=class="str">"cmt">//--- Everything was okay class="kw">return(INIT_SUCCEEDED); } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Expert deinitialization function | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">void OnDeinit(class="kw">const class="type">int reason) { class=class="str">"cmt">//--- OnnxRelease(onnx_model); IndicatorRelease(bb_handler); IndicatorRelease(ma_h_handler); IndicatorRelease(ma_l_handler); } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Expert tick function | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">void OnTick() { class=class="str">"cmt">//--- Update technical data update(); if(PositionsTotal() == class="num">0) {
用 ONNX 模型驱动仓位闭环
这段逻辑把机器学习推断直接嵌进了 EA 的持仓管理。有持仓时,先拿模型输出的预测值和日线收盘价比大小,标记 UP 或 DOWN 并写到图表注释,再触发 close_setup() 去判定平仓条件。 model_predict() 只取最近 3 根 D1 的 O/H/L/C,分别用第 3 根减第 1 根得到差值序列,塞进 4 维 vectorf 喂给 OnnxRun。也就是说模型看的是三日开高低收的位移斜率,而不是绝对价位。 close_setup() 里有个 patience 开关:state==1 时,若慢线 ma_l 上穿布林上轨 bb_h 就把 patience 置 false;若 ma_h 与 ma_l 都掉到 bb_m 下方,则直接 PositionClose。state==-1 的对称逻辑在原文截断处延续,思路一致。 外汇与贵金属杠杆高,模型预测仅提供概率倾向,实盘前务必在 MT5 策略测试器用历史数据验证信号频率与回撤。
patience = true; check_setup(); } if(PositionsTotal() > class="num">0) { class="type">class="kw">string direction = model_forecast[class="num">0] > iClose(Symbol(),PERIOD_D1,class="num">0) ? "UP" : "DOWN"; Comment("Model Forecast: ",model_forecast[class="num">0]," ",direction); close_setup(); } } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Get a prediction from our model | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">void model_predict(class="type">void) { class="type">class="kw">double o,h,l,c; vector op,hi,lo,cl; op.CopyRates(Symbol(),PERIOD_D1,COPY_RATES_OPEN,class="num">0,class="num">3); hi.CopyRates(Symbol(),PERIOD_D1,COPY_RATES_HIGH,class="num">0,class="num">3); lo.CopyRates(Symbol(),PERIOD_D1,COPY_RATES_LOW,class="num">0,class="num">3); cl.CopyRates(Symbol(),PERIOD_D1,COPY_RATES_CLOSE,class="num">0,class="num">3); o = op[class="num">2] - op[class="num">0]; h = hi[class="num">2] - hi[class="num">0]; l = lo[class="num">2] - lo[class="num">0]; c = cl[class="num">2] - cl[class="num">0]; vectorf model_inputs = vectorf::Zeros(class="num">4); model_inputs[class="num">0] = o; model_inputs[class="num">1] = h; model_inputs[class="num">2] = l; model_inputs[class="num">3] = c; OnnxRun(onnx_model,ONNX_DEFAULT,model_inputs,model_forecast); } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Close our open positions | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">void close_setup(class="type">void) { if(patience) { if(state == class="num">1) { if(ma_l_buffer[class="num">0] > bb_h_buffer[class="num">0]) { patience = class="kw">false; } if((ma_h_buffer[class="num">0] < bb_m_buffer[class="num">0]) && (ma_l_buffer[class="num">0] < bb_m_buffer[class="num">0])) { Trade.PositionClose(Symbol()); } } else if(state == -class="num">1) { if(ma_h_buffer[class="num">0] < bb_l_buffer[class="num">0]) {
◍ 出场与建仓的条件耦合
这段逻辑把持仓退出和新信号触发绑在同一套均线+布林通道结构上。当快线高低双轨同时站上布林中轨,且价格状态为多头时,脚本会直接平掉当前仓位——也就是说趋势通道被反向击穿就被判为离场,不拖泥带水。 在 check_setup 里,条件更苛刻一些:高低均线必须同时低于中轨,且模型预测值低于当前收盘价,才允许以 0.3 手卖单介入 GBPUSD,标签写死为 "GBPUSD AI";若低轨已破布林下轨则把 patience 置 false,放弃等待。反向对称逻辑对应买入:双均线高于中轨、预测值高于现价、且高轨破上轨时清掉 patience 后做多 0.3 手。 外汇与贵金属杠杆高,这类固定 0.3 手仓位的 EA 在 GBPUSD 上遇跳空可能滑点放大,实盘前务必在 MT5 策略测试器用 2020—2023 年 tick 数据回测,观察最大回撤是否超出你的承受线。
…
patience = class="kw">false; } if((ma_h_buffer[class="num">0] > bb_m_buffer[class="num">0]) && (ma_l_buffer[class="num">0] > bb_m_buffer[class="num">0])) { Trade.PositionClose(Symbol()); } } } else { if((state == -class="num">1) && (ma_l_buffer[class="num">0] > bb_l_buffer[class="num">0])) { Trade.PositionClose(Symbol()); } if((state == class="num">1) && (ma_h_buffer[class="num">0] < bb_h_buffer[class="num">0])) { Trade.PositionClose(Symbol()); } } } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Check for valid trade setups | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">void check_setup(class="type">void) { if((ma_h_buffer[class="num">0] < bb_m_buffer[class="num">0]) && (ma_l_buffer[class="num">0] < bb_m_buffer[class="num">0])) { model_predict(); if((model_forecast[class="num">0] < iClose(Symbol(),PERIOD_CURRENT,class="num">0))) { if(ma_h_buffer[class="num">0] < bb_l_buffer[class="num">0]) patience = class="kw">false; Trade.Sell(class="num">0.3,Symbol(),bid,class="num">0,class="num">0,"GBPUSD AI"); state = -class="num">1; } } if((ma_h_buffer[class="num">0] > bb_m_buffer[class="num">0]) && (ma_l_buffer[class="num">0] > bb_m_buffer[class="num">0])) { model_predict(); if(model_forecast[class="num">0] > iClose(Symbol(),PERIOD_CURRENT,class="num">0)) { if(ma_l_buffer[class="num">0] > bb_h_buffer[class="num">0]) patience = class="kw">false; Trade.Buy(class="num">0.3,Symbol(),ask,class="num">0,class="num">0,"GBPUSD AI"); state = class="num">1; } } } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Update our market data | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">void update(class="type">void) { CopyBuffer(bb_handler,class="num">0,class="num">0,class="num">1,bb_m_buffer);
「把指标缓冲读进数组的四条调用」
在 MT5 里用 iBands 和两条 iMA 做通道叠加时,光拿到指标句柄还不够,必须靠 CopyBuffer 把最新一根的缓冲值拉进自定义数组,否则后续比较逻辑全跑在空数据上。 上面四行分别取布林带的上轨(缓冲号1)、下轨(缓冲号2),以及两条移动均线的主线(缓冲号0)。每个调用都是 from=0、count=1,意味着只拷贝当前柱这一根,省内存也避免错位。 实际验证时,把 bb_h_buffer[0] 与 ma_h_buffer[0] 打进 Print 看是否同步刷新;若某次返回 0 而数组值不变,多半是句柄未就绪或品种不在市场观察列表。外汇与贵金属波动大,这种通道值仅作概率参考,别当成方向保证。
CopyBuffer(bb_handler,class="num">1,class="num">0,class="num">1,bb_h_buffer); CopyBuffer(bb_handler,class="num">2,class="num">0,class="num">1,bb_l_buffer); CopyBuffer(ma_h_handler,class="num">0,class="num">0,class="num">1,ma_h_buffer); CopyBuffer(ma_l_handler,class="num">0,class="num">0,class="num">1,ma_l_buffer); }
把工具请下神坛
前面几节跑通的日线 GBPUSD 模型,本质是用 1533.83 KB 的 notebook 训出一个 1.74 KB 的 ONNX 轻量网络,再由 7.64 KB 的 EA 接进 MT5。它只吃日线、只处理少量行,所以普通笔记本也能跑,不需要你去啃并行计算。 过拟合在这套思路里不是死刑判决,更像是提醒你:模型容量或数据窗口可能还没调到能榨出信号的临界点。把训练集从全程砍到 2016–2020,留 2020–2024 当盲测,是低成本换稳健性的直接办法。 外汇和贵金属是高杠杆高风险品种,任何 AI 模型都只给概率倾向,别把它当圣杯。打开附件里的 GBPUSD_AI.mq5,先在原参数下回测 2020 年后行情,再减半训练窗口对比曲线,比听谁说「模型够用了」都实在。