数据科学和机器学习(第 27 部分):MetaTrader 5 中训练卷积神经网络(CNN)交易机器人  值得吗?·综合运用
📘

数据科学和机器学习(第 27 部分):MetaTrader 5 中训练卷积神经网络(CNN)交易机器人 值得吗?·综合运用

第 3/3 篇

◍ 把 Keras 模型塞进 MT5 前先看这组分类指标

上面那张混淆矩阵汇总里,样本总量 198 条,macro avg 的精确率 0.55、召回率 0.53、F1 0.50,weighted avg 的 F1 也只有 0.52。单类里标号为 1 的那一组精确率 0.58、召回 0.83、F1 0.68,支撑样本 110 条,说明模型对这一类抓得比较全,但误报偏多。 外汇与贵金属日线模型本身高风险,0.5 出头的 F1 意味着信号有一半左右可能是噪声,直接跟单不可取,先当过滤器用更稳。 要把训练好的 CNN 转成 MT5 能加载的 ONNX,下面这段 Python 是关键一步。它把 Keras 模型按指定输入签名导出,并把标准化器的均值与缩放量存成二进制,供 EA 端还原特征。 import tf2onnx onnx_file_name = "cnn.EURUSD.D1.onnx" spec = (tf.TensorSpec((None, window_size, X_train.shape[2]), tf.float16, name="input"),) model.output_names = ['outputs'] onnx_model, _ = tf2onnx.convert.from_keras(model, input_signature=spec, opset=13) # Save the ONNX model to a file with open(onnx_file_name, "wb") as f: f.write(onnx_model.SerializeToString()) # Save the mean and scale parameters to binary files scaler.mean_.tofile(f"{onnx_file_name.replace('.onnx','')}.standard_scaler_mean.bin") scaler.scale_.tofile(f"{onnx_file_name.replace('.onnx','')}.standard_scaler_scale.bin") 开 MT5 前先确认你的 window_size 和 X_train 特征维度和导出时一致,否则 ONNX 推理会直接报错。

MQL5 / C++
class="kw">import tf2onnx
onnx_file_name = "cnn.EURUSD.D1.onnx"
spec = (tf.TensorSpec((None, window_size, X_train.shape[class="num">2]), tf.float16, name="class="kw">input"),)
model.output_names = [&class="macro">#x27;outputs&class="macro">#x27;]
onnx_model, _ = tf2onnx.convert.from_keras(model, input_signature=spec, opset=class="num">13)
# Save the ONNX model to a file
with open(onnx_file_name, "wb") as f:
    f.write(onnx_model.SerializeToString())


# Save the mean and scale parameters to binary files
scaler.mean_.tofile(f"{onnx_file_name.replace(&class="macro">#x27;.onnx&class="macro">#x27;,&class="macro">#x27;&class="macro">#x27;)}.standard_scaler_mean.bin")
scaler.scale_.tofile(f"{onnx_file_name.replace(&class="macro">#x27;.onnx&class="macro">#x27;,&class="macro">#x27;&class="macro">#x27;)}.standard_scaler_scale.bin")

把 CNN 模型塞进 EA 并跑通信号

在 MT5 里跑卷积神经网络,第一步是把训练好的 ONNX 模型和标准化参数作为资源编译进 EA。模型用的是 EURUSD 日线训练产出,定标均值和标准差分别存成二进制,加载时须和训练侧完全一致,否则推理会偏。 初始化阶段同时拉起 ONNX 会话与 StandardizationScaler,前者靠 cnn.Init(onnx_model) 完成,后者用二进制里的 mean / stddev 重建。窗口大小 cnn_data_window 设为 10,含义是从前一根收盘柱往前数 10 根日线,和 python 训练时的取值窗口必须相同。 自变量抓取函数按 OHLC 四个序列从日线拷贝,起始偏移 start_bar=1 表示跳过当前未收盘柱。策略本身极简:模型给买入信号就开无止损止盈的多单,反向信号出现平多并反向;卖出信号对称处理。 实盘回测区间 2014.01.01–2024.05.27,H4 触发、日线取数,EURUSD 十年样本下模型预测准确率为 58%,EA 净盈利 503 美元。外汇与贵金属属高风险品种,该结果仅说明历史样本倾向,不代表未来收益。 别把 58% 当圣杯 准确率刚过随机线一点,无止损结构在极端跳空下可能快速回吐,上 MT5 用策略测试器自己跑一遍 EURUSD 十年区间再决定参数。

MQL5 / C++
class="macro">#resource "\Files\cnn.EURUSD.D1.onnx" as class="type">uchar onnx_model[]
class="macro">#resource "\Files\cnn.EURUSD.D1.standard_scaler_scale.bin" as class="type">class="kw">double scaler_stddev[]
class="macro">#resource "\Files\cnn.EURUSD.D1.standard_scaler_mean.bin" as class="type">class="kw">double scaler_mean[]
class="macro">#include <MALE5\Convolutioal Neural Networks(CNNs)\Convnet.mqh>
class="macro">#include <MALE5\preprocessing.mqh>
CConvNet cnn;
StandardizationScaler scaler;
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//|                                                                  |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="kw">input group "cnn";
class="kw">input class="type">uint cnn_data_window = class="num">10;
class=class="str">"cmt">//this value must be the same as the one used during training in a python script
vector classes_in_y = {class="num">0,class="num">1}; class=class="str">"cmt">//we have to assign the classes manually | it is essential that their order is preserved as they can be seen in python code, HINT: They are usually in ascending order
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Expert initialization function                                     |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">int OnInit()
  {
class=class="str">"cmt">//---
   if (!cnn.Init(onnx_model)) class=class="str">"cmt">//Initialize the ONNX model
     class="kw">return INIT_FAILED;
class=class="str">"cmt">//--- Initializing the scaler with values loaded from binary files 
   scaler = new StandardizationScaler(scaler_mean, scaler_stddev); class=class="str">"cmt">//load the scaler
     class="kw">return(INIT_SUCCEEDED);
  }
class="kw">input group "cnn";
class="kw">input class="type">uint cnn_data_window = class="num">10;
class=class="str">"cmt">//this value must be the same as the one used during training in a python script
class="kw">input ENUM_TIMEFRAMES timeframe = PERIOD_D1;
class="kw">input class="type">int magic_number = class="num">1945;
class="kw">input class="type">int slippage = class="num">50;
matrix GetXVars(class="type">int bars, class="type">int start_bar=class="num">1)
{
   vector open(bars),
         high(bars),
         low(bars),
         close(bars);
class=class="str">"cmt">//--- Getting OHLC values
   open.CopyRates(Symbol(), timeframe, COPY_RATES_OPEN, start_bar, bars);
   high.CopyRates(Symbol(), timeframe, COPY_RATES_HIGH, start_bar, bars);
   low.CopyRates(Symbol(), timeframe, COPY_RATES_LOW, start_bar, bars);
   close.CopyRates(Symbol(), timeframe, COPY_RATES_CLOSE, start_bar, bars);

「把CNN信号接进MT5下单流」

这段逻辑把卷积模型输出的二分类信号直接喂给实盘下单函数,核心在 OnTick 里只在 NewBar() 成立时跑一次,避免一根蜡烛内反复开仓。特征矩阵固定取 4 列(open/high/low/close),行数由 cnn_data_window 控制,默认回看 10 根。 数据先过 scaler.transform 做标准化,再丢给 cnn.predict_bin 拿 signal(1 多 / 0 空)。Comment 会把信号打印在图表左上角,肉眼盯盘时能立刻核对模型倾向。 signal==1 时若没有买单就市价 Buy,同时 ClosePosition 平掉卖单;signal==0 对称处理。外汇与贵金属杠杆高,这类信号只是概率倾向,实盘前务必在策略测试器用历史数据验证信号分布与滑点表现。 下面逐行拆关键片段: matrix data(bars, 4); // 建 bars 行 4 列矩阵,4 列对应 OHLC,列数写死 数据.Col(open,0); // 第0列塞开盘价 数据.Col(high,1); // 第1列塞最高价 数据.Col(low,2); // 第2列塞最低价 数据.Col(close,3); // 第3列塞收盘价 return data; // 返回特征矩阵给调用方 if(NewBar()) // 新蜡烛才触发,降低刷单频率 matrix input_data_matrix = GetXVars(cnn_data_window); // 取默认10根窗口特征 input_data_matrix = scaler.transform(input_data_matrix); // 标准缩放,匹配训练分布 int signal = cnn.predict_bin(input_data_matrix, classes_in_y); // 得二分类信号 if(signal==1) // 模型偏多 if(!PosExists(POSITION_TYPE_BUY)) // 无多单才开 m_trade.Buy(lotsize, Symbol(), ticks.ask, 0, 0); // 市价多,无止损止盈 ClosePosition(POSITION_TYPE_SELL); // 平空 else if(signal==0) // 模型偏空 m_trade.Sell(lotsize, Symbol(), ticks.bid, 0, 0); // 市价空 ClosePosition(POSITION_TYPE_BUY); // 平多

MQL5 / C++
  matrix data(bars, class="num">4); class=class="str">"cmt">//we have class="num">10 inputs from cnn | this value is fixed

class=class="str">"cmt">//--- adding the features into a data matrix

  data.Col(open, class="num">0);
  data.Col(high, class="num">1);
  data.Col(low, class="num">2);
  data.Col(close, class="num">3);

  class="kw">return data;
}
class="type">void OnTick()
  {
class=class="str">"cmt">//---

  if (NewBar()) class=class="str">"cmt">//Trade at the opening of a new candle
    {
      matrix input_data_matrix = GetXVars(cnn_data_window); class=class="str">"cmt">//get data for the past class="num">10 days(class="kw">default)
      input_data_matrix = scaler.transform(input_data_matrix); class=class="str">"cmt">//applying StandardSCaler to the class="kw">input data
      
      class="type">int signal = cnn.predict_bin(input_data_matrix, classes_in_y); class=class="str">"cmt">//getting trade signal from the RNN model

      Comment("Signal==",signal);

    class=class="str">"cmt">//---

      class="type">MqlTick ticks;
      SymbolInfoTick(Symbol(), ticks);
      
      if (signal==class="num">1) class=class="str">"cmt">//if the signal is bullish
       {
        if (!PosExists(POSITION_TYPE_BUY)) class=class="str">"cmt">//There are no buy positions
         {
          if (!m_trade.Buy(lotsize, Symbol(), ticks.ask, class="num">0, class="num">0)) class=class="str">"cmt">//Open a buy trade
            printf("Failed to open a buy position err=%d",GetLastError());
          ClosePosition(POSITION_TYPE_SELL); class=class="str">"cmt">//close opposite trade
         }
       }
      else if (signal==class="num">0) class=class="str">"cmt">//Bearish signal
        {
        if (!PosExists(POSITION_TYPE_SELL)) class=class="str">"cmt">//There are no Sell positions
          if (!m_trade.Sell(lotsize, Symbol(), ticks.bid, class="num">0, class="num">0)) class=class="str">"cmt">//open a sell trade
            printf("Failed to open a sell position err=%d",GetLastError());
          
          ClosePosition(POSITION_TYPE_BUY);
        }
      else class=class="str">"cmt">//There was an error
        class="kw">return;
    }
  }

◍ 别急着下结论

CNN 本是为图像和视频设计的,但喂进 EURUSD 的 D1 表格数据(仅 OHLC 四个自变量)后,仍能在 MT5 策略测试器里给出可用的形态识别与方向预测。从已发布的 ConvNet EA 回测看,这种轻量结构在少变量条件下,表现大概率优于线性回归、SVM、朴素贝叶斯等传统表格模型。 附件里的 cnn.EURUSD.D1.onnx 配合两个 standard_scaler 二进制文件,可直接由 ConvNet.mqh 在 EA 内加载;preprocessing.mqh 负责标准化,python 笔记簿则复现了训练全流程。外汇与贵金属杠杆高、滑点跳空频繁,任何模型信号都只是概率倾斜,实盘前务必用策略测试器跑一遍自己的品种与周期。 模型文件开源在作者 GitHub,讨论也集中在那;想验证就下载 ZIP,把 ONNX 丢进 MT5 测一测,比空谈架构更有用。

常见问题

重点看准确率、精确率、召回率和 F1 分数,以及混淆矩阵里的假信号比例;若验证集上假突破识别率低于六成,塞进实盘前建议先降采样重训。
把模型推理结果写成中间信号文件或由 DLL 返回整数信号,EA 主循环只做信号读取与订单判定,避免每 tick 重跑模型;用异步调用隔离推理延迟。
可以。小布能按品种页自动汇总信号触发频次、命中率和滑点损耗,把重复核对交给它,你只需要在偏离阈值时介入看盘。
多半是训练数据含未来函数或标签泄漏,以及实盘价差/停牌使输入分布偏移;用样本外滚动验证和在线归一化能暴露这类问题。
若你只有几千根 K 线且不懂调参,CNN 容易过拟合;先用价格行为+传统滤波,等样本和算力够再考虑模型化,别急着神化这条线。