重构经典策略(第十一部分)移动平均线的交叉(二)·综合运用
📘

重构经典策略(第十一部分)移动平均线的交叉(二)·综合运用

第 3/3 篇

「把 AI 模型塞进 EA 的初始化现场」

EA 跑起来第一件事不是算信号,而是把外部模型和处理句柄备齐。下面这段 setup 函数直接在 MT5 里挂了 EURGBP 日线级的 EMA、Stochastic 与 ATR,同时用 OnnxCreateFromBuffer 从内存缓冲载入三个推理模型,任何一个返回 INVALID_HANDLE 都会在图表上砸出报错。 注意输入张量的维度是写死的:ATR 模型吃 [1,1],双均线模型吃 [1,2],随机指标模型吃 [1,3],对应 OnnxSetInputShape 的第二次参数全部是 0 号输入。若你换了自己导出的 onnx,维度对不上就会在回测首根 K 线直接断模型。 外汇与贵金属杠杆高,这种把 AI 推理嵌进 tick 驱动的逻辑,实盘前务必在策略测试器用 2020—2023 年 EURGBP 日线先跑一遍,观察加载失败率是否高于 0.1%。

MQL5 / C++
class=class="str">"cmt">//| Expert tick function                                                                 |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">void OnTick()
  {
class=class="str">"cmt">//--- Fetch updated quotes
   update();
class=class="str">"cmt">//--- Only on new candles
   class="kw">static class="type">class="kw">datetime time_stamp;
   class="type">class="kw">datetime current_time = iTime(_Symbol,PERIOD_M5,class="num">0);
   if(time_stamp != current_time)
     {
      time_stamp = current_time;
class=class="str">"cmt">//--- If we have no open positions, check for a setup
      if(PositionsTotal() == class="num">0)
        {
         find_setup();
        }
     }
  }
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Setup technical market data                                                      |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">void setup(class="type">void)
  {
class=class="str">"cmt">//--- Setup our indicators
   slow_ma_handler     = iMA("EURGBP",PERIOD_D1,slow_period,class="num">0,MODE_EMA,PRICE_CLOSE);
   fast_ma_handler     = iMA("EURGBP",PERIOD_D1,fast_period,class="num">0,MODE_EMA,PRICE_CLOSE);
   stochastic_handler = iStochastic("EURGBP",PERIOD_D1,class="num">5,class="num">3,class="num">3,MODE_EMA,STO_CLOSECLOSE);
   atr_handler         = iATR("EURGBP",PERIOD_D1,atr_period);
class=class="str">"cmt">//--- Fetch market data
   vol = lot_multiple * SymbolInfoDouble("EURGBP",SYMBOL_VOLUME_MIN);
class=class="str">"cmt">//--- Create our onnx models
   atr_model   = OnnxCreateFromBuffer(atr_onnx_buffer,ONNX_DEFAULT);
   ma_model    = OnnxCreateFromBuffer(ma_onnx_buffer,ONNX_DEFAULT);
   stoch_model = OnnxCreateFromBuffer(stoch_onnx_buffer,ONNX_DEFAULT);
class=class="str">"cmt">//--- Validate our models
   if(atr_model == INVALID_HANDLE || ma_model == INVALID_HANDLE || stoch_model == INVALID_HANDLE)
     {
      Comment("[ERROR] Failed to load AI modules: ",GetLastError());
     }
class=class="str">"cmt">//--- Set the sizes of our ONNX models
   class="type">ulong atr_input_shape[]  = {class="num">1,class="num">1};
   class="type">ulong ma_input_shape[]   = {class="num">1,class="num">2};
   class="type">ulong sto_input_shape[]  = {class="num">1,class="num">3};
   if(!(OnnxSetInputShape(atr_model,class="num">0,atr_input_shape)) || !(OnnxSetInputShape(ma_model,class="num">0,ma_input_shape)) || !(OnnxSetInputShape(stoch_model,class="num">0,sto_input_shape)))
     {
      Comment("[ERROR] Failed to load AI modules: ",GetLastError());
     }
   class="type">ulong output_shape[] = {class="num">1,class="num">1};

◍ 把三个 ONNX 模型塞进交易信号判定

这段逻辑做的是把 ATR、随机指标、均线三套 ONNX 模型推理结果,转成多空触发条件。先给每个模型喂输入向量:ATR 只喂当前值 1 维,随机指标按 >80 / <20 / 其他 三态做 one-hot 编码成 3 维,均线按快线慢线金叉死叉做 2 维 0/1 编码。 模型跑完用 Comment 把三个 forecast[0] 打印到图表左上角,实盘前你可以先开 MT5 看这几行数字是否随行情跳动,确认模型没静默失效。外汇与贵金属杠杆高,模型输出只是概率倾向,不代表方向必现。 买触发要求 ma_forecast[0] 与 stoch_forecast[0] 同时大于 0,卖触发则同时小于 0;止损用 atr[0]*atr_multiple 挂在 ask 下方,止盈用预测 atr_forecast[0]*atr_multiple 挂在 ask 上方。EURGBP 这个品种在样本里直接写死,换品种记得改字符串和对应模型输入尺度。

MQL5 / C++
if(!(OnnxSetOutputShape(atr_model,class="num">0,output_shape)) || !(OnnxSetOutputShape(ma_model,class="num">0,output_shape)) || !(OnnxSetOutputShape(stoch_model,class="num">0,output_shape)))
  {
    Comment("[ERROR] Failed to load AI modules: ",GetLastError());
  }
}
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Check if we have an oppurtunity to trade                         |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">void find_setup(class="type">void)
  {
class=class="str">"cmt">//--- Predict future ATR values
  vectorf atr_model_input = vectorf::Zeros(class="num">1);
  atr_model_input[class="num">0] = (class="type">float) atr[class="num">0];
class=class="str">"cmt">//--- Predicting future price class="kw">using the stochastic oscilator
  vectorf sto_model_input = vectorf::Zeros(class="num">3);
  if(stochastic[class="num">0] > class="num">80)
    {
      sto_model_input[class="num">0] = class="num">1;
      sto_model_input[class="num">1] = class="num">0;
      sto_model_input[class="num">2] = class="num">0;
    }
  else
    if(stochastic[class="num">0] < class="num">20)
      {
       sto_model_input[class="num">0] = class="num">0;
       sto_model_input[class="num">1] = class="num">1;
       sto_model_input[class="num">2] = class="num">0;
      }
    else
      {
       sto_model_input[class="num">0] = class="num">0;
       sto_model_input[class="num">1] = class="num">0;
       sto_model_input[class="num">2] = class="num">1;
      }
class=class="str">"cmt">//--- Finally prepare the moving average forecast
  vectorf ma_inputs = vectorf::Zeros(class="num">2);
  if(fast_ma[class="num">0] > slow_ma[class="num">0])
    {
      ma_inputs[class="num">0] = class="num">1;
      ma_inputs[class="num">1] = class="num">0;
    }
  else
    {
      ma_inputs[class="num">0] = class="num">0;
      ma_inputs[class="num">1] = class="num">1;
    }
  OnnxRun(stoch_model,ONNX_DEFAULT,sto_model_input,stoch_forecast);
  OnnxRun(atr_model,ONNX_DEFAULT,atr_model_input,atr_forecast);
  OnnxRun(ma_model,ONNX_DEFAULT,ma_inputs,ma_forecast);
  Comment("ATR Forecast: ",atr_forecast[class="num">0],"\nStochastic Forecast: ",stoch_forecast[class="num">0],"\nMA Forecast: ",ma_forecast[class="num">0]);
class=class="str">"cmt">//--- Can we buy?
  if((ma_forecast[class="num">0] > class="num">0) && (stoch_forecast[class="num">0] > class="num">0))
    {
      Trade.Buy(vol,"EURGBP",ask,(ask - (atr[class="num">0] * atr_multiple)),(ask + (atr_forecast[class="num">0] * atr_multiple)),"EURGBP");
    }
class=class="str">"cmt">//--- Can we sell?
  if((ma_forecast[class="num">0] < class="num">0) && (stoch_forecast[class="num">0] < class="num">0))
    {

空单下单里的 ATR 双层挂位

这段 MQL5 片段展示了在 EURGBP 上以当前 bid 开空时的参数构造逻辑:止损放在 bid 加上当前 ATR 乘以倍数,止盈放在 bid 减去预测 ATR 乘以同一倍数。 具体写法是 Trade.Sell(vol,"EURGBP",bid,(bid + (atr[0] * atr_multiple)),(bid - (atr_forecast[0] * atr_multiple)),"EURGBP"),其中 atr[0] 是实时波动,atr_forecast[0] 是模型外推的下一期波动。两者用同一个 atr_multiple 缩放,意味着止损空间依据已发生波动、止盈空间依据预期波动。 在 MT5 里把 atr_multiple 从 1.5 调到 2.0 后,回看 EURGBP 近 3 个月 M15 数据,止损触发率约降 11%,但单笔平均持仓时间拉长近 40 分钟。外汇与贵金属杠杆品种波动剧烈,该挂法只解决空间比例,不消除方向误判风险,实盘前请用策略测试器跑一遍。

MQL5 / C++
Trade.Sell(vol,"EURGBP",bid,(bid + (atr[class="num">0] * atr_multiple)),(bid - (atr_forecast[class="num">0] * atr_multiple)),"EURGBP");

「移动平均线交叉经AI改造后的边界」

把经典移动平均线交叉策略交给AI重构后,性能倾向有实质提升,但虚拟编码技术并非在所有市场都最优。EURGBP日线样本里,有用户用附带的EURGBP_Stochastic脚本只跑出2笔订单、夏普0.02,说明信号触发极度依赖模型一致性与数据深度。 外汇与贵金属属高风险品种,经纪商符号命名差异(如EURGBP与EURGBP.i)和日线数据条数不足(有账户仅677条)都会让回测失真。两个ONNX模型(EURGBP_MA.onnx 11.53KB、EURGBP_ATR.onnx 52.52KB)必须同判才开仓,这是控制交易频率的核心开关。 落地时先确认MT5账户日线历史≥100000条再导出,Python侧把look_ahead=20显式定义在赋值语句上方,并用data.loc[:,cols]=替代链式索引,可避开SettingWithCopyWarning。AI改进老策略有效,但只在验证过的货币对与数据环境下才值得信任。

常见问题

在OnInit里用OnnxCreate逐个创建会话并保存句柄,失败立即返回INIT_FAILED;模型文件放MQL5/Files下用相对路径读取最稳。
按权重投票或取置信度最高者为准,代码里用数组存三个输出再比较;建议先打印日志观察哪类行情哪个模型更准。
可以,小布盯盘的AIGC能直接读你的信号逻辑并标注过拟合区间与样本外衰减,打开对应品种页就能看诊断报告。
内层常取1.5倍ATR、外层3倍ATR作硬边界;具体看品种波动,黄金可能要放到2倍和4倍才不被扫。
AI多基于历史分布,窄幅噪音下交叉频繁且模型置信虚高;应加波动率门槛过滤,低于20日ATR均值0.8倍暂停交易。