数据科学和机器学习(第 27 部分):MetaTrader 5 中训练卷积神经网络(CNN)交易机器人 值得吗?·综合运用
◍ 把 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 推理会直接报错。
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 十年区间再决定参数。
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); // 平多
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 测一测,比空谈架构更有用。