基于MQL5和Python的自优化EA(第六部分):利用深度双重下降算法·综合运用
📘

基于MQL5和Python的自优化EA(第六部分):利用深度双重下降算法·综合运用

第 3/3 篇

「日线布林与双均线喂给 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 信号延迟天然偏大,仅适合做趋势过滤而非高频触发。

MQL5 / C++
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 策略测试器用历史数据验证信号频率与回撤。

MQL5 / C++
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 数据回测,观察最大回撤是否超出你的承受线。

MQL5 / C++
里的 update 函数只露了头:CopyBuffer 从布林指标句柄 bb_handler 的第 0 线(中轨)取 1 根数据进 bb_m_buffer,这是后面所有中轨判断的源头,缺了它上面那些 > 或 < 比较全会变成空谈。

MQL5 / C++
      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 而数组值不变,多半是句柄未就绪或品种不在市场观察列表。外汇与贵金属波动大,这种通道值仅作概率参考,别当成方向保证。

MQL5 / C++
  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 年后行情,再减半训练窗口对比曲线,比听谁说「模型够用了」都实在。

常见问题

先把iBands和双均线缓冲区用CopyBuffer读进数组,再按时间轴对齐拼接成特征行,最后转成模型需要的张量格式送进ONNX。
把建仓置信度和出场阈值放在同一状态机里,模型信号触发建仓后,只有反向置信超过出场线才平,避免重复开平仓。
小布可替你加载品种页并跑通指标缓冲到数组的四条调用,把重复劳动交给它,你只调特征和阈值。
常错在句柄未建、计数传0、起止错位、未处理空值;先校验句柄再按实际可用柱数拷贝即可。
模型只在历史分布内有效,实盘分布漂移就会失效;外汇贵金属高风险,应作为参考而非自动全托。