如何在 MQL5 中集成 ONNX 模型的示例·综合运用
📘

如何在 MQL5 中集成 ONNX 模型的示例·综合运用

第 3/3 篇

◍ 把 ONNX 预测信号接进开平仓逻辑

下面这段把模型输出的分类直接映射成 MT5 的下单动作。预测类别 PRICE_DOWN 对应卖单,PRICE_UP 对应买单,其余情况 signal 保持 WRONG_VALUE 不下单,避免模型模糊时硬做。 静态向量 output_data(3) 承接三个类别的推理得分,用 ArgMax 取最大下标当预测类。归一化时先减均值 m 再除标准差 s,样本来自最近 sample_size 根 K 线的收盘价,这一步若 CopyRates 拉不到数据直接 return(-1),说明行情源断流要先排查。 开仓函数里有个实打实的限制:必须 TerminalInfoInteger(TERMINAL_TRADE_ALLOWED) 为真才允许 PositionOpen,自动交易开关没开就完全不跑。手数用外部参数 InpLots,发单价格按信号方向取 BID 或 ASK,滑点参数填 0。 平仓逻辑更激进一些:持多单遇到 PRICE_DOWN、持空单遇到 PRICE_UP 就先 PositionClose(_Symbol,3) 市价平掉,紧接着调 CheckForOpen() 反手。外汇和贵金属杠杆高,这种模型翻转即反手的做法在震荡市可能连续挨巴掌,概率上更怕假突破。 枚举 EnModels 留了 USE_FIRST_MODEL 和 USE_SECOND_MODEL 两个档,意味着同一 EA 可切换不同 ONNX 权重,实盘前建议在策略测试器里分别跑一遍看哪套在近期品种上回撤更小。

MQL5 / C++
class="kw">static vectorf output_data(class="num">3);                      class=class="str">"cmt">// vector to get result
class=class="str">"cmt">//--- request last bars
   if(!input_data.CopyRates(_Symbol,_Period,COPY_RATES_CLOSE,class="num">1,sample_size))
      class="kw">return(-class="num">1);
class=class="str">"cmt">//--- get series Mean
   class="type">class="kw">float m=input_data.Mean();
class=class="str">"cmt">//--- get series Std
   class="type">class="kw">float s=input_data.Std();
class=class="str">"cmt">//--- normalize prices
   input_data-=m;
   input_data/=s;
class=class="str">"cmt">//--- run the inference
   if(!OnnxRun(handle,ONNX_NO_CONVERSION,input_data,output_data))
      class="kw">return(-class="num">1);
class=class="str">"cmt">//--- evaluate prediction
   class="kw">return(class="type">int(output_data.ArgMax()));
   }
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Check for open position conditions                               |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">void CheckForOpen(class="type">void)
   {
   ENUM_ORDER_TYPE signal=WRONG_VALUE;
class=class="str">"cmt">//--- check signals
   if(ExtPredictedClass==PRICE_DOWN)
      signal=ORDER_TYPE_SELL;      class=class="str">"cmt">// sell condition
   else
    {
      if(ExtPredictedClass==PRICE_UP)
         signal=ORDER_TYPE_BUY;    class=class="str">"cmt">// buy condition
    }
class=class="str">"cmt">//--- open position if possible according to signal
   if(signal!=WRONG_VALUE && TerminalInfoInteger(TERMINAL_TRADE_ALLOWED))
      ExtTrade.PositionOpen(_Symbol,signal,InpLots,
                           SymbolInfoDouble(_Symbol,signal==ORDER_TYPE_SELL ? SYMBOL_BID:SYMBOL_ASK),
                           class="num">0,class="num">0);
   }
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Check for close position conditions                              |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">void CheckForClose(class="type">void)
   {
   class="type">bool bsignal=class="kw">false;
class=class="str">"cmt">//--- position already selected before
   class="type">long type=PositionGetInteger(POSITION_TYPE);
class=class="str">"cmt">//--- check signals
   if(type==POSITION_TYPE_BUY && ExtPredictedClass==PRICE_DOWN)
      bsignal=true;
   if(type==POSITION_TYPE_SELL && ExtPredictedClass==PRICE_UP)
      bsignal=true;
class=class="str">"cmt">//--- close position if possible
   if(bsignal && TerminalInfoInteger(TERMINAL_TRADE_ALLOWED))
    {
      ExtTrade.PositionClose(_Symbol,class="num">3);
      class=class="str">"cmt">//--- open opposite
      CheckForOpen();
    }
   }
enum EnModels
   {
   USE_FIRST_MODEL,      class=class="str">"cmt">// Use first model only
   USE_SECOND_MODEL,    class=class="str">"cmt">// Use second model only

「双模型投票怎么在 MT5 里落地」

EA 里用枚举控制模型参与方式,默认 USE_BOTH_MODELS 表示两个模型都跑;手数通过 InpLots=1.0 写死,实盘前建议按账户净值重算,外汇与贵金属杠杆高,重仓可能直接爆仓。 Predict() 先按开关算各自分类:开双模型或仅第一模型时调 PredictPrice,开双模型或仅第二模型时调 PredictPriceMovement。两个句柄与样本数 SAMPLE_SIZE1/2 来自前段训练产物。 投票逻辑在 switch 里:单模型直接采用;双模型时两分类一致才输出该类,否则 ExtPredictedClass=-1 代表信号冲突。你在 MT5 策略测试器把 InpModels 切到 USE_FIRST_MODEL 对比,可能发现冲突周里单边模型胜率反而更高。

MQL5 / C++
enum EnModels
  {
  USE_FIRST_MODEL,    class=class="str">"cmt">// Use first model
  USE_SECOND_MODEL,   class=class="str">"cmt">// Use second model
  USE_BOTH_MODELS    class=class="str">"cmt">// Use both models
  };
input EnModels InpModels = USE_BOTH_MODELS;   class=class="str">"cmt">// Models using
input class="type">class="kw">double   InpLots   = class="num">1.0;              class=class="str">"cmt">// Lots amount to open position
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Voting classification                                            |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">void Predict(class="type">void)
  {
class=class="str">"cmt">//--- evaluate first model
   if(InpModels==USE_BOTH_MODELS || InpModels==USE_FIRST_MODEL)
      ExtPredictedClass1=PredictPrice(ExtHandle1,SAMPLE_SIZE1);
class=class="str">"cmt">//--- evaluate second model
   if(InpModels==USE_BOTH_MODELS || InpModels==USE_SECOND_MODEL)
      ExtPredictedClass2=PredictPriceMovement(ExtHandle2,SAMPLE_SIZE2);
class=class="str">"cmt">//--- check predictions
   class="kw">switch(InpModels)
     {
      case USE_FIRST_MODEL :
         ExtPredictedClass=ExtPredictedClass1;
         class="kw">break;
      case USE_SECOND_MODEL :
         ExtPredictedClass=ExtPredictedClass2;
         class="kw">break;
      case USE_BOTH_MODELS :
         if(ExtPredictedClass1==ExtPredictedClass2)
            ExtPredictedClass=ExtPredictedClass1;
         else
            ExtPredictedClass=-class="num">1;
     }
  }

多模型调用的硬上限与复现坑

在 MT5 里用 ONNX 做推理,单 EA 同时挂的模型数有硬顶:最多 256 个。前面两模型叠加的示范只是开胃菜,真要上三个以上,就得换面向对象的写法去管生命周期和推理调度,不然内存和调用秩序都会乱。 回测结果对不上,往往不是代码错。有用户用相同日期和设置跑,结果却不同;社区里提到两个常见源:一是交易服务器不是 MetaQuotes-Demo,时区和品种报价节奏会偏;二是模型权重初始化没做固定随机种子,重新训练后权重起点不同,输出自然漂移。外汇和贵金属本身高杠杆、高波动,这类偏差会直接放大成账户曲线的分歧。 下面这段是某次重训 EURUSD D1 模型的终端输出,能直接看到无 GPU 环境下降级到 CPU、以及 50 轮训练后测试集 mae 停在 0.758 附近。想复现的人,先确认自己也是 CPU 版 TF、且用的 MetaQuotes 演示账户,否则数字可能对不上。

MQL5 / C++
D:\MT5 Demo1\MQL5\Experts\article_12433\Python>python ONNX.eurusd.D1.class="num">10.Training.py
class="num">2023-class="num">11-class="num">19 class="num">18:class="num">07:class="num">38.169418: W tensorflow/stream_executor/platform/class="kw">default/dso_loader.cc:class="num">64] Could not load dynamic library &class="macro">#x27;cudart64_110.dll&class="macro">#x27;; dlerror: cudart64_110.dll not found
class="num">2023-class="num">11-class="num">19 class="num">18:class="num">07:class="num">38.169664: I tensorflow/stream_executor/cuda/cudart_stub.cc:class="num">29] Ignore above cudart dlerror if you do not have a GPU set up on your machine.
data path to save onnx model
class="num">100%|███████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████| class="num">5187/class="num">5187 [class="num">00:class="num">00<class="num">00:class="num">00, class="num">6068.93it/s]
class="num">2023-class="num">11-class="num">19 class="num">18:class="num">07:class="num">40.434910: W tensorflow/stream_executor/platform/class="kw">default/dso_loader.cc:class="num">64] Could not load dynamic library &class="macro">#x27;nvcuda.dll&class="macro">#x27;; dlerror: nvcuda.dll not found
class="num">2023-class="num">11-class="num">19 class="num">18:class="num">07:class="num">40.435070: W tensorflow/stream_executor/cuda/cuda_driver.cc:class="num">263] failed call to cuInit: UNKNOWN ERROR(class="num">303)
class="num">2023-class="num">11-class="num">19 class="num">18:class="num">07:class="num">40.437138: I tensorflow/stream_executor/cuda/cuda_diagnostics.cc:class="num">169] retrieving CUDA diagnostic information for host: WIN-SSPXX7BO0B0
class="num">2023-class="num">11-class="num">19 class="num">18:class="num">07:class="num">40.437323: I tensorflow/stream_executor/cuda/cuda_diagnostics.cc:class="num">176] hostname: WIN-SSPXX7BO0B0
class="num">2023-class="num">11-class="num">19 class="num">18:class="num">07:class="num">40.437676: I tensorflow/core/platform/cpu_feature_guard.cc:class="num">193] This TensorFlow binary is optimized with oneAPI Deep Neural Network Library(oneDNN) to use the following CPU instructions in performance-critical operations:  AVX AVX2
To enable them in other operations, rebuild TensorFlow with the appropriate compiler flags.
Epoch class="num">1/class="num">50
class="num">111/class="num">111 - 1s - loss: class="num">1.6160 - mae: class="num">0.9378 - val_loss: class="num">2.7602 - val_mae: class="num">1.3423 - lr: class="num">0.0010 - 1s/epoch - 12ms/step
Epoch class="num">2/class="num">50
class="num">111/class="num">111 - 0s - loss: class="num">1.4932 - mae: class="num">0.8952 - val_loss: class="num">2.4339 - val_mae: class="num">1.2412 - lr: class="num">0.0010 - 287ms/epoch - 3ms/step
...
class="num">111/class="num">111 - 0s - loss: class="num">1.2812 - mae: class="num">0.8145 - val_loss: class="num">1.2598 - val_mae: class="num">0.8142 - lr: class="num">1.0000 e-class="num">06 - 366ms/epoch - 3ms/step
Epoch class="num">50/class="num">50
class="num">111/class="num">111 - 0s - loss: class="num">1.3030 - mae: class="num">0.8203 - val_loss: class="num">1.2604 - val_mae: class="num">0.8143 - lr: class="num">1.0000 e-class="num">06 - 365ms/epoch - 3ms/step
class="num">33/class="num">33 [==============================] - 0s 1ms/step - loss: class="num">1.1542 - mae: class="num">0.7584
test_loss=class="num">1.154
test_mae=class="num">0.758

◍ 把这条线请下神坛

上面这段日志来自一次 EURUSD D1 周期的 TensorFlow 训练跑批,模型最终落地为 model.eurusd.D1.10.onnx 与 model.eurusd.D1.63.onnx 两个文件。训练到第 300 轮时,训练集 accuracy 停在 0.6770,而验证集 val_accuracy 只有 0.4789,测试集 test_accuracy 进一步滑到 0.479。 差距说明一个事实:在日线级别用这类浅层网络做方向分类,过拟合倾向很明显,验证与测试表现接近随机猜。外汇和贵金属本身高波动、高杠杆,直接拿 0.48 命中率的信号去开仓,亏损概率天然占优。 小布盯盘接这条线,目的不是神化 AI 预测,而是把日志、onnx 模型和 MT5 实盘价差摆在一起,让你自己跑一遍看差距。能验证、能证伪,才算工具,不是神谕。

MQL5 / C++
class="num">2023-class="num">11-class="num">19 class="num">18:class="num">07:class="num">57.480814: I tensorflow/core/grappler/devices.cc:class="num">66] Number of eligible GPUs(core count >= class="num">8, compute capability >= class="num">0.0): class="num">0
class="num">2023-class="num">11-class="num">19 class="num">18:class="num">07:class="num">57.481315: I tensorflow/core/grappler/clusters/single_machine.cc:class="num">358] Starting new session
class="num">2023-class="num">11-class="num">19 class="num">18:class="num">07:class="num">57.560110: I tensorflow/core/grappler/devices.cc:class="num">66] Number of eligible GPUs(core count >= class="num">8, compute capability >= class="num">0.0): class="num">0
class="num">2023-class="num">11-class="num">19 class="num">18:class="num">07:class="num">57.560380: I tensorflow/core/grappler/clusters/single_machine.cc:class="num">358] Starting new session
class="num">2023-class="num">11-class="num">19 class="num">18:class="num">07:class="num">57.611678: I tensorflow/compiler/mlir/mlir_graph_optimization_pass.cc:class="num">354] MLIR V1 optimization pass is not enabled
saved model to model.eurusd.D1.class="num">10.onnx
class="num">24/class="num">24 - class="num">0 s - loss: class="num">0.6618 - accuracy: class="num">0.6736 - val_loss: class="num">0.8993 - val_accuracy: class="num">0.4759 - lr: class="num">4.1746 e-class="num">05 - class="num">37 ms/epoch - class="num">2 ms/step
Epoch class="num">300/class="num">300
class="num">24/class="num">24 - class="num">0 s - loss: class="num">0.6531 - accuracy: class="num">0.6770 - val_loss: class="num">0.8997 - val_accuracy: class="num">0.4789 - lr: class="num">4.1746 e-class="num">05 - class="num">39 ms/epoch - class="num">2 ms/step
class="num">11/class="num">11 [==============================] - class="num">0 s class="num">682 us/step - loss: class="num">0.8997 - accuracy: class="num">0.4789
test_loss=class="num">0.900
test_accuracy=class="num">0.479
class="num">2023-class="num">11-class="num">19 class="num">18:class="num">07:class="num">19.838160: I tensorflow/core/grappler/devices.cc:class="num">66] Number of eligible GPUs(core count >= class="num">8, compute capability >= class="num">0.0): class="num">0
class="num">2023-class="num">11-class="num">19 class="num">18:class="num">07:class="num">19.838516: I tensorflow/core/grappler/clusters/single_machine.cc:class="num">358] Starting new session
class="num">2023-class="num">11-class="num">19 class="num">18:class="num">07:class="num">19.872285: I tensorflow/core/grappler/devices.cc:class="num">66] Number of eligible GPUs(core count >= class="num">8, compute capability >= class="num">0.0): class="num">0
class="num">2023-class="num">11-class="num">19 class="num">18:class="num">07:class="num">19.872584: I tensorflow/core/grappler/clusters/single_machine.cc:class="num">358] Starting new session
saved model to model.eurusd.D1.class="num">63.onnx

常见问题

在 OnTick 里调用 model.Run 取预测值,用返回值替换原指标条件;平仓侧同样用第二个模型输出做反向过滤即可。
设定主模型给方向、辅模型做确认,只有两者同向才发单;参数里留投票权重阈值,方便你按品种微调。
可以,小布盯盘的 AIGC 已内置多模型信号比对,打开对应品种页就能直接看投票结果和冲突提示。
模型数有硬上限且占用显存/内存,超了就复现不了;建议只留两个关键模型,其余用离线预计算替代。
黄金波动快、滑点大,模型信号偏慢时容易接在影线上;建议只做日线级倾向判断,别拿它当秒级入场依据。