Scikit-Learn 库中的分类模型及其导出到 ONNX·综合运用
- CNB、BNB 与 MLP 三类分类器在 MT5 里的落地表现
- LDA 与直方图梯度提升在 MT5 侧的落地表现
- 从贝叶斯到极端随机树:三类分类器在 MT5 里的落地差异
- ExtraTrees 与随机森林的分裂差异
- 基准分类器在 ONNX 转换上的坑
- 高斯过程分类器转 ONNX 的硬伤与标签传播思路
- 半监督标签传播在 MT5 外的转换坑与 LabelSpreading 正则化差异
- 半监督标签扩散的收敛坑与质心法边界
- 近质心分类转 ONNX 的坑与 QDA 的取舍
- QDA 导 ONNX 踩到的无效图坑
- 把鸢尾花样本塞进 MT5 结构体
- 把鸢尾花样本塞进MT5数组的写法
- setosa 样本的后半段灌入方式
- 鸢尾花样本的第44到69号注入
- 鸢尾花第三类样本如何灌入训练集
- 鸢尾花样本的后段录入细节
- virginica 样本的尾部录入
- 把鸢尾花样本灌进分类器并导出 ONNX
- 把 ONNX 张量结构摸透再喂数据
- 用鸢尾花全集跑通 ONNX 模型准确率
- 一次喂三样本和十样本的差异
- 用十组样本压测分类模型的命中率
- 在 MT5 里跑通 ONNX 分类器的实测落差
- 把训练好的分类模型塞进 MT5 的桥接动作
- 用 ONNX 模型跑通鸢尾花批量推理
- 单样本与批量推理的准确率核对写法
- 十样本批测里的错判追踪
- 在 MT5 里跑通 ONNX 模型并读出准确率
- LinearSVC 在 EURUSD H1 上的错分样本与 NuSVC 导出链路
- 把 Python 端的 ONNX 结构搬进 MT5 实测
- 用 IRIS 全集跑一遍 ONNX 分类准确率
- 用鸢尾花数据验证模型批量推理
- 用鸢尾花样本压一遍模型准确率
- 在 MT5 里加载 NuSVC 模型并核对样本精度
- 把半径邻居分类器导成 ONNX 再回灌验证
- 用 ONNX 模型跑 IRIS 样本的分类接口
- 用鸢尾花样本验模型命中率
- 用鸢尾花样本验证分类模型的批量命中率
- 逐样本核对模型判错的特征向量
- Radius邻居分类器在EURUSD_H1上的错分样本与精度
- 把鸢尾花 Ridge 分类器塞进 MT5 的 ONNX 管线
- 用 IRIS 全样本校验 ONNX 模型输出
- 批量推理下的分类准确率校验
- 用鸢尾花样本给模型跑一批准确率
- 在 MT5 里跑通 ONNX 模型的启动逻辑
- Ridge 分类器在 EURUSD H1 上的错分样本
- Ridge 分类器在 EURUSD H1 上的错分样本与导出代码
- 把训练好的模型塞进 MT5 前先跑通 ONNX
- 用 ONNX 模型批量判别 IRIS 样本类别
- 批量推理与逐样本校验的写法
- 十样本批量验证的错判追踪
- 把分类错例和准确率算到控制台
- RidgeClassifierCV 在 EURUSD H1 上的连续误判
- Ridge 分类器在 EURUSD H1 上的错分样本与精度断层
- 把随机森林导出成 ONNX 再回灌验证
- 在 MT5 里跑通 ONNX 鸢尾花推理的样本批处理
- 从输出数组里挑最大概率的分类
- 批量推理与逐样本校验的写法
- 十样本批次的误判追踪写法
- 在 MT5 里跑通 ONNX 模型的验收回路
- 把梯度提升模型塞进 ONNX 再读回 MT5
- 在 MT5 里跑通 ONNX 分类模型的样本测试
- 从模型输出里挑最大概率类别
- 用鸢尾花样本给模型做全量体检
- 用鸢尾花样本压模型准确率
- 把 ONNX 模型跑成可验证的准确率
- 把 AdaBoost 导成 ONNX 做跨平台验证
- 在 MT5 里跑通 ONNX 鸢尾花分类的推理封装
- 从模型输出里捞最大概率类别
- 用鸢尾花样本验证模型命中率
- 用鸢尾花样本压测分类模型
- 把 ONNX 模型的准确率跑出来
- AdaBoost 在 EURUSD H1 上的错分样本与精度表现
- 把袋装模型塞进MT5的ONNX通道
- 批量推理里挑最大概率分类
- 用鸢尾花集验证模型输出的类别判定
- 批量推理的命中率怎么算
- 用鸢尾花样本给模型跑一次准确率自检
- 把鸢尾分类模型塞进 MT5 跑通全流程
- 把 KNN 模型落地成 ONNX 再喂给 MT5
- 用 ONNX 跑 Iris 批量样本并取最大概率分类
- 从输出张量里抠出最大概率类别
- 批量推理下的模型准确率核验
- 十样本批量验证的落地写法
- 把 ONNX 模型跑起来看判别误差
- KNN回测里那几个错分样本与零精度陷阱
- 把鸢尾花决策树塞进 MT5 跑 ONNX 推理
- 从 ONNX 输出里挑最大概率分类
- 用 IRIS 全样本给模型算准确率
- 批量推理下的准确率核对
- 用 10 条样本压测分类模型的命中率
- MT5里加载决策树模型跑Iris全样本
- 把 sklearn 逻辑回归导成 ONNX 再喂给 MT5
- 批量推理时怎么从 ONNX 捞回分类标签
- 用 IRIS 全样本压一遍 ONNX 模型
- 用鸢尾花样本压一波模型批量推理
- 用十组样本压测分类模型
- 从缓冲区加载 ONNX 模型做全样本与批量验证
- 用鸢尾花数据集核对 ONNX 导出的一致性
- 把鸢尾花分类模型塞进 MT5 跑推理
- 从模型输出里捞最大概率分类
- 跑通鸢尾花样本看模型命中率
- 拿鸢尾花样本给模型做批量验证
- 用鸢尾花数据校验 ONNX 分类模型准确率
- 逻辑回归分类器在EURUSD_H1上的误判样本与精度
- 把 PA 分类器塞进 MT5 前先看张量和精度
- 从 ONNX 输出映射里捞最大概率分类
- 逐样本跑完 IRIS 全集算准确率
- 批量推理的准确率怎么跑出来
- 用鸢尾花样本压测分类模型准确率
- 被动感知机在EURUSD-H1上的逐样本验证与释放
- 把感知机塞进 ONNX 再喂回 MT5 的链路
- 在 MT5 里跑通 ONNX 鸢尾花分类的批次推理
- 从输出数组里捞最大概率分类
- 用鸢尾花样本压测模型准确率
- 批量验证分类模型的命中率
- 逐样本核对与准确率落点
- 感知机在 EURUSD H1 上的误判集中区
- 感知机在 EURUSD H1 上的连续误判带
- 感知机在 EURUSD H1 上的连续判错
- 感知机在 EURUSD H1 上的连续误判段
- 感知机在 EURUSD 小时图上的分类塌陷
- 把鸢尾花分类器导成 ONNX 再回测精度
- 在MT5里跑通ONNX分类器的样本推断
- 从输出数组里捞最大概率那一类
- 用鸢尾花样本验证模型准确率与批量推理
- 十样本批次的模型校验写法
- 拿 ONNX 模型在 MT5 里跑 Iris 分类的失败样本
- SGD分类器在EURUSD_H1上的误判集中在类边界
- 把 sklearn 分类器转成 ONNX 喂给 MT5
- 用 ONNX 模型批量推断 IRIS 样本类别
- 从输出数组里挑最大激活值定类
- 批量跑样本看模型命中率
- 十样本批推理的验证闭环
- 高斯贝叶斯分类器的逐样本与批量验证
- 高斯贝叶斯在 EURUSD H1 上的误判样本与导出流程
- 把 ONNX 张量结构和推理精度先摸清楚
- 从 ONNX 输出里捞最大概率分类
- 逐样本回测 IRIS 分类模型
- 批量跑模型看命中率
- 用鸢尾花样本给模型做批量验收
- 在 MT5 里跑通朴素贝叶斯分类器
- CNB分类器在EURUSD_H1上的准确率与导出断层
- 把 ONNX 模型塞进 MT5 做批量推理
- 从 ONNX 输出里捞最大概率分类
- 逐样本跑完 IRIS 全集算准确率
- 一次喂多样本:模型批量推理怎么验
- 用鸢尾花样本跑通 ONNX 模型验收
- CNB分类器在EURUSD_H1上的错判样本
- 高斯先验在 EURUSD 小样本上的连续误判
- CNB分类器在EURUSD_H1上的连续误判段
- CNB分类器在EURUSD_H1上的连续误判带
- CNB在EURUSD_H1上全盘判错与BNB导出脚本
- 把 ONNX 模型塞进 MT5 做批量推理
- 从 ONNX 输出里捞最大概率分类
- 用鸢尾花全集压测模型准确率
- 批量推理的准确率核对写法
- 拿鸢尾花样本压一波分类模型准确率
- 逐样本与批量测试的回测落点
- 贝叶斯分类器在欧美H1上的连续误判段
- 朴素贝叶斯在EURUSD_H1上的连续判错
- BNB分类器在EURUSD_H1上的连续误判段
- 朴素贝叶斯在 EURUSD H1 上的连续误判
- 朴素贝叶斯在 EURUSD H1 上的整段误判
- 朴素贝叶斯在 EURUSD H1 上的连续误判段
- 朴素贝叶斯在 EURUSD H1 上的分类塌方
- 朴素贝叶斯在EURUSD_H1上零准确率的现场
- 把 Python 侧的 ONNX 精度验证搬进 MT5 实跑
- 从 ONNX 输出里捞最大概率分类
- 逐样本跑模型并算命中率
- 用鸢尾花样本给模型做批量体检
- 用鸢尾花样本压测 ONNX 模型准确率
- MLP分类器回测翻车与LDA训练脚本
- 把 LDA 分类器落地成 ONNX 并在 MT5 调用
- 跑 ONNX 模型做样本分类的底层写法
- 用 IRIS 全样本回测模型准确率
- 用鸢尾花样本压一波模型批量推理
- 用十组样本压一下模型准确率
- 加载 ONNX 模型跑 Iris 全样本与批量验证
- 把 sklearn 模型导成 ONNX 再跑一遍验证
- 在MT5里跑通鸢尾花分类的ONNX模型
- 从模型输出里捞最大概率分类
- 用鸢尾花数据集核验模型准确率
- 用三样本和十样本批次验证分类模型
- 用鸢尾花数据集给 ONNX 模型做批量准确率核验
- 把鸢尾花分类器导成 ONNX 再回测
- 把 Python 训好的分类器塞进 MT5 跑推理
- 从模型输出里捞最大概率类别
- 用鸢尾花样本回测模型准确率
- 用鸢尾花样本压模型准确率
- 用鸢尾花集验证分类模型命中率
- 分类器在 EURUSD H1 上的误判样本与准确率
- 分类器在 EURUSD 上扑街与 ONNX 导出实测
- 把 ONNX 模型搬进 MT5 做批量推断
- 从 ONNX 输出里捞最大概率分类
- 遍历鸢尾花样本跑模型准确率
- 用鸢尾花样本给模型做批量体检
- 用鸢尾花样本压测 ONNX 分类模型
- 把 ExtraTrees 模型导成 ONNX 后在 MT5 里跑批测
- 把鸢尾花分类模型塞进 MT5 跑通
- 从 ONNX 输出里捞最大概率类别
- 逐样本跑完 IRIS 全集算命中率
- 批量推理的准确率怎么验
- 用鸢尾花样本跑通 ONNX 模型准确率统计
- 把模型跑满全集再放内存
- 把二十多种分类器一次性塞进同一份特征里
- 把 sklearn 分类器批量导成 ONNX 并核对精度
- 按准确率给模型排座次并画图对比
- 用 ONNX 跑 Iris 样本并取最大概率分类
- 从模型输出里捞最大概率分类
- 把鸢尾花分类模型逐个塞进 MT5 跑一遍
- 跑一遍鸢尾花分类器看准确率落差
- EURUSD_H1 上各 ONNX 分类器的准确率落差
- Iris 分类器在 EURUSD H1 上的精度断层
- 多分类器在 EURUSD H1 上的错判样本
- 半径邻居与逻辑回归在EURUSD-H1上的错分样本
- 分类器在 EURUSD H1 上的错分样本与精度
- 分类器在EURUSD_H1上的错判样本与精度
- 高斯与多项式贝叶斯在EURUSD_H1上的错分样本
- SGD 与朴素贝叶斯的误判样本对照
- 两类分类器在 EURUSD H1 上的错分样本
- 岭分类器在欧元小时图上的错分样本分布
- 分类器在 EURUSD H1 上的错分样本与精度落点
- Ridge 分类器在 EURUSD H1 上的错分样本画像
- 互补朴素贝叶斯在 EURUSD H1 上的连续误判
- 互补朴素贝叶斯在EURUSD_H1上的连续误判段
- 互补朴素贝叶斯在EURUSD_H1上的连续误判
- 互补朴素贝叶斯在 EURUSD H1 上的分类塌方
- 感知机在EURUSD_H1上的误判样本分布
- 感知机在 EURUSD H1 上的连续误判带
- 感知机在 EURUSD H1 上的连续误判段
- 感知机在EURUSD_H1上的连续误判带
- 感知机在 EURUSD H1 上的分类失准现象
- 伯努利贝叶斯在 EURUSD H1 上的连续判错
- 伯努利贝叶斯在 EURUSD-H1 上的连续判错
- 伯努利贝叶斯在EURUSD_H1上的连续误判段
- 伯努利朴素贝叶斯在 EURUSD H1 上的连续误判
- 伯努利朴素贝叶斯在 EURUSD H1 上的整段误判
- 伯努利贝叶斯在EURUSD_H1上的整段误判
- 伯努利贝叶斯在 EURUSD_H1 上的整段误判
- 伯努利贝叶斯在EURUSD_H1上的翻车现场
- 33个分类模型里谁转不了ONNX
- 把工具请下神坛
「CNB、BNB 与 MLP 三类分类器在 MT5 里的落地表现」
补充朴素贝叶斯(CNB)专门冲着类别不平衡去设计,它不算类内特征概率,而是算类外特征分布来纠偏。在 Iris 数据集上跑原始与导出的 ONNX 模型,准确率都落在 66.67%,分类报告里类别 1 的 precision 和 recall 全是 0.00,说明三分类场景下这类纠偏假设反而拖了后腿。 伯努利朴素贝叶斯(BNB)只吃二元特征,Iris 的 4 个连续维度被强行二值化后,原始与 ONNX 模型准确率均仅 33.33%,三类里 macro avg 的 f1-score 只有 0.17。做文本情感或垃圾邮件标记时它才真正好使,直接拿连续行情特征喂它会废掉。 多层感知器(MLP)靠反向传播啃非线性依赖,隐藏层数和每层神经元数都是敏感超参。它要吃大量样本防过拟合,在 MT5 里若用少量 K 线形态做标签,网络稍深就可能在样本外崩。 三类模型导出 ONNX 后,输入张量都固定为 float_input 且 shape [None, 4],输出含 output_label 与 output_probability,这意味着你写 MQL5 加载代码时接口可以复用同一套解析逻辑,不必为每个分类器重写。
LDA 与直方图梯度提升在 MT5 侧的落地表现
把 sklearn 里的 LDA 分类器训在 Iris 上再导出 ONNX,原始模型与 ONNX 模型在 150 条样本上的准确率都是 0.98;逐类看,类别 1 的 recall 为 0.96、类别 2 的 precision 为 0.96,其余两类基本满血。MT5 脚本读取 lda_classifier_iris.onnx 后,输入张量名 float_input 形状 [None, 4],输出 output_label 与 output_probability 两项,跑全量数据准确率仍锁在 98%。
LDA 本质是找超平面最大化类间均值差、压住类内方差,顺带把 4 维特征压进低维空间;它默认特征正态且各类协方差相等,外汇或贵金属的 tick 序列明显不服这个假设,直接套用可能掉精度。异常值也会拽偏投影参数,实盘前建议先截尾处理。
Hist Gradient Boosting 走另一条路:用特征直方图替代穷举,在残差上连续堆树。Iris 上同样能摸到 0.98 准确率,且对混合类型特征更宽容;但树一多就易过拟合,参数没调好时回测漂亮、样本外容易垮。
两类模型都能通过 ONNX 丢进 MT5 的 Scripts 目录跑,路径形如 ...\MQL5\Scripts\lda_classifier_iris.onnx。想验证就开 MT5 加载对应脚本,换你自己的特征矩阵改掉 4 维输入,看终端日志里的 accuracy 是否和 Python 端对齐。
◍ 从贝叶斯到极端随机树:三类分类器在 MT5 里的落地差异
CategoricalNB 是面向离散特征的朴素贝叶斯变体,在 Iris 上跑出的准确率是 0.9333(150 样本支持集),其中类别 1 的 recall 0.96、precision 0.86,类别 2 的 recall 仅 0.84。它假设特征在给定类别下条件独立,计算开销极小,适合做文本类离散数据的基线模型,但遇到特征高度相关或缺失值未处理时,概率估计会明显偏掉。 导出的 ONNX 模型放在 MT5 的 Scripts 目录下(路径含 Terminal 标识 D0E8209F…\MQL5\Scripts\categorical_nb_iris.onnx),输入张量 float_input 为 [None,4] 的 float,输出 output_label 与 output_probability 两张表。ONNX 版准确率与原模型一致,都是 93.33%,说明 Python 端训完直接进 MT5 推理没有精度损失。 ExtraTreeClassifier 与 ExtraTreesClassifier 容易混:前者每个节点随机切分、不搜最优阈值,训练快但精度倾向更低;后者在随机性基础上仍搜最优分割,精度可能更高但计算更重。ExtraTreeClassifier 靠随机阈值+多树汇总抗过拟合,在 Iris 这类小样本上训练极快,但对异常值敏感,参数没调好会出现预测抖动。 外汇与贵金属行情序列若离散化为状态特征(如涨跌段、波动率档),用 CategoricalNB 做基准分类可行,但务必先认清高风险:历史离散模式不代表未来重复,实盘前用 MT5 脚本加载 ONNX 跑一遍样本外验证。
「ExtraTrees 与随机森林的分裂差异」
ExtraTreesClassifier 可视为随机森林的变体,核心改动在节点分裂方式:随机森林为每个节点挑「最佳分割特征」,而 ExtraTrees 直接随机抽特征、再随机取阈值切分。这种更激进的随机化让单棵树更「野」,集合后倾向降低过拟合概率。
在 Iris 150 样本全集上,原始 ExtraTreesClassifier 与导出的 ONNX 版本准确率均为 1.0000;同集中 Random Forest、Gradient Boosting、Bagging、Decision Tree、Extra Tree、Hist Gradient Boosting 也达到同样满分,说明这 7 类模型在干净小样本上几乎无误差。
训练侧它比梯度提升省算力,对异常值也更具鲁棒性;但代价是随机分割导致可解释性差,超参数仍须细调。若你在 MT5 里跑 ONNX 版,输入张量名 float_input 形状 [None,4]、输出 output_label 形状 [None],与 Python 端一致即可无缝替换。
外汇与贵金属行情噪声远高于 Iris,直接套此类分类器易过拟合历史,实盘前务必做样本外验证。
基准分类器在 ONNX 转换上的坑
uniform 策略的 DummyClassifier 对每个类别等概率随机猜,适合类别平衡时测模型平均基线;而 most_frequent 策略只恒猜训练集最多类。在 Iris 150 样本(三类各 50)上跑 most_frequent,准确率卡在 0.333,分类报告里第 0 类 recall=1.00、其余两类全 0,说明它纯粹靠频率蹭基线,没有任何特征学习。 这类零信息模型的价值不在预测,而在流水线压测:你能用它快速量出「换个复杂分类器至少得比 33.3% 高多少才算没白训」。但它不能碰复杂任务,也别指望从输出里挖出数据模式。 真到了导出环节,坑就显形了。下面这段 Python 是在 Iris 上训 most_frequent 并试图转 ONNX 的代码: # import necessary libraries from sklearn import datasets from sklearn.dummy import DummyClassifier from sklearn.metrics import accuracy_score, classification_report from skl2onnx import convert_sklearn from skl2onnx.common.data_types import FloatTensorType import onnxruntime as ort import numpy as np from sys import argv # define the path for saving the model data_path = argv[0] last_index = data_path.rfind("\\") + 1 data_path = data_path[0:last_index] # load the Iris dataset iris = datasets.load_iris() X = iris.data y = iris.target # create a DummyClassifier model with the strategy "most_frequent" dummy_classifier = DummyClassifier(strategy="most_frequent") # train the model on the entire dataset dummy_classifier.fit(X, y) # predict classes for the entire dataset y_pred = dummy_classifier.predict(X) # evaluate the model's accuracy accuracy = accuracy_score(y, y_pred) print("Accuracy of DummyClassifier model:", accuracy) # display the classification report print("\nClassification Report:\n", classification_report(y, y_pred)) # define the input data type initial_type = [('float_input', FloatTensorType([None, X.shape[1]]))] # export the model to ONNX format with float data type onnx_model = convert_sklearn(dummy_classifier, initial_types=initial_type, target_opset=12) # save the model to a file onnx_filename = data_path + "dummy_classifier_iris.onnx" with open(onnx_filename, "wb") as f: f.write(onnx_model.SerializeToString()) # print model path print(f"Model saved to {onnx_filename}") # load the ONNX model and make predictions onnx_session = ort.InferenceSession(onnx_filename) input_name = onnx_session.get_inputs()[0].name output_name = onnx_session.get_outputs()[0].name # display information about input tensors in ONNX print("\nInformation about input tensors in ONNX:") for i, input_tensor in enumerate(onnx_session.get_inputs()): print(f"{i + 1}.Name: {input_tensor.name}, Data Type: {input_tensor.type}, Shape: {input_tensor.shape}") # display information about output tensors in ONNX print("\nInformation about output tensors in ONNX:") for i, output_tensor in enumerate(onnx_session.get_outputs()): print(f"{i + 1}.Name: {output_tensor.name}, Data Type: {output_tensor.type}, Shape: {output_tensor.shape}") # convert data to floating-point format (float32) X_float32 = X.astype(np.float32) # predict classes for the entire dataset using ONNX y_pred_onnx = onnx_session.run([output_name], {input_name:X_float32})[0] # evaluate the accuracy of the ONNX model accuracy_onnx = accuracy_score(y, y_pred_onnx) print("\nAccuracy of DummyClassifier model in ONNX format:", accuracy_onnx) 逐行看关键点:第 23 行 convert_sklearn 调用时传入 target_opset=12;skl2onnx 对 sklearn.dummy.DummyClassifier 没有注册形状计算器,运行直接抛 MissingShapeCalculator,报「Unable to find a shape calculator for type 'sklearn.dummy.DummyClassifier'」。 实测脚本跑完耗时约 2071 ms,原模型准确率 0.333 能打印,但 onnx 文件没生成成。做 MT5 + ONNX 推理管线前,先确认你用的分类器在 sklearn-onnx 覆盖范围内,否则基准都导不进终端。外汇与贵金属市场波动剧烈、杠杆风险高,任何模型导入前的转换失败都得在离线环境先排清,别等实盘会话时报错。
# class="kw">import necessary libraries from sklearn class="kw">import datasets from sklearn.dummy class="kw">import DummyClassifier from sklearn.metrics class="kw">import accuracy_score, classification_report from skl2onnx class="kw">import convert_sklearn from skl2onnx.common.data_types class="kw">import FloatTensorType class="kw">import onnxruntime as ort class="kw">import numpy as np from sys class="kw">import argv # define the path for saving the model data_path = argv[class="num">0] last_index = data_path.rfind("\\") + class="num">1 data_path = data_path[class="num">0:last_index] # load the Iris dataset iris = datasets.load_iris() X = iris.data y = iris.target # create a DummyClassifier model with the strategy "most_frequent" dummy_classifier = DummyClassifier(strategy="most_frequent") # train the model on the entire dataset dummy_classifier.fit(X, y) # predict classes for the entire dataset y_pred = dummy_classifier.predict(X) # evaluate the model&class="macro">#x27;s accuracy accuracy = accuracy_score(y, y_pred) print("Accuracy of DummyClassifier model:", accuracy) # display the classification report print("\nClassification Report:\n", classification_report(y, y_pred)) # define the input data type initial_type = [(&class="macro">#x27;float_input&class="macro">#x27;, FloatTensorType([None, X.shape[class="num">1]]))] # class="kw">export the model to ONNX format with class="type">float data type onnx_model = convert_sklearn(dummy_classifier, initial_types=initial_type, target_opset=class="num">12) # save the model to a file onnx_filename = data_path + "dummy_classifier_iris.onnx" with open(onnx_filename, "wb") as f: f.write(onnx_model.SerializeToString()) # print model path print(f"Model saved to {onnx_filename}") # load the ONNX model and make predictions onnx_session = ort.InferenceSession(onnx_filename) input_name = onnx_session.get_inputs()[class="num">0].name output_name = onnx_session.get_outputs()[class="num">0].name # display information about input tensors in ONNX print("\nInformation about input tensors in ONNX:") for i, input_tensor in enumerate(onnx_session.get_inputs()): print(f"{i + class="num">1}.Name: {input_tensor.name}, Data Type: {input_tensor.type}, Shape: {input_tensor.shape}") # display information about output tensors in ONNX print("\nInformation about output tensors in ONNX:") for i, output_tensor in enumerate(onnx_session.get_outputs()): print(f"{i + class="num">1}.Name: {output_tensor.name}, Data Type: {output_tensor.type}, Shape: {output_tensor.shape}") # convert data to floating-point format(float32) X_float32 = X.astype(np.float32) # predict classes for the entire dataset using ONNX y_pred_onnx = onnx_session.run([output_name], {input_name:X_float32})[class="num">0] # evaluate the accuracy of the ONNX model accuracy_onnx = accuracy_score(y, y_pred_onnx) print("\nAccuracy of DummyClassifier model in ONNX format:", accuracy_onnx)
◍ 高斯过程分类器转 ONNX 的硬伤与标签传播思路
GaussianProcessClassifier 给出的是概率类估计,对评估模型不确定性有帮助,也能随新样本更新预测;可用校准方法改善概率输出。但在 MT5 的 Python 环境里跑 Iris 样例,训练集 150 条三分类准确率约 0.987,分类报告里三类 f1 都在 0.98 以上。 代价是计算成本:样本量大或特征维度高时训练很吃资源,不适合大样本高效训练;贝叶斯统计底子薄的交易者也会觉得它难解释。 更实际的问题是工程落地——示例代码试图用 skl2onnx 导出 ONNX,convert_sklearn 在 target_opset=12 下直接抛 NotImplementedError: Only binary classification is implemented. 也就是说高斯过程分类器目前只能二分类转 ONNX,三分类的 Iris 模型根本导不出,MT5 端没法直接吃。
…
# class="kw">import necessary libraries from sklearn class="kw">import datasets from sklearn.gaussian_process class="kw">import GaussianProcessClassifier from sklearn.gaussian_process.kernels class="kw">import RBF from sklearn.metrics class="kw">import accuracy_score, classification_report from skl2onnx class="kw">import convert_sklearn from skl2onnx.common.data_types class="kw">import FloatTensorType class="kw">import onnxruntime as ort class="kw">import numpy as np from sys class="kw">import argv # define the path for saving the model data_path = argv[class="num">0] last_index = data_path.rfind("\\") + class="num">1 data_path = data_path[class="num">0:last_index] # load the Iris dataset iris = datasets.load_iris() X = iris.data y = iris.target # create a GaussianProcessClassifier model with an RBF kernel kernel = class="num">1.0 * RBF(class="num">1.0) gpc_model = GaussianProcessClassifier(kernel=kernel) # train the model on the entire dataset gpc_model.fit(X, y) # predict classes for the entire dataset y_pred = gpc_model.predict(X) # evaluate the model&class="macro">#x27;s accuracy accuracy = accuracy_score(y, y_pred) print("Accuracy of GaussianProcessClassifier model:", accuracy) # display the classification report print("\nClassification Report:\n", classification_report(y, y_pred)) # define the input data type initial_type = [(&class="macro">#x27;float_input&class="macro">#x27;, FloatTensorType([None, X.shape[class="num">1]]))] # class="kw">export the model to ONNX format with class="type">float data type onnx_model = convert_sklearn(gpc_model, initial_types=initial_type, target_opset=class="num">12) # save the model to a file onnx_filename = data_path + "gpc_iris.onnx" with open(onnx_filename, "wb") as f: f.write(onnx_model.SerializeToString()) # print the path to the model print(f"Model saved to {onnx_filename}") # load the ONNX model and make predictions onnx_session = ort.InferenceSession(onnx_filename) input_name = onnx_session.get_inputs()[class="num">0].name output_name = onnx_session.get_outputs()[class="num">0].name # display information about input tensors in ONNX print("\nInformation about input tensors in ONNX:") for i, input_tensor in enumerate(onnx_session.get_inputs()): print(f"{i + class="num">1}.Name: {input_tensor.name}, Data Type: {input_tensor.type}, Shape: {input_tensor.shape}") # display information about output tensors in ONNX print("\nInformation about output tensors in ONNX:") for i, output_tensor in enumerate(onnx_session.get_outputs()): print(f"{i + class="num">1}.Name: {output_tensor.name}, Data Type: {output_tensor.type}, Shape: {output_tensor.shape}") # convert data to floating-point format(float32) X_float32 = X.astype(np.float32) # predict classes for the entire dataset using ONNX y_pred_onnx = onnx_session.run([output_name], {input_name:X_float32})[class="num">0] # evaluate the accuracy of the ONNX model accuracy_onnx = accuracy_score(y, y_pred_onnx) print("\nAccuracy of GaussianProcessClassifier model in ONNX format:", accuracy_onnx)
「半监督标签传播在 MT5 外的转换坑与 LabelSpreading 正则化差异」
用 Iris 数据集跑 LabelPropagation 做全量训练,分类报告里 3 个类的 precision / recall / f1-score 全是 1.00,150 条样本整体 accuracy 也是 1.0,看起来完美。但把模型通过 skl2onnx 转 ONNX 时直接炸了:convert_sklearn 抛 MissingShapeCalculator,明确说找不到 sklearn.semi_supervised 里 LabelPropagation 的 shape 计算器,脚本在 2064 ms 内中断,lp_iris.onnx 没写出来。 这意味着你想在 MT5 的 Python 环境外复用这套标签传播模型做推理,卡在导出环节。LabelSpreading 与 LabelPropagation 思路同源——都是图上把标记样本标签向未标记样本扩散,但前者多了一层正则化:不仅看实例相似度,还惩罚相邻节点标签差异,迭代中标签改动更稳,理论上过拟合概率更低。 代价是计算侧更重。图结构、相似度度量和正则系数选不好,分类质量会明显波动;样本量或特征维度一大,几次迭代下来的资源消耗可能让本地脚本明显变慢。外汇与贵金属市场属高风险领域,这类半监督思路若用于行情标注,参数敏感性强,实盘前务必用历史数据小批量验证。
半监督标签扩散的收敛坑与质心法边界
LabelSpreading 用标签传播把无标记样本拉进分类体系,少数情况下迭代不收敛,得手动卡迭代次数或调 kernel/alpha,否则在 MT5 的 Python 环境里容易空转。它属于半监督里挺锋利的一类,但参数没调顺之前别直接丢进实盘信号管线。 下面这段是在 Iris 全量标记上训 LabelSpreading 并试图导 ONNX 的示例,跑完原始模型准确率 1.00(150 样本全对),但 convert_sklearn 在 target_opset=12 时直接抛 MissingShapeCalculator——sklearn-onnx 没给 LabelSpreading 注册转换器,意味着这类半监督模型当前没法走 ONNX 进 MT5 的 ONNX 推理节点。
from sklearn.semi_supervised class="kw">import LabelSpreading from skl2onnx class="kw">import convert_sklearn from skl2onnx.common.data_types class="kw">import FloatTensorType # 载入 iris 特征与标签 iris = datasets.load_iris() X = iris.data y = iris.target # 建标签扩散模型,默认 kernel=&class="macro">#x27;rbf&class="macro">#x27; alpha=class="num">0.2 ls_model = LabelSpreading() # 全量拟合,无未标记样本参与 ls_model.fit(X, y) # 定义输入浮点类型供 ONNX 导出 initial_type = [(&class="macro">#x27;float_input&class="macro">#x27;, FloatTensorType([None, X.shape[class="num">1]]))] # 尝试导出,这里会报错 onnx_model = convert_sklearn(ls_model, initial_types=initial_type, target_opset=class="num">12)
from sklearn.semi_supervised class="kw">import LabelSpreading from skl2onnx class="kw">import convert_sklearn from skl2onnx.common.data_types class="kw">import FloatTensorType # 载入 iris 特征与标签 iris = datasets.load_iris() X = iris.data y = iris.target # 建标签扩散模型,默认 kernel=&class="macro">#x27;rbf&class="macro">#x27; alpha=class="num">0.2 ls_model = LabelSpreading() # 全量拟合,无未标记样本参与 ls_model.fit(X, y) # 定义输入浮点类型供 ONNX 导出 initial_type = [(&class="macro">#x27;float_input&class="macro">#x27;, FloatTensorType([None, X.shape[class="num">1]]))] # 尝试导出,这里会报错 onnx_model = convert_sklearn(ls_model, initial_types=initial_type, target_opset=class="num">12)
◍ 近质心分类转 ONNX 的坑与 QDA 的取舍
NearestCentroid 思路很直白:每一类取所有样本的中心点,新数据离哪个中心最近就判给哪类。它在类别线性可分、无明显异常值时解释性强、部署轻,但在 Iris 全集上原生模型准确率约 0.927,类别 1 的 recall 只有 0.92,说明边界样本仍会被邻近中心吸走。 直接把 NearestCentroid 用 skl2onnx 导出会报错:MissingShapeCalculator,即 sklearn-onnx 未实现该预测器的形状计算器。转换代码在 convert_sklearn(nc_model, initial_types=initial_type, target_opset=12) 处中断,意味着这条路线在 MT5 侧走 ONNX 推理前就死了,需要自写转换器或换模型。
# Iris_NearestCentroidClassifier.py # The code demonstrates the process of training NearestCentroid Classifier model on the Iris dataset, exporting it to ONNX format, and making predictions using the ONNX model. # It also evaluates the accuracy of both the original model and the ONNX model. # class="kw">import necessary libraries from sklearn class="kw">import datasets from sklearn.neighbors class="kw">import NearestCentroid from sklearn.metrics class="kw">import accuracy_score, classification_report from skl2onnx class="kw">import convert_sklearn from skl2onnx.common.data_types class="kw">import FloatTensorType class="kw">import onnxruntime as ort class="kw">import numpy as np class="kw">import sys # get the script path script_path = sys.argv[class="num">0] last_index = script_path.rfind("\\") + class="num">1 data_path = script_path[class="num">0:last_index] # load the Iris dataset iris = datasets.load_iris() X = iris.data y = iris.target # create a NearestCentroid model nc_model = NearestCentroid() # train the model on the entire dataset nc_model.fit(X, y) # predict classes for the entire dataset y_pred = nc_model.predict(X) # evaluate the model&class="macro">#x27;s accuracy accuracy = accuracy_score(y, y_pred) print("Accuracy of NearestCentroid model:", accuracy) # display the classification report print("\nClassification Report:\n", classification_report(y, y_pred)) # define the input data type initial_type = [(&class="macro">#x27;float_input&class="macro">#x27;, FloatTensorType([None, X.shape[class="num">1]]))] # class="kw">export the model to ONNX format with class="type">float data type onnx_model = convert_sklearn(nc_model, initial_types=initial_type,target_opset=class="num">12) # save the model to a file onnx_filename = data_path + "nc_iris.onnx" with open(onnx_filename, "wb") as f: f.write(onnx_model.SerializeToString()) # print the path to the model print(f"Model saved to {onnx_filename}") # load the ONNX model and make predictions onnx_session = ort.InferenceSession(onnx_filename) input_name = onnx_session.get_inputs()[class="num">0].name output_name = onnx_session.get_outputs()[class="num">0].name # display information about input tensors in ONNX print("\nInformation about input tensors in ONNX:") for i, input_tensor in enumerate(onnx_session.get_inputs()): print(f"{i + class="num">1}.Name: {input_tensor.name}, Data Type: {input_tensor.type}, Shape: {input_tensor.shape}") # display information about output tensors in ONNX print("\nInformation about output tensors in ONNX:") for i, output_tensor in enumerate(onnx_session.get_outputs()): print(f"{i + class="num">1}.Name: {output_tensor.name}, Data Type: {output_tensor.type}, Shape: {output_tensor.shape}") # convert data to floating-point format(float32) X_float32 = X.astype(np.float32) # predict classes for the entire dataset using ONNX y_pred_onnx = onnx_session.run([output_name], {input_name:X_float32})[class="num">0] # evaluate the accuracy of the ONNX model accuracy_onnx = accuracy_score(y, y_pred_onnx) print("\nAccuracy of NearestCentroid model in ONNX format:", accuracy_onnx)
# Iris_NearestCentroidClassifier.py # The code demonstrates the process of training NearestCentroid Classifier model on the Iris dataset, exporting it to ONNX format, and making predictions using the ONNX model. # It also evaluates the accuracy of both the original model and the ONNX model. # class="kw">import necessary libraries from sklearn class="kw">import datasets from sklearn.neighbors class="kw">import NearestCentroid from sklearn.metrics class="kw">import accuracy_score, classification_report from skl2onnx class="kw">import convert_sklearn from skl2onnx.common.data_types class="kw">import FloatTensorType class="kw">import onnxruntime as ort class="kw">import numpy as np class="kw">import sys # get the script path script_path = sys.argv[class="num">0] last_index = script_path.rfind("\\") + class="num">1 data_path = script_path[class="num">0:last_index] # load the Iris dataset iris = datasets.load_iris() X = iris.data y = iris.target # create a NearestCentroid model nc_model = NearestCentroid() # train the model on the entire dataset nc_model.fit(X, y) # predict classes for the entire dataset y_pred = nc_model.predict(X) # evaluate the model&class="macro">#x27;s accuracy accuracy = accuracy_score(y, y_pred) print("Accuracy of NearestCentroid model:", accuracy) # display the classification report print("\nClassification Report:\n", classification_report(y, y_pred)) # define the input data type initial_type = [(&class="macro">#x27;float_input&class="macro">#x27;, FloatTensorType([None, X.shape[class="num">1]]))] # class="kw">export the model to ONNX format with class="type">float data type onnx_model = convert_sklearn(nc_model, initial_types=initial_type,target_opset=class="num">12) # save the model to a file onnx_filename = data_path + "nc_iris.onnx" with open(onnx_filename, "wb") as f: f.write(onnx_model.SerializeToString()) # print the path to the model print(f"Model saved to {onnx_filename}") # load the ONNX model and make predictions onnx_session = ort.InferenceSession(onnx_filename) input_name = onnx_session.get_inputs()[class="num">0].name output_name = onnx_session.get_outputs()[class="num">0].name # display information about input tensors in ONNX print("\nInformation about input tensors in ONNX:") for i, input_tensor in enumerate(onnx_session.get_inputs()): print(f"{i + class="num">1}.Name: {input_tensor.name}, Data Type: {input_tensor.type}, Shape: {input_tensor.shape}") # display information about output tensors in ONNX print("\nInformation about output tensors in ONNX:") for i, output_tensor in enumerate(onnx_session.get_outputs()): print(f"{i + class="num">1}.Name: {output_tensor.name}, Data Type: {output_tensor.type}, Shape: {output_tensor.shape}") # convert data to floating-point format(float32) X_float32 = X.astype(np.float32) # predict classes for the entire dataset using ONNX y_pred_onnx = onnx_session.run([output_name], {input_name:X_float32})[class="num">0] # evaluate the accuracy of the ONNX model accuracy_onnx = accuracy_score(y, y_pred_onnx) print("\nAccuracy of NearestCentroid model in ONNX format:", accuracy_onnx)
「QDA 导 ONNX 踩到的无效图坑」
在 Iris 数据集上用 Quadratic Discriminant Analysis 训完分类器,原生模型准确率 0.98(150 条样本,三类各 50,macro avg 也是 0.98),看上去没问题。接着用 skl2onnx 以 target_opset=12 导出 qda_iris.onnx,文件确实写到了 MT5 的 Scripts 目录下。 但用 onnxruntime 加载时直接炸了:InvalidGraph,提示 Load model failed: This is an invalid model。脚本跑到第 55 行 ort.InferenceSession(onnx_filename) 就终止,总耗时 2063 ms,说明 QDA 转 ONNX 这一步产出的图本身不被 runtime 认。 实盘外接 ML 模型做信号过滤时,外汇与贵金属波动受宏观事件驱动,模型失效概率高、杠杆风险大;这类导出失败若没在 MT5 启动前本地验过,策略可能静默停摆。先在本机 Python 侧把 InferenceSession 跑通,再谈往终端搬。 下面这段是 scikit-learn 里列全部分类器的引子代码,和上面 QDA 导出同属一套 ML 工具链,贴出来方便你核对环境版本。
# ScikitLearnClassifiers.py # The script lists all the classification algorithms available in scikit-learn # Copyright class="num">2023, MetaQuotes Ltd. # [MQL5官方文档] # print Python version from platform class="kw">import python_version print("The Python version is ", python_version()) # print scikit-learn version class="kw">import sklearn print(&class="macro">#x27;The scikit-learn version is {}.&class="macro">#x27;.format(sklearn.__version__)) # print scikit-learn classifiers from sklearn.utils class="kw">import all_estimators classifiers = all_estimators(type_filter=&class="macro">#x27;classifier&class="macro">#x27;)
把鸢尾花样本塞进 MT5 结构体
这段脚本前半段是 Python 风格的枚举打印,用来逐个列出分类器名称;真正能在 MT5 里跑的是后面那截 Iris.mqh 头文件骨架。它定义了一个 sIRISsample 结构体,把经典 150 条鸢尾花数据映射成可计算的样本单元。 结构体里 sample_id 取 1–150,features[4] 依次存花萼长、花萼宽、花瓣长、花瓣宽四个浮点特征,class_name 是三种鸢尾亚种字符串,class_id 则由 IRISClassID() 转成 0/1/2 整数标签。 IRISClassID 是个朴素查表函数:setosa 返 0,versicolor 返 1,virginica 返 2,匹配不到返 -1。ExtIRISDataset[] 作为全局数组承接样本集,Exttotal 初始为 0,后续靠 AddSample 一类接口补数据。 开 MT5 建个 mqh 把这段贴进去,先手动填几条 sample 进 ExtIRISDataset,调 IRISClassID("Iris-setosa") 验证返值是否为 0,就能确认分类管道没接反。外汇与贵金属行情受杠杆影响大,这类监督学习原型仅供策略建模参考,实盘前务必自测过拟合风险。
for index, (name, ClassifierClass) in enumerate(classifiers, start=class="num">1): print(f"Classifier {index}: {name}") class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Iris.mqh | class=class="str">"cmt">//| Copyright class="num">2023, MetaQuotes Ltd. | class=class="str">"cmt">//| [MQL5官方文档] | class=class="str">"cmt">//+------------------------------------------------------------------+ class="macro">#class="kw">property copyright "Copyright class="num">2023, MetaQuotes Ltd." class="macro">#class="kw">property link "[MQL5官方文档] class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Structure for the IRIS Dataset sample | class=class="str">"cmt">//+------------------------------------------------------------------+ class="kw">struct sIRISsample { class="type">int sample_id; class=class="str">"cmt">// sample id(class="num">1-class="num">150) class="type">class="kw">double features[class="num">4]; class=class="str">"cmt">// SepalLengthCm,SepalWidthCm,PetalLengthCm,PetalWidthCm class="type">class="kw">string class_name; class=class="str">"cmt">// class ("Iris-setosa","Iris-versicolor","Iris-virginica") class="type">int class_id; class=class="str">"cmt">// class id(class="num">0,class="num">1,class="num">2), calculated by function IRISClassID }; class=class="str">"cmt">//--- Iris dataset sIRISsample ExtIRISDataset[]; class="type">int Exttotal=class="num">0; class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Returns class id by class name | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">int IRISClassID(class="type">class="kw">string class_name) { class=class="str">"cmt">//--- if(class_name=="Iris-setosa") class="kw">return(class="num">0); else if(class_name=="Iris-versicolor") class="kw">return(class="num">1); else if(class_name=="Iris-virginica") class="kw">return(class="num">2); class=class="str">"cmt">//--- class="kw">return(-class="num">1); } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| AddSample | class=class="str">"cmt">//+------------------------------------------------------------------+
◍ 把鸢尾花样本塞进MT5数组的写法
想在 MT5 里跑机器学习原型,先得把训练数据装进自定义结构数组。下面这段就是典型做法:用 AddSample 往全局的 ExtIRISDataset 里追加一条四维特征样本,并顺手把品种名转成整型 class_id 方便后续距离计算。 Exttotal 充当写入游标,每次落一条就自增,结构里的 features[0]~[3] 分别对应花萼长、花萼宽、花瓣长、花瓣宽。注意 ArrayResize(ExtIRISDataset,150) 先一次性开够 150 行,避免循环中反复扩容拖慢初始化。 PrepareIrisDataset 里前 17 条全是 Iris-setosa,花萼长集中在 4.3~5.8cm、花瓣宽不超过 0.4cm,这种紧致分布适合拿来做类别可分性验证。开 MT5 把前 17 行抄进脚本,Print 一下 ExtIRISDataset[16].features[3] 应该得到 0.4,能跑通再补剩下 133 条。 外汇与贵金属行情和鸢尾花静态样本不同,实时序列高风险且非平稳,这套装载逻辑只解决「数据进场」,别直接当信号源。
class="type">bool AddSample(const class="type">int Id,const class="type">class="kw">double SepalLengthCm,const class="type">class="kw">double SepalWidthCm,const class="type">class="kw">double PetalLengthCm,const class="type">class="kw">double PetalWidthCm, const class="type">class="kw">string Species) { class=class="str">"cmt">//--- ExtIRISDataset[Exttotal].sample_id=Id; class=class="str">"cmt">//--- ExtIRISDataset[Exttotal].features[class="num">0]=SepalLengthCm; ExtIRISDataset[Exttotal].features[class="num">1]=SepalWidthCm; ExtIRISDataset[Exttotal].features[class="num">2]=PetalLengthCm; ExtIRISDataset[Exttotal].features[class="num">3]=PetalWidthCm; class=class="str">"cmt">//--- ExtIRISDataset[Exttotal].class_name=Species; ExtIRISDataset[Exttotal].class_id=IRISClassID(Species); class=class="str">"cmt">//--- Exttotal++; class=class="str">"cmt">//--- class="kw">return(true); } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Prepare Iris Dataset | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">bool PrepareIrisDataset(sIRISsample &iris_samples[]) { ArrayResize(ExtIRISDataset,class="num">150); Exttotal=class="num">0; class=class="str">"cmt">//--- AddSample(class="num">1,class="num">5.1,class="num">3.5,class="num">1.4,class="num">0.2,"Iris-setosa"); AddSample(class="num">2,class="num">4.9,class="num">3.0,class="num">1.4,class="num">0.2,"Iris-setosa"); AddSample(class="num">3,class="num">4.7,class="num">3.2,class="num">1.3,class="num">0.2,"Iris-setosa"); AddSample(class="num">4,class="num">4.6,class="num">3.1,class="num">1.5,class="num">0.2,"Iris-setosa"); AddSample(class="num">5,class="num">5.0,class="num">3.6,class="num">1.4,class="num">0.2,"Iris-setosa"); AddSample(class="num">6,class="num">5.4,class="num">3.9,class="num">1.7,class="num">0.4,"Iris-setosa"); AddSample(class="num">7,class="num">4.6,class="num">3.4,class="num">1.4,class="num">0.3,"Iris-setosa"); AddSample(class="num">8,class="num">5.0,class="num">3.4,class="num">1.5,class="num">0.2,"Iris-setosa"); AddSample(class="num">9,class="num">4.4,class="num">2.9,class="num">1.4,class="num">0.2,"Iris-setosa"); AddSample(class="num">10,class="num">4.9,class="num">3.1,class="num">1.5,class="num">0.1,"Iris-setosa"); AddSample(class="num">11,class="num">5.4,class="num">3.7,class="num">1.5,class="num">0.2,"Iris-setosa"); AddSample(class="num">12,class="num">4.8,class="num">3.4,class="num">1.6,class="num">0.2,"Iris-setosa"); AddSample(class="num">13,class="num">4.8,class="num">3.0,class="num">1.4,class="num">0.1,"Iris-setosa"); AddSample(class="num">14,class="num">4.3,class="num">3.0,class="num">1.1,class="num">0.1,"Iris-setosa"); AddSample(class="num">15,class="num">5.8,class="num">4.0,class="num">1.2,class="num">0.2,"Iris-setosa"); AddSample(class="num">16,class="num">5.7,class="num">4.4,class="num">1.5,class="num">0.4,"Iris-setosa"); AddSample(class="num">17,class="num">5.4,class="num">3.9,class="num">1.3,class="num">0.4,"Iris-setosa");
「setosa 样本的后半段灌入方式」
上面这段是 Iris-setosa 类别从第 18 到第 43 号共 26 条样本的连续写入,每条都通过 AddSample 推入内存数组,前四个浮点分别对应花萼长、花萼宽、花瓣长、花瓣宽,末位是类别标签字符串。 观察数值分布:花瓣长度集中在 1.0~1.9 之间,花瓣宽度全部分布在 0.1~0.5,第 33 与 38 号甚至压到 0.1;花萼长介于 4.4~5.7,花萼宽 2.3~4.2,第 42 号花萼宽仅 2.3 为区间低点。 把这段代码直接贴进 MT5 的 EA 或脚本里,只要 AddSample 原型已定义,就能在初始化阶段把这批已知标签样本先建好,后续做 KNN 或聚类距离计算时直接引用。外汇与贵金属行情的高波动特征与此静态样本不同,实盘建模须自行评估样本外风险。
AddSample(class="num">18,class="num">5.1,class="num">3.5,class="num">1.4,class="num">0.3,"Iris-setosa"); AddSample(class="num">19,class="num">5.7,class="num">3.8,class="num">1.7,class="num">0.3,"Iris-setosa"); AddSample(class="num">20,class="num">5.1,class="num">3.8,class="num">1.5,class="num">0.3,"Iris-setosa"); AddSample(class="num">21,class="num">5.4,class="num">3.4,class="num">1.7,class="num">0.2,"Iris-setosa"); AddSample(class="num">22,class="num">5.1,class="num">3.7,class="num">1.5,class="num">0.4,"Iris-setosa"); AddSample(class="num">23,class="num">4.6,class="num">3.6,class="num">1.0,class="num">0.2,"Iris-setosa"); AddSample(class="num">24,class="num">5.1,class="num">3.3,class="num">1.7,class="num">0.5,"Iris-setosa"); AddSample(class="num">25,class="num">4.8,class="num">3.4,class="num">1.9,class="num">0.2,"Iris-setosa"); AddSample(class="num">26,class="num">5.0,class="num">3.0,class="num">1.6,class="num">0.2,"Iris-setosa"); AddSample(class="num">27,class="num">5.0,class="num">3.4,class="num">1.6,class="num">0.4,"Iris-setosa"); AddSample(class="num">28,class="num">5.2,class="num">3.5,class="num">1.5,class="num">0.2,"Iris-setosa"); AddSample(class="num">29,class="num">5.2,class="num">3.4,class="num">1.4,class="num">0.2,"Iris-setosa"); AddSample(class="num">30,class="num">4.7,class="num">3.2,class="num">1.6,class="num">0.2,"Iris-setosa"); AddSample(class="num">31,class="num">4.8,class="num">3.1,class="num">1.6,class="num">0.2,"Iris-setosa"); AddSample(class="num">32,class="num">5.4,class="num">3.4,class="num">1.5,class="num">0.4,"Iris-setosa"); AddSample(class="num">33,class="num">5.2,class="num">4.1,class="num">1.5,class="num">0.1,"Iris-setosa"); AddSample(class="num">34,class="num">5.5,class="num">4.2,class="num">1.4,class="num">0.2,"Iris-setosa"); AddSample(class="num">35,class="num">4.9,class="num">3.1,class="num">1.5,class="num">0.2,"Iris-setosa"); AddSample(class="num">36,class="num">5.0,class="num">3.2,class="num">1.2,class="num">0.2,"Iris-setosa"); AddSample(class="num">37,class="num">5.5,class="num">3.5,class="num">1.3,class="num">0.2,"Iris-setosa"); AddSample(class="num">38,class="num">4.9,class="num">3.6,class="num">1.4,class="num">0.1,"Iris-setosa"); AddSample(class="num">39,class="num">4.4,class="num">3.0,class="num">1.3,class="num">0.2,"Iris-setosa"); AddSample(class="num">40,class="num">5.1,class="num">3.4,class="num">1.5,class="num">0.2,"Iris-setosa"); AddSample(class="num">41,class="num">5.0,class="num">3.5,class="num">1.3,class="num">0.3,"Iris-setosa"); AddSample(class="num">42,class="num">4.5,class="num">2.3,class="num">1.3,class="num">0.3,"Iris-setosa"); AddSample(class="num">43,class="num">4.4,class="num">3.2,class="num">1.3,class="num">0.2,"Iris-setosa");
鸢尾花样本的第44到69号注入
在 MT5 的策略推演里,给分类器喂样本不是跑行情,而是先建立特征边界。下面这段调用把 44~50 号归为 Iris-setosa,51~69 号归为 Iris-versicolor,花萼和花瓣的四维测量值直接写死进数组。 setosa 这组花瓣长度集中在 1.4~1.9、宽度 0.2~0.6,和 versicolor 的花瓣长度 3.3~4.9、宽度 1.0~1.6 拉开明显断层。这种数值间距,正是后续 KNN 或贝叶斯分类能直接切分的基础。 把代码贴进 MQ5 的 OnStart 前面做静态初始化,编译后你能在观察窗口看到样本计数从 43 跳到 69。外汇与贵金属实盘迁移时记住:这类标注数据的高拟合不代表实盘概率,杠杆品种回撤风险极高。
AddSample(class="num">44,class="num">5.0,class="num">3.5,class="num">1.6,class="num">0.6,"Iris-setosa"); AddSample(class="num">45,class="num">5.1,class="num">3.8,class="num">1.9,class="num">0.4,"Iris-setosa"); AddSample(class="num">46,class="num">4.8,class="num">3.0,class="num">1.4,class="num">0.3,"Iris-setosa"); AddSample(class="num">47,class="num">5.1,class="num">3.8,class="num">1.6,class="num">0.2,"Iris-setosa"); AddSample(class="num">48,class="num">4.6,class="num">3.2,class="num">1.4,class="num">0.2,"Iris-setosa"); AddSample(class="num">49,class="num">5.3,class="num">3.7,class="num">1.5,class="num">0.2,"Iris-setosa"); AddSample(class="num">50,class="num">5.0,class="num">3.3,class="num">1.4,class="num">0.2,"Iris-setosa"); AddSample(class="num">51,class="num">7.0,class="num">3.2,class="num">4.7,class="num">1.4,"Iris-versicolor"); AddSample(class="num">52,class="num">6.4,class="num">3.2,class="num">4.5,class="num">1.5,"Iris-versicolor"); AddSample(class="num">53,class="num">6.9,class="num">3.1,class="num">4.9,class="num">1.5,"Iris-versicolor"); AddSample(class="num">54,class="num">5.5,class="num">2.3,class="num">4.0,class="num">1.3,"Iris-versicolor"); AddSample(class="num">55,class="num">6.5,class="num">2.8,class="num">4.6,class="num">1.5,"Iris-versicolor"); AddSample(class="num">56,class="num">5.7,class="num">2.8,class="num">4.5,class="num">1.3,"Iris-versicolor"); AddSample(class="num">57,class="num">6.3,class="num">3.3,class="num">4.7,class="num">1.6,"Iris-versicolor"); AddSample(class="num">58,class="num">4.9,class="num">2.4,class="num">3.3,class="num">1.0,"Iris-versicolor"); AddSample(class="num">59,class="num">6.6,class="num">2.9,class="num">4.6,class="num">1.3,"Iris-versicolor"); AddSample(class="num">60,class="num">5.2,class="num">2.7,class="num">3.9,class="num">1.4,"Iris-versicolor"); AddSample(class="num">61,class="num">5.0,class="num">2.0,class="num">3.5,class="num">1.0,"Iris-versicolor"); AddSample(class="num">62,class="num">5.9,class="num">3.0,class="num">4.2,class="num">1.5,"Iris-versicolor"); AddSample(class="num">63,class="num">6.0,class="num">2.2,class="num">4.0,class="num">1.0,"Iris-versicolor"); AddSample(class="num">64,class="num">6.1,class="num">2.9,class="num">4.7,class="num">1.4,"Iris-versicolor"); AddSample(class="num">65,class="num">5.6,class="num">2.9,class="num">3.6,class="num">1.3,"Iris-versicolor"); AddSample(class="num">66,class="num">6.7,class="num">3.1,class="num">4.4,class="num">1.4,"Iris-versicolor"); AddSample(class="num">67,class="num">5.6,class="num">3.0,class="num">4.5,class="num">1.5,"Iris-versicolor"); AddSample(class="num">68,class="num">5.8,class="num">2.7,class="num">4.1,class="num">1.0,"Iris-versicolor"); AddSample(class="num">69,class="num">6.2,class="num">2.2,class="num">4.5,class="num">1.5,"Iris-versicolor");
◍ 鸢尾花第三类样本如何灌入训练集
在 MT5 里做分类器原型时,常拿鸢尾花数据集当最小可跑样本。上面这段就是把编号 70 到 95 的 Iris-versicolor 逐条写进内存样本池,每条含 4 个浮点特征加一个类标字符串。 从数据看,这一类花瓣长度集中在 3.3–5.1,花瓣宽度 1.0–1.8,和前两类有明显数值断层。你在 EA 里复现时,直接把下面代码贴进 OnInit 前的样本装载函数就能看到聚类边界。 外汇与贵金属行情虽不是花萼尺寸,但用同样结构喂历史 K 线特征,也可能跑出可区分的波段形态。注意杠杆市场高风险,样本过拟合会直接放大实盘回撤。
AddSample(class="num">70,class="num">5.6,class="num">2.5,class="num">3.9,class="num">1.1,"Iris-versicolor"); AddSample(class="num">71,class="num">5.9,class="num">3.2,class="num">4.8,class="num">1.8,"Iris-versicolor"); AddSample(class="num">72,class="num">6.1,class="num">2.8,class="num">4.0,class="num">1.3,"Iris-versicolor"); AddSample(class="num">73,class="num">6.3,class="num">2.5,class="num">4.9,class="num">1.5,"Iris-versicolor"); AddSample(class="num">74,class="num">6.1,class="num">2.8,class="num">4.7,class="num">1.2,"Iris-versicolor"); AddSample(class="num">75,class="num">6.4,class="num">2.9,class="num">4.3,class="num">1.3,"Iris-versicolor"); AddSample(class="num">76,class="num">6.6,class="num">3.0,class="num">4.4,class="num">1.4,"Iris-versicolor"); AddSample(class="num">77,class="num">6.8,class="num">2.8,class="num">4.8,class="num">1.4,"Iris-versicolor"); AddSample(class="num">78,class="num">6.7,class="num">3.0,class="num">5.0,class="num">1.7,"Iris-versicolor"); AddSample(class="num">79,class="num">6.0,class="num">2.9,class="num">4.5,class="num">1.5,"Iris-versicolor"); AddSample(class="num">80,class="num">5.7,class="num">2.6,class="num">3.5,class="num">1.0,"Iris-versicolor"); AddSample(class="num">81,class="num">5.5,class="num">2.4,class="num">3.8,class="num">1.1,"Iris-versicolor"); AddSample(class="num">82,class="num">5.5,class="num">2.4,class="num">3.7,class="num">1.0,"Iris-versicolor"); AddSample(class="num">83,class="num">5.8,class="num">2.7,class="num">3.9,class="num">1.2,"Iris-versicolor"); AddSample(class="num">84,class="num">6.0,class="num">2.7,class="num">5.1,class="num">1.6,"Iris-versicolor"); AddSample(class="num">85,class="num">5.4,class="num">3.0,class="num">4.5,class="num">1.5,"Iris-versicolor"); AddSample(class="num">86,class="num">6.0,class="num">3.4,class="num">4.5,class="num">1.6,"Iris-versicolor"); AddSample(class="num">87,class="num">6.7,class="num">3.1,class="num">4.7,class="num">1.5,"Iris-versicolor"); AddSample(class="num">88,class="num">6.3,class="num">2.3,class="num">4.4,class="num">1.3,"Iris-versicolor"); AddSample(class="num">89,class="num">5.6,class="num">3.0,class="num">4.1,class="num">1.3,"Iris-versicolor"); AddSample(class="num">90,class="num">5.5,class="num">2.5,class="num">4.0,class="num">1.3,"Iris-versicolor"); AddSample(class="num">91,class="num">5.5,class="num">2.6,class="num">4.4,class="num">1.2,"Iris-versicolor"); AddSample(class="num">92,class="num">6.1,class="num">3.0,class="num">4.6,class="num">1.4,"Iris-versicolor"); AddSample(class="num">93,class="num">5.8,class="num">2.6,class="num">4.0,class="num">1.2,"Iris-versicolor"); AddSample(class="num">94,class="num">5.0,class="num">2.3,class="num">3.3,class="num">1.0,"Iris-versicolor"); AddSample(class="num">95,class="num">5.6,class="num">2.7,class="num">4.2,class="num">1.3,"Iris-versicolor");
「鸢尾花样本的后段录入细节」
上面这段 AddSample 调用从编号 96 一直排到 121,共 26 条记录,前半段 96–100 仍属于 Iris-versicolor,从 101 起切换为 Iris-virginica。可见类别边界恰好落在样本 100 与 101 之间,versicolor 的 petal length 最大约 4.3,而 virginica 从 5.1 起跳,两者在 4.3–5.1 之间存在明显断层。 如果你在 MT5 里写分类器,直接把这段粘进 OnInit 前的样本装载函数即可。注意第 4 个参数(petal length)和第 5 个参数(petal width)是区分这两类的主分量,versicolor 的 width 集中在 1.1–1.3,virginica 则在 1.5–2.5 区间,用这两个维度做阈值切割可能比塞四个特征更干净。 外汇与贵金属行情虽不是鸢尾花,但用历史 K 线特征做品种聚类时,同样建议先肉眼扫一遍类别边界附近的样本分布,避免标签错位导致后续模型在高风险品种上给出偏误信号。
AddSample(class="num">96,class="num">5.7,class="num">3.0,class="num">4.2,class="num">1.2,"Iris-versicolor"); AddSample(class="num">97,class="num">5.7,class="num">2.9,class="num">4.2,class="num">1.3,"Iris-versicolor"); AddSample(class="num">98,class="num">6.2,class="num">2.9,class="num">4.3,class="num">1.3,"Iris-versicolor"); AddSample(class="num">99,class="num">5.1,class="num">2.5,class="num">3.0,class="num">1.1,"Iris-versicolor"); AddSample(class="num">100,class="num">5.7,class="num">2.8,class="num">4.1,class="num">1.3,"Iris-versicolor"); AddSample(class="num">101,class="num">6.3,class="num">3.3,class="num">6.0,class="num">2.5,"Iris-virginica"); AddSample(class="num">102,class="num">5.8,class="num">2.7,class="num">5.1,class="num">1.9,"Iris-virginica"); AddSample(class="num">103,class="num">7.1,class="num">3.0,class="num">5.9,class="num">2.1,"Iris-virginica"); AddSample(class="num">104,class="num">6.3,class="num">2.9,class="num">5.6,class="num">1.8,"Iris-virginica"); AddSample(class="num">105,class="num">6.5,class="num">3.0,class="num">5.8,class="num">2.2,"Iris-virginica"); AddSample(class="num">106,class="num">7.6,class="num">3.0,class="num">6.6,class="num">2.1,"Iris-virginica"); AddSample(class="num">107,class="num">4.9,class="num">2.5,class="num">4.5,class="num">1.7,"Iris-virginica"); AddSample(class="num">108,class="num">7.3,class="num">2.9,class="num">6.3,class="num">1.8,"Iris-virginica"); AddSample(class="num">109,class="num">6.7,class="num">2.5,class="num">5.8,class="num">1.8,"Iris-virginica"); AddSample(class="num">110,class="num">7.2,class="num">3.6,class="num">6.1,class="num">2.5,"Iris-virginica"); AddSample(class="num">111,class="num">6.5,class="num">3.2,class="num">5.1,class="num">2.0,"Iris-virginica"); AddSample(class="num">112,class="num">6.4,class="num">2.7,class="num">5.3,class="num">1.9,"Iris-virginica"); AddSample(class="num">113,class="num">6.8,class="num">3.0,class="num">5.5,class="num">2.1,"Iris-virginica"); AddSample(class="num">114,class="num">5.7,class="num">2.5,class="num">5.0,class="num">2.0,"Iris-virginica"); AddSample(class="num">115,class="num">5.8,class="num">2.8,class="num">5.1,class="num">2.4,"Iris-virginica"); AddSample(class="num">116,class="num">6.4,class="num">3.2,class="num">5.3,class="num">2.3,"Iris-virginica"); AddSample(class="num">117,class="num">6.5,class="num">3.0,class="num">5.5,class="num">1.8,"Iris-virginica"); AddSample(class="num">118,class="num">7.7,class="num">3.8,class="num">6.7,class="num">2.2,"Iris-virginica"); AddSample(class="num">119,class="num">7.7,class="num">2.6,class="num">6.9,class="num">2.3,"Iris-virginica"); AddSample(class="num">120,class="num">6.0,class="num">2.2,class="num">5.0,class="num">1.5,"Iris-virginica"); AddSample(class="num">121,class="num">6.9,class="num">3.2,class="num">5.7,class="num">2.3,"Iris-virginica");
virginica 样本的尾部录入
上面这段是分类器训练数据里 virginica 类的收尾批次,编号从 122 到 147,共 26 条记录。每条 AddSample 的前四个浮点参数分别对应花萼长、花萼宽、花瓣长、花瓣宽,末位字符串写死类别标签。 拿 132 号看,6.4/2.8/5.6/2.2 这组四维特征被标成 virginica;而 135 号是 6.1/2.6/5.6/1.4,花瓣宽掉到 1.4 仍归同一类。说明该类内部花瓣宽波动区间大约 1.4–2.5,单看这一维并不能干净切类。 在 MT5 里直接把这些行贴进你自己的样本装载函数,跑一遍临近分类,观察 122–147 这一段对整体判错率的影响。外汇与贵金属行情的高风险同理:历史片段再干净,也只代表过去样本分布。
AddSample(class="num">122,class="num">5.6,class="num">2.8,class="num">4.9,class="num">2.0,"Iris-virginica"); AddSample(class="num">123,class="num">7.7,class="num">2.8,class="num">6.7,class="num">2.0,"Iris-virginica"); AddSample(class="num">124,class="num">6.3,class="num">2.7,class="num">4.9,class="num">1.8,"Iris-virginica"); AddSample(class="num">125,class="num">6.7,class="num">3.3,class="num">5.7,class="num">2.1,"Iris-virginica"); AddSample(class="num">126,class="num">7.2,class="num">3.2,class="num">6.0,class="num">1.8,"Iris-virginica"); AddSample(class="num">127,class="num">6.2,class="num">2.8,class="num">4.8,class="num">1.8,"Iris-virginica"); AddSample(class="num">128,class="num">6.1,class="num">3.0,class="num">4.9,class="num">1.8,"Iris-virginica"); AddSample(class="num">129,class="num">6.4,class="num">2.8,class="num">5.6,class="num">2.1,"Iris-virginica"); AddSample(class="num">130,class="num">7.2,class="num">3.0,class="num">5.8,class="num">1.6,"Iris-virginica"); AddSample(class="num">131,class="num">7.4,class="num">2.8,class="num">6.1,class="num">1.9,"Iris-virginica"); AddSample(class="num">132,class="num">7.9,class="num">3.8,class="num">6.4,class="num">2.0,"Iris-virginica"); AddSample(class="num">133,class="num">6.4,class="num">2.8,class="num">5.6,class="num">2.2,"Iris-virginica"); AddSample(class="num">134,class="num">6.3,class="num">2.8,class="num">5.1,class="num">1.5,"Iris-virginica"); AddSample(class="num">135,class="num">6.1,class="num">2.6,class="num">5.6,class="num">1.4,"Iris-virginica"); AddSample(class="num">136,class="num">7.7,class="num">3.0,class="num">6.1,class="num">2.3,"Iris-virginica"); AddSample(class="num">137,class="num">6.3,class="num">3.4,class="num">5.6,class="num">2.4,"Iris-virginica"); AddSample(class="num">138,class="num">6.4,class="num">3.1,class="num">5.5,class="num">1.8,"Iris-virginica"); AddSample(class="num">139,class="num">6.0,class="num">3.0,class="num">4.8,class="num">1.8,"Iris-virginica"); AddSample(class="num">140,class="num">6.9,class="num">3.1,class="num">5.4,class="num">2.1,"Iris-virginica"); AddSample(class="num">141,class="num">6.7,class="num">3.1,class="num">5.6,class="num">2.4,"Iris-virginica"); AddSample(class="num">142,class="num">6.9,class="num">3.1,class="num">5.1,class="num">2.3,"Iris-virginica"); AddSample(class="num">143,class="num">5.8,class="num">2.7,class="num">5.1,class="num">1.9,"Iris-virginica"); AddSample(class="num">144,class="num">6.8,class="num">3.2,class="num">5.9,class="num">2.3,"Iris-virginica"); AddSample(class="num">145,class="num">6.7,class="num">3.3,class="num">5.7,class="num">2.5,"Iris-virginica"); AddSample(class="num">146,class="num">6.7,class="num">3.0,class="num">5.2,class="num">2.3,"Iris-virginica"); AddSample(class="num">147,class="num">6.3,class="num">2.5,class="num">5.0,class="num">1.9,"Iris-virginica");
◍ 把鸢尾花样本灌进分类器并导出 ONNX
MQL5 侧先把 148~150 号三条 Iris-virginica 样本补进数据集,随后用 ArrayResize 把 iris_samples 锁成 150 条,并把 ExtIRISDataset 的内容逐条拷进运行数组,函数返回 true 表示装载完成。 Python 这边走的是另一条路:用 sklearn 加载原生 iris 数据(共 150 条、4 维特征),以线性核 SVC(kernel='linear', C=1.0) 在全量数据上 fit,对同批数据预测后打 accuracy_score,线性核在鸢尾花全集上通常能到 0.97 以上,但这是闭集回测,不能外推到行情。 模型训完立刻转 ONNX:initial_type 按 X.shape[1]=4 指定 FloatTensorType,convert_sklearn 用 target_opset=12 导出 svc_iris.onnx。MT5 后续加载该文件做推理时,输入必须严格是 4 维浮点,否则 InferenceSession 会直接报错。 别把鸢尾花精度当信号质量:它只是验证 ONNX 管道通不通,外汇和贵金属实盘用同类模型面临样本漂移,杠杆品种高风险,盈亏倾向随训练窗口剧烈变化。
AddSample(class="num">148,class="num">6.5,class="num">3.0,class="num">5.2,class="num">2.0,"Iris-virginica"); AddSample(class="num">149,class="num">6.2,class="num">3.4,class="num">5.4,class="num">2.3,"Iris-virginica"); AddSample(class="num">150,class="num">5.9,class="num">3.0,class="num">5.1,class="num">1.8,"Iris-virginica"); class=class="str">"cmt">//--- ArrayResize(iris_samples,class="num">150); for(class="type">int i=class="num">0; i<Exttotal; i++) { iris_samples[i]=ExtIRISDataset[i]; } class=class="str">"cmt">//--- class="kw">return(true); } # class="kw">import necessary libraries from sklearn class="kw">import datasets from sklearn.svm class="kw">import SVC from sklearn.metrics class="kw">import accuracy_score, classification_report from skl2onnx class="kw">import convert_sklearn from skl2onnx.common.data_types class="kw">import FloatTensorType class="kw">import onnxruntime as ort class="kw">import numpy as np from sys class="kw">import argv # define the path for saving the model data_path = argv[class="num">0] last_index = data_path.rfind("\\") + class="num">1 data_path = data_path[class="num">0:last_index] # load the Iris dataset iris = datasets.load_iris() X = iris.data y = iris.target # create an SVC Classifier model with a linear kernel svc_model = SVC(kernel=&class="macro">#x27;linear&class="macro">#x27;, C=class="num">1.0) # train the model on the entire dataset svc_model.fit(X, y) # predict classes for the entire dataset y_pred = svc_model.predict(X) # evaluate the model&class="macro">#x27;s accuracy accuracy = accuracy_score(y, y_pred) print("Accuracy of SVC Classifier model:", accuracy) # display the classification report print("\nClassification Report:\n", classification_report(y, y_pred)) # define the input data type initial_type = [(&class="macro">#x27;float_input&class="macro">#x27;, FloatTensorType([None, X.shape[class="num">1]]))] # class="kw">export the model to ONNX format with class="type">float data type onnx_model = convert_sklearn(svc_model, initial_types=initial_type, target_opset=class="num">12) # save the model to a file onnx_filename = data_path +"svc_iris.onnx" with open(onnx_filename, "wb") as f: f.write(onnx_model.SerializeToString()) # print model path print(f"Model saved to {onnx_filename}") # load the ONNX model and make predictions onnx_session = ort.InferenceSession(onnx_filename) input_name = onnx_session.get_inputs()[class="num">0].name output_name = onnx_session.get_outputs()[class="num">0].name # display information about input tensors in ONNX print("\nInformation about input tensors in ONNX:")
「把 ONNX 张量结构摸透再喂数据」
在 MT5 里跑 ONNX 模型前,先用 Python 侧把输入输出张量的名字、类型和形状打印出来,避免后面在 MQL5 里设错维度。下面这段脚本会遍历会话的输入与输出,逐条输出序号、Name、Data Type 和 Shape,例如输入可能是 float32 的 [N,4],输出是 [N] 的类别索引。 X_float32 = X.astype(np.float32) y_pred_onnx = onnx_session.run([output_name], {input_name: X_float32})[0] accuracy_onnx = accuracy_score(y, y_pred_onnx) 上面三行把数据转成 float32、整批推理并算准确率;在鸢尾花 SVC 例子里,ONNX 推理得到的 accuracy_onnx 通常和原 sklearn 模型一致,可作为本地校验基准。 到了 MQL5 端,模型以 #resource 嵌入为 uchar 数组,TestSamples 函数先取 batch_size=input_data.Range(0),若为 0 直接返回 false。随后用 OnnxSetInputShape(model,0,input_shape) 把输入形状锁成 {batch_size,4},输出按 {batch_size} 准备,这一步必须和 Python 里看到的 Shape 对上,否则推理会报维度错。
class="macro">#include "iris.mqh" class="macro">#resource "svc_iris.onnx" as const class="type">uchar ExtModel[]; class="type">bool TestSamples(class="type">long model,class="type">float &input_data[][class="num">4], class="type">int &model_classes_id[]) { class="type">ulong batch_size=input_data.Range(class="num">0); if(batch_size==class="num">0) class="kw">return(false); ArrayResize(model_classes_id,(class="type">int)batch_size); class="type">ulong input_shape[]= { batch_size, input_data.Range(class="num">1)}; OnnxSetInputShape(model,class="num">0,input_shape); class="type">int output1[]; class="type">float output2[][class="num">3]; ArrayResize(output1,(class="type">int)batch_size); ArrayResize(output2,(class="type">int)batch_size); class="type">ulong output_shape[]= {batch_size};
用鸢尾花全集跑通 ONNX 模型准确率
把 ONNX 模型的两个输出形状先钉死:索引 0 给主输出,索引 1 给第二路输出(例如 3 类标签)。OnnxRun 带调试日志跑完,类别 id 就落在 output1[k],直接搬进 model_classes_id 数组即可。 TestAllIrisDataset 负责把 150 条鸢尾花样本逐条喂回去验证。它先 PrepareIrisDataset 读文件,total_samples 为 0 就直接退出,避免空跑。 循环里每条样本做成 1×4 的 float 输入,batch 固定为 1,单条推理后比对 model_output_classes_id[0] 与原始 class_id,命中就 correct_results++。150 条全跑完,correct_results/150 就是模型在样本集上的准确率参考值,外汇贵金属模型换数据后同理可测,但实盘信号失效概率仍高,属高风险验证。
OnnxSetOutputShape(model,class="num">0,output_shape); class=class="str">"cmt">//--- class="type">ulong output_shape2[]= {batch_size,class="num">3}; OnnxSetOutputShape(model,class="num">1,output_shape2); class=class="str">"cmt">//--- class="type">bool res=OnnxRun(model,ONNX_DEBUG_LOGS,input_data,output1,output2); class=class="str">"cmt">//--- classes are ready in output1[k]; if(res) { for(class="type">int k=class="num">0; k<(class="type">int)batch_size; k++) model_classes_id[k]=output1[k]; } class=class="str">"cmt">//--- class="kw">return(res); } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Test all samples from IRIS dataset(class="num">150) | class=class="str">"cmt">//| Here we test all samples with batch=class="num">1, sample by sample | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">bool TestAllIrisDataset(const class="type">long model,const class="type">class="kw">string model_name,class="type">class="kw">double &model_accuracy) { sIRISsample iris_samples[]; class=class="str">"cmt">//--- load dataset from file PrepareIrisDataset(iris_samples); class=class="str">"cmt">//--- test class="type">int total_samples=ArraySize(iris_samples); if(total_samples==class="num">0) { Print("iris dataset not prepared"); class="kw">return(false); } class=class="str">"cmt">//--- show dataset for(class="type">int k=class="num">0; k<total_samples; k++) { class=class="str">"cmt">//PrintFormat("%d(%.2f,%.2f,%.2f,%.2f) class %d(%s)",iris_samples[k].sample_id,iris_samples[k].features[class="num">0],iris_samples[k].features[class="num">1],iris_samples[k].features[class="num">2],iris_samples[k].features[class="num">3],iris_samples[k].class_id,iris_samples[k].class_name); } class=class="str">"cmt">//--- array for output classes class="type">int model_output_classes_id[]; class=class="str">"cmt">//--- check all Iris dataset samples class="type">int correct_results=class="num">0; for(class="type">int k=class="num">0; k<total_samples; k++) { class=class="str">"cmt">//--- input array class="type">float iris_sample_input_data[class="num">1][class="num">4]; class=class="str">"cmt">//--- prepare input data from kth iris sample dataset iris_sample_input_data[class="num">0][class="num">0]=(class="type">float)iris_samples[k].features[class="num">0]; iris_sample_input_data[class="num">0][class="num">1]=(class="type">float)iris_samples[k].features[class="num">1]; iris_sample_input_data[class="num">0][class="num">2]=(class="type">float)iris_samples[k].features[class="num">2]; iris_sample_input_data[class="num">0][class="num">3]=(class="type">float)iris_samples[k].features[class="num">3]; class=class="str">"cmt">//--- run model class="type">bool res=TestSamples(model,iris_sample_input_data,model_output_classes_id); class=class="str">"cmt">//--- check result if(res) { if(model_output_classes_id[class="num">0]==iris_samples[k].class_id) { correct_results++; } else {
◍ 一次喂三样本和十样本的差异
模型跑单条样本之外,更实用的做法是批量推理。下面这段逻辑用 3×4 的浮点数组一次塞进 3 个鸢尾花样本:第一行 5.1/3.5/1.4/0.2 对应 sample id=1 的 Iris-setosa,第二行 6.3/2.5/4.9/1.5 是 id=73 的 versicolor,第三行 6.3/2.7/4.9/1.8 是 id=124 的 virginica。正确类别数组写死为 {0,1,2},和模型输出做逐位比对。 如果某一位预测类不等于正确类,就打印失败样本的四个特征值和错判类。3 条全对时 correct_results 累加到 3,total_results 也是 3,准确率就是 100%。 随后换 10×4 的批次继续跑,前一条已给的是 id=37 的 setosa(5.5/3.5/1.3/0.2)。两个批次分开统计,能直接看出小批量下模型在边界样本上的稳定性——外汇或贵金属行情用类似结构做批量特征推断时,也要注意样本间相关性会放大误判概率,这类品种杠杆高、波动 abrupt,实盘前务必在 MT5 用历史 tick 复算一遍。
class="type">bool TestBatchExecution(const class="type">long model,const class="type">class="kw">string model_name,class="type">class="kw">double &model_accuracy) { model_accuracy=class="num">0; class=class="str">"cmt">//--- array for output classes class="type">int model_output_classes_id[]; class="type">int correct_results=class="num">0; class="type">int total_results=class="num">0; class="type">bool res=false; class=class="str">"cmt">//--- run batch with class="num">3 samples class="type">float input_data_batch3[class="num">3][class="num">4]= { {class="num">5.1f,class="num">3.5f,class="num">1.4f,class="num">0.2f}, class=class="str">"cmt">// iris dataset sample id=class="num">1, Iris-setosa {class="num">6.3f,class="num">2.5f,class="num">4.9f,class="num">1.5f}, class=class="str">"cmt">// iris dataset sample id=class="num">73, Iris-versicolor {class="num">6.3f,class="num">2.7f,class="num">4.9f,class="num">1.8f} class=class="str">"cmt">// iris dataset sample id=class="num">124, Iris-virginica }; class="type">int correct_classes_batch3[class="num">3]= {class="num">0,class="num">1,class="num">2}; class=class="str">"cmt">//--- run model res=TestSamples(model,input_data_batch3,model_output_classes_id); if(res) { class=class="str">"cmt">//--- check result for(class="type">int j=class="num">0; j<ArraySize(model_output_classes_id); j++) { class=class="str">"cmt">//--- check result if(model_output_classes_id[j]==correct_classes_batch3[j]) correct_results++; else { PrintFormat("model:%s FAILED [class=%d, true class=%d] features=(%.2f,%.2f,%.2f,%.2f)",model_name,model_output_classes_id[j],correct_classes_batch3[j],input_data_batch3[j][class="num">0],input_data_batch3[j][class="num">1],input_data_batch3[j][class="num">2],input_data_batch3[j][class="num">3]); } total_results++; } } else class="kw">return(false); class=class="str">"cmt">//--- run batch with class="num">10 samples class="type">float input_data_batch10[class="num">10][class="num">4]= { {class="num">5.5f,class="num">3.5f,class="num">1.3f,class="num">0.2f}, class=class="str">"cmt">// iris dataset sample id=class="num">37 (Iris-setosa)
「用十组样本压测分类模型的命中率」
上面这段逻辑给模型喂了 10 组四维特征样本,前 4 组是 setosa、接着 2 组 versicolor、最后 4 组 virginica,对应 correct_classes_batch10 里的标签序列 {0,0,0,0,1,1,2,2,2,2}。TestSamples 跑完之后,逐条比对 model_output_classes_id 与真实标签,命中就累加 correct_results,否则把四个特征值和错分信息打印出来。 准确率直接由 correct_results/total_results 算得,total_results 在这里恒为 10。若某次 res 返回 false,函数提前 return(false),后面的精度统计就不会执行。 在 MT5 里把这段接进自己的模型校验脚本,改 input_data_batch10 的前两维当模拟价格特征(例如把 4.9、3.1 换成两段波动率),能快速看分类器在样本外的失真点。外汇与贵金属行情受杠杆与跳空影响,这类离线精度只反映历史样本拟合,实盘信号失效概率偏高,须以小仓位验证。
{class="num">4.9f,class="num">3.1f,class="num">1.5f,class="num">0.1f}, class=class="str">"cmt">// iris dataset sample id=class="num">38 (Iris-setosa)
{class="num">4.4f,class="num">3.0f,class="num">1.3f,class="num">0.2f}, class=class="str">"cmt">// iris dataset sample id=class="num">39 (Iris-setosa)
{class="num">5.0f,class="num">3.3f,class="num">1.4f,class="num">0.2f}, class=class="str">"cmt">// iris dataset sample id=class="num">50 (Iris-setosa)
{class="num">7.0f,class="num">3.2f,class="num">4.7f,class="num">1.4f}, class=class="str">"cmt">// iris dataset sample id=class="num">51 (Iris-versicolor)
{class="num">6.4f,class="num">3.2f,class="num">4.5f,class="num">1.5f}, class=class="str">"cmt">// iris dataset sample id=class="num">52 (Iris-versicolor)
{class="num">6.3f,class="num">3.3f,class="num">6.0f,class="num">2.5f}, class=class="str">"cmt">// iris dataset sample id=class="num">101 (Iris-virginica)
{class="num">5.8f,class="num">2.7f,class="num">5.1f,class="num">1.9f}, class=class="str">"cmt">// iris dataset sample id=class="num">102 (Iris-virginica)
{class="num">7.1f,class="num">3.0f,class="num">5.9f,class="num">2.1f}, class=class="str">"cmt">// iris dataset sample id=class="num">103 (Iris-virginica)
{class="num">6.3f,class="num">2.9f,class="num">5.6f,class="num">1.8f} class=class="str">"cmt">// iris dataset sample id=class="num">104 (Iris-virginica)
};
class=class="str">"cmt">//--- correct classes for all class="num">10 samples in the batch
class="type">int correct_classes_batch10[class="num">10]= {class="num">0,class="num">0,class="num">0,class="num">0,class="num">1,class="num">1,class="num">2,class="num">2,class="num">2,class="num">2};
class=class="str">"cmt">//--- run model
res=TestSamples(model,input_data_batch10,model_output_classes_id);
class=class="str">"cmt">//--- check result
if(res)
{
for(class="type">int j=class="num">0; j<ArraySize(model_output_classes_id); j++)
{
if(model_output_classes_id[j]==correct_classes_batch10[j])
correct_results++;
else
{
class="type">class="kw">double f1=input_data_batch10[j][class="num">0];
class="type">class="kw">double f2=input_data_batch10[j][class="num">1];
class="type">class="kw">double f3=input_data_batch10[j][class="num">2];
class="type">class="kw">double f4=input_data_batch10[j][class="num">3];
PrintFormat("model:%s FAILED [class=%d, true class=%d] features=(%.2f,%.2f,%.2f,%.2f)",model_name,model_output_classes_id[j],correct_classes_batch10[j],input_data_batch10[j][class="num">0],input_data_batch10[j][class="num">1],input_data_batch10[j][class="num">2],input_data_batch10[j][class="num">3]);
}
total_results++;
}
}
else
class="kw">return(false);
class=class="str">"cmt">//--- calculate accuracy
model_accuracy=correct_results/total_results;
class=class="str">"cmt">//---
class="kw">return(res);
}在 MT5 里跑通 ONNX 分类器的实测落差
把训练好的 SVC 分类器塞进 MT5,靠的是 OnnxCreateFromBuffer 从内存缓冲区直接建句柄,而不是从磁盘读文件。下面这段 OnStart 就是标准入口:先建模型,失败就打印错误码,成功则分别跑全样本与批量推理,最后必须 OnnxRelease 释放,否则句柄会泄漏。
class="type">int OnStart(class="type">void) { class="type">class="kw">string model_name="SVCClassifier"; class=class="str">"cmt">//--- class="type">long model=OnnxCreateFromBuffer(ExtModel,ONNX_DEFAULT); if(model==INVALID_HANDLE) { PrintFormat("model_name=%s OnnxCreate error %d for",model_name,GetLastError()); } else { class=class="str">"cmt">//--- test all dataset class="type">class="kw">double model_accuracy=class="num">0; class=class="str">"cmt">//-- test sample by sample execution for all Iris dataset if(TestAllIrisDataset(model,model_name,model_accuracy)) PrintFormat("model=%s all samples accuracy=%f",model_name,model_accuracy); else PrintFormat("error in testing model=%s ",model_name); class=class="str">"cmt">//--- test batch execution for several samples if(TestBatchExecution(model,model_name,model_accuracy)) PrintFormat("model=%s batch test accuracy=%f",model_name,model_accuracy); else PrintFormat("error in testing model=%s ",model_name); class=class="str">"cmt">//--- release model OnnxRelease(model); } class="kw">return(class="num">0); }
class="type">int OnStart(class="type">void) { class="type">class="kw">string model_name="SVCClassifier"; class=class="str">"cmt">//--- class="type">long model=OnnxCreateFromBuffer(ExtModel,ONNX_DEFAULT); if(model==INVALID_HANDLE) { PrintFormat("model_name=%s OnnxCreate error %d for",model_name,GetLastError()); } else { class=class="str">"cmt">//--- test all dataset class="type">class="kw">double model_accuracy=class="num">0; class=class="str">"cmt">//-- test sample by sample execution for all Iris dataset if(TestAllIrisDataset(model,model_name,model_accuracy)) PrintFormat("model=%s all samples accuracy=%f",model_name,model_accuracy); else PrintFormat("error in testing model=%s ",model_name); class=class="str">"cmt">//--- test batch execution for several samples if(TestBatchExecution(model,model_name,model_accuracy)) PrintFormat("model=%s batch test accuracy=%f",model_name,model_accuracy); else PrintFormat("error in testing model=%s ",model_name); class=class="str">"cmt">//--- release model OnnxRelease(model); } class="kw">return(class="num">0); }
◍ 把训练好的分类模型塞进 MT5 的桥接动作
Python 侧先把 LinearSVC 的精度与分类报告打出来,再用 convert_sklearn 以 target_opset=12 导出 ONNX,文件名 linear_svc_iris.onnx。这一步决定了后面 MT5 能不能直接吃进模型权重。 导出时定义的输入类型是 FloatTensorType([None, X.shape[1]]),也就是变长样本数、特征维度锁定为训练集列数。写成文件后,用 onnxruntime 载入并轮询 inputs/outputs 的 name、type、shape,确认张量契约和 Python 端一致,避免 MT5 传错缓冲区。 重新用 ONNX 跑全量 X(转成 float32),算出的 accuracy_onnx 若和原 sklearn 的 accuracy 一致,说明序列化没丢精度。外汇与贵金属行情用这类模型做信号倾向时,样本外表现可能漂移,实盘前务必在 MT5 用历史 tick 复验。 MT5 端只需 #resource 把 onnx 以 uchar 数组编进 exe,include 自己的 iris.mqh 做特征对齐。下面这段是 EA 头部的资源挂载与版本声明,复制后即可在 OnTick 里调 ONNX 推理。
class="macro">#class="kw">property copyright "Copyright class="num">2023, MetaQuotes Ltd." class="macro">#class="kw">property link "[MQL5官方文档] class="macro">#class="kw">property version "class="num">1.00" class="macro">#include "iris.mqh" class="macro">#resource "linear_svc_iris.onnx" as const class="type">uchar ExtModel[]; class=class="str">"cmt">//+------------------------------------------------------------------+
「用 ONNX 模型跑通鸢尾花批量推理」
在 MT5 里接 ONNX 模型做分类,核心是先按模型输入维度对齐张量形状。下面这段 TestSamples 负责把外部传进来的二维特征数组(每行 4 个浮点)灌进模型,并取回类别索引与概率矩阵。
class="type">bool TestSamples(class="type">long model,class="type">float &input_data[][class="num">4], class="type">int &model_classes_id[]) { class=class="str">"cmt">//--- check number of input samples class="type">ulong batch_size=input_data.Range(class="num">0); if(batch_size==class="num">0) class="kw">return(false); class=class="str">"cmt">//--- prepare output array ArrayResize(model_classes_id,(class="type">int)batch_size); class=class="str">"cmt">//--- class="type">ulong input_shape[]= { batch_size, input_data.Range(class="num">1)}; OnnxSetInputShape(model,class="num">0,input_shape); class=class="str">"cmt">//--- class="type">int output1[]; class="type">float output2[][class="num">3]; class=class="str">"cmt">//--- ArrayResize(output1,(class="type">int)batch_size); ArrayResize(output2,(class="type">int)batch_size); class=class="str">"cmt">//--- class="type">ulong output_shape[]= {batch_size}; OnnxSetOutputShape(model,class="num">0,output_shape); class=class="str">"cmt">//--- class="type">ulong output_shape2[]= {batch_size,class="num">3}; OnnxSetOutputShape(model,class="num">1,output_shape2); class=class="str">"cmt">//--- class="type">bool res=OnnxRun(model,ONNX_DEBUG_LOGS,input_data,output1,output2); class=class="str">"cmt">//--- classes are ready in output1[k]; if(res) { for(class="type">int k=class="num">0; k<(class="type">int)batch_size; k++) model_classes_id[k]=output1[k]; } class=class="str">"cmt">//--- class="kw">return(res); }
class="type">bool TestSamples(class="type">long model,class="type">float &input_data[][class="num">4], class="type">int &model_classes_id[]) { class=class="str">"cmt">//--- check number of input samples class="type">ulong batch_size=input_data.Range(class="num">0); if(batch_size==class="num">0) class="kw">return(false); class=class="str">"cmt">//--- prepare output array ArrayResize(model_classes_id,(class="type">int)batch_size); class=class="str">"cmt">//--- class="type">ulong input_shape[]= { batch_size, input_data.Range(class="num">1)}; OnnxSetInputShape(model,class="num">0,input_shape); class=class="str">"cmt">//--- class="type">int output1[]; class="type">float output2[][class="num">3]; class=class="str">"cmt">//--- ArrayResize(output1,(class="type">int)batch_size); ArrayResize(output2,(class="type">int)batch_size); class=class="str">"cmt">//--- class="type">ulong output_shape[]= {batch_size}; OnnxSetOutputShape(model,class="num">0,output_shape); class=class="str">"cmt">//--- class="type">ulong output_shape2[]= {batch_size,class="num">3}; OnnxSetOutputShape(model,class="num">1,output_shape2); class=class="str">"cmt">//--- class="type">bool res=OnnxRun(model,ONNX_DEBUG_LOGS,input_data,output1,output2); class=class="str">"cmt">//--- classes are ready in output1[k]; if(res) { for(class="type">int k=class="num">0; k<(class="type">int)batch_size; k++) model_classes_id[k]=output1[k]; } class=class="str">"cmt">//--- class="kw">return(res); } class="type">bool TestAllIrisDataset(const class="type">long model,const class="type">class="kw">string model_name,class="type">class="kw">double &model_accuracy) { sIRISsample iris_samples[]; class=class="str">"cmt">//--- load dataset from file PrepareIrisDataset(iris_samples); class=class="str">"cmt">//--- test class="type">int total_samples=ArraySize(iris_samples); if(total_samples==class="num">0) { Print("iris dataset not prepared"); class="kw">return(false); } class=class="str">"cmt">//--- show dataset for(class="type">int k=class="num">0; k<total_samples; k++) { class=class="str">"cmt">//PrintFormat("%d(%.2f,%.2f,%.2f,%.2f) class %d(%s)",iris_samples[k].sample_id,iris_samples[k].features[class="num">0],iris_samples[k].features[class="num">1],iris_samples[k].features[class="num">2],iris_samples[k].features[class="num">3],iris_samples[k].class_id,iris_samples[k].class_name); } class=class="str">"cmt">//--- array for output classes class="type">int model_output_classes_id[]; class=class="str">"cmt">//--- check all Iris dataset samples class="type">int correct_results=class="num">0; for(class="type">int k=class="num">0; k<total_samples; k++) { class=class="str">"cmt">//--- input array class="type">float iris_sample_input_data[class="num">1][class="num">4]; class=class="str">"cmt">//--- prepare input data from kth iris sample dataset
单样本与批量推理的准确率核对写法
在 MT5 里验证 ONNX 模型效果时,先逐条灌入样本是最直接的办法。下面这段代码把第 k 个鸢尾花样本的四个特征(花萼长/宽、花瓣长/宽)强转成 float 后塞进输入数组,再调用 TestSamples 得到预测类别。 若预测类别与样本真实 class_id 一致,correct_results 加一;不一致则 PrintFormat 打出具体特征值和错分信息,方便回看是哪一类边界样本被误判。循环结束后用 correct_results/total_samples 算出模型准确率,例如 150 个样本全跑完可能得到 96.00% 这类输出。 批量跑法更适合压测推理延迟。代码里写死了 3 条样本:id=1 的 setosa(5.1,3.5,1.4,0.2)、id=73 的 versicolor(6.3,2.5,4.9,1.5)、id=124 的 virginica(6.3,2.7,4.9,1.8),对应正确类别数组为 {0,1,2}。一次 TestSamples 进 3 行 4 列矩阵,比单条循环更贴近实盘行情批量推断的场景。 开 MT5 把这段 batch 数组换成你自己的 EURUSD 特征矩阵(例如 4 根 K 线的收盘价、RSI、ATR、成交量),就能粗略估计模型在外汇品种上的推断稳定性。外汇与贵金属杠杆高,模型准确率再高也只代表历史样本倾向,实盘仍可能连续回撤。
iris_sample_input_data[class="num">0][class="num">0]=(class="type">float)iris_samples[k].features[class="num">0]; iris_sample_input_data[class="num">0][class="num">1]=(class="type">float)iris_samples[k].features[class="num">1]; iris_sample_input_data[class="num">0][class="num">2]=(class="type">float)iris_samples[k].features[class="num">2]; iris_sample_input_data[class="num">0][class="num">3]=(class="type">float)iris_samples[k].features[class="num">3]; class=class="str">"cmt">//--- run model class="type">bool res=TestSamples(model,iris_sample_input_data,model_output_classes_id); class=class="str">"cmt">//--- check result if(res) { if(model_output_classes_id[class="num">0]==iris_samples[k].class_id) { correct_results++; } else { PrintFormat("model:%s sample=%d FAILED [class=%d, true class=%d] features=(%.2f,%.2f,%.2f,%.2f]",model_name,iris_samples[k].sample_id,model_output_classes_id[class="num">0],iris_samples[k].class_id,iris_samples[k].features[class="num">0],iris_samples[k].features[class="num">1],iris_samples[k].features[class="num">2],iris_samples[k].features[class="num">3]); } } } model_accuracy=class="num">1.0*correct_results/total_samples; class=class="str">"cmt">//--- PrintFormat("model:%s correct results: %.2f%%",model_name,class="num">100*model_accuracy); class=class="str">"cmt">//--- class="kw">return(true); } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Here we test batch execution of the model | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">bool TestBatchExecution(const class="type">long model,const class="type">class="kw">string model_name,class="type">class="kw">double &model_accuracy) { model_accuracy=class="num">0; class=class="str">"cmt">//--- array for output classes class="type">int model_output_classes_id[]; class="type">int correct_results=class="num">0; class="type">int total_results=class="num">0; class="type">bool res=false; class=class="str">"cmt">//--- run batch with class="num">3 samples class="type">float input_data_batch3[class="num">3][class="num">4]= { {class="num">5.1f,class="num">3.5f,class="num">1.4f,class="num">0.2f}, class=class="str">"cmt">// iris dataset sample id=class="num">1, Iris-setosa {class="num">6.3f,class="num">2.5f,class="num">4.9f,class="num">1.5f}, class=class="str">"cmt">// iris dataset sample id=class="num">73, Iris-versicolor {class="num">6.3f,class="num">2.7f,class="num">4.9f,class="num">1.8f} class=class="str">"cmt">// iris dataset sample id=class="num">124, Iris-virginica }; class="type">int correct_classes_batch3[class="num">3]= {class="num">0,class="num">1,class="num">2}; class=class="str">"cmt">//--- run model res=TestSamples(model,input_data_batch3,model_output_classes_id); if(res) { class=class="str">"cmt">//--- check result for(class="type">int j=class="num">0; j<ArraySize(model_output_classes_id); j++) { class=class="str">"cmt">//--- check result
◍ 十样本批测里的错判追踪
在模型验证环节,把 10 组特征塞进 TestSamples 跑一遍,比 3 样本批测更能暴露分类器的边界问题。下面这段批测数据直接取自鸢尾花数据集的 37–104 号样本,前 4 组为 setosa(标签 0),中间 2 组 versicolor(标签 1),后 4 组 virginica(标签 2),correct_classes_batch10 写死了真实分类。 跑完之后用 for 循环比对 model_output_classes_id 与 correct_classes_batch10,一致就 correct_results++,不一致则进入 else 分支把四个特征原样打印。注意这里错判日志用的是 double f1~f4 局部变量承接,和前面 3 样本批测里 PrintFormat 直接取数组的写法不同,算是同一种检查的两种落地方式。 开 MT5 把这段接在你自己的 ONNX 推理后面,若 correct_results 不足 10,终端会列出具体哪一行特征导致模型分错类——外汇或贵金属行情特征若也按四维输入,可用同样结构做样本回放,但需注意实盘数据分布漂移,模型误判概率可能显著高于静态数据集。
if(model_output_classes_id[j]==correct_classes_batch3[j]) correct_results++; else { PrintFormat("model:%s FAILED [class=%d, true class=%d] features=(%.2f,%.2f,%.2f,%.2f)",model_name,model_output_classes_id[j],correct_classes_batch3[j],input_data_batch3[j][class="num">0],input_data_batch3[j][class="num">1],input_data_batch3[j][class="num">2],input_data_batch3[j][class="num">3]); } total_results++; } } else class="kw">return(false); class=class="str">"cmt">//--- run batch with class="num">10 samples class="type">float input_data_batch10[class="num">10][class="num">4]= { {class="num">5.5f,class="num">3.5f,class="num">1.3f,class="num">0.2f}, class=class="str">"cmt">// iris dataset sample id=class="num">37 (Iris-setosa) {class="num">4.9f,class="num">3.1f,class="num">1.5f,class="num">0.1f}, class=class="str">"cmt">// iris dataset sample id=class="num">38 (Iris-setosa) {class="num">4.4f,class="num">3.0f,class="num">1.3f,class="num">0.2f}, class=class="str">"cmt">// iris dataset sample id=class="num">39 (Iris-setosa) {class="num">5.0f,class="num">3.3f,class="num">1.4f,class="num">0.2f}, class=class="str">"cmt">// iris dataset sample id=class="num">50 (Iris-setosa) {class="num">7.0f,class="num">3.2f,class="num">4.7f,class="num">1.4f}, class=class="str">"cmt">// iris dataset sample id=class="num">51 (Iris-versicolor) {class="num">6.4f,class="num">3.2f,class="num">4.5f,class="num">1.5f}, class=class="str">"cmt">// iris dataset sample id=class="num">52 (Iris-versicolor) {class="num">6.3f,class="num">3.3f,class="num">6.0f,class="num">2.5f}, class=class="str">"cmt">// iris dataset sample id=class="num">101 (Iris-virginica) {class="num">5.8f,class="num">2.7f,class="num">5.1f,class="num">1.9f}, class=class="str">"cmt">// iris dataset sample id=class="num">102 (Iris-virginica) {class="num">7.1f,class="num">3.0f,class="num">5.9f,class="num">2.1f}, class=class="str">"cmt">// iris dataset sample id=class="num">103 (Iris-virginica) {class="num">6.3f,class="num">2.9f,class="num">5.6f,class="num">1.8f} class=class="str">"cmt">// iris dataset sample id=class="num">104 (Iris-virginica) }; class=class="str">"cmt">//--- correct classes for all class="num">10 samples in the batch class="type">int correct_classes_batch10[class="num">10]= {class="num">0,class="num">0,class="num">0,class="num">0,class="num">1,class="num">1,class="num">2,class="num">2,class="num">2,class="num">2}; class=class="str">"cmt">//--- run model res=TestSamples(model,input_data_batch10,model_output_classes_id); class=class="str">"cmt">//--- check result if(res) { for(class="type">int j=class="num">0; j<ArraySize(model_output_classes_id); j++) { if(model_output_classes_id[j]==correct_classes_batch10[j]) correct_results++; else { class="type">class="kw">double f1=input_data_batch10[j][class="num">0]; class="type">class="kw">double f2=input_data_batch10[j][class="num">1]; class="type">class="kw">double f3=input_data_batch10[j][class="num">2];
「在 MT5 里跑通 ONNX 模型并读出准确率」
把训练好的 LinearSVC 模型塞进 MT5,靠的是 OnnxCreateFromBuffer 从内存缓冲区直接建句柄,而不是去读外部文件。建不出来就打印错误码,建出来才往下走测试逻辑。 测试分两条线:TestAllIrisDataset 逐样本跑全量数据,TestBatchExecution 按批跑若干样本,两者都把算出的正确率写进同一个 model_accuracy 变量。跑完无论准不准,都得调 OnnxRelease 把模型句柄释放掉,不然会占着资源。 实跑日志里能直接看到漏判样本。比如 sample=71 被模型判成 class=2,真实是 class=1,特征向量是 (5.90,3.20,4.80,1.80);sample=84、85 也是同样错法,特征分别在 (6.00,2.70,5.10,1.60) 和 (5.40,3.00,4.50,1.50)。这说明该模型在 class=1 与 class=2 边界附近有系统性混淆,准确率可能掉到九成出头。 外汇和贵金属行情高波动、高杠杆,这类离线分类模型只反映历史样本分布,实盘信号失效概率不低,任何结论都只能当概率参考。
class="type">class="kw">double f4=input_data_batch10[j][class="num">3]; PrintFormat("model:%s FAILED [class=%d, true class=%d] features=(%.2f,%.2f,%.2f,%.2f)",model_name,model_output_classes_id[j],correct_classes_batch10[j],input_data_batch10[j][class="num">0],input_data_batch10[j][class="num">1],input_data_batch10[j][class="num">2],input_data_batch10[j][class="num">3]); } total_results++; } } else class="kw">return(false); class=class="str">"cmt">//--- calculate accuracy model_accuracy=correct_results/total_results; class=class="str">"cmt">//--- class="kw">return(res); } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Script program start function | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">int OnStart(class="type">void) { class="type">class="kw">string model_name="LinearSVC"; class=class="str">"cmt">//--- class="type">long model=OnnxCreateFromBuffer(ExtModel,ONNX_DEFAULT); if(model==INVALID_HANDLE) { PrintFormat("model_name=%s OnnxCreate error %d for",model_name,GetLastError()); } else { class=class="str">"cmt">//--- test all dataset class="type">class="kw">double model_accuracy=class="num">0; class=class="str">"cmt">//-- test sample by sample execution for all Iris dataset if(TestAllIrisDataset(model,model_name,model_accuracy)) PrintFormat("model=%s all samples accuracy=%f",model_name,model_accuracy); else PrintFormat("error in testing model=%s ",model_name); class=class="str">"cmt">//--- test batch execution for several samples if(TestBatchExecution(model,model_name,model_accuracy)) PrintFormat("model=%s batch test accuracy=%f",model_name,model_accuracy); else PrintFormat("error in testing model=%s ",model_name); class=class="str">"cmt">//--- release model OnnxRelease(model); } class="kw">return(class="num">0); }
LinearSVC 在 EURUSD H1 上的错分样本与 NuSVC 导出链路
把 Iris 特征直接套到 EURUSD H1 做 LinearSVC 分类,整批准确率报 0.966667,但样本 130 和 134 暴露了边界问题:模型判 class=1,真实是 class=2,特征向量分别是 (7.20,3.00,5.80,1.60) 与 (6.30,2.80,5.10,1.50)。这说明线性核在花萼/花瓣尺寸接近的重叠区会把两类揉在一起,回测里错分就落在这些窄缝。 batch test accuracy 跑到 1.000000 只是小批量抽样假象,全样本 96.67% 才更接近实情。外汇和贵金属行情里这类重叠区对应的信号模糊地带,手动跟单容易吃假突破,属于高风险场景,概率上更该降权处理。 换 NuSVC(nu=0.5, kernel='linear')走同数据,再把模型转 ONNX 交给 MT5 侧推理,是另一条更稳的落地路径。下面这段 Python 负责训练、评估并导出 nusvc_iris.onnx,核心行都拆在注释对应处。 别把 96% 当安全垫 全样本准确率和 batch 准确率差 3 个多点,说明你的验证集一旦避开重叠样本就会虚高。开 MT5 前先拿线性模型跑一遍错分索引,确认自己是不是只在挑容易的样本。
# class="kw">import necessary libraries from sklearn class="kw">import datasets from sklearn.svm class="kw">import NuSVC from sklearn.metrics class="kw">import accuracy_score, classification_report from skl2onnx class="kw">import convert_sklearn from skl2onnx.common.data_types class="kw">import FloatTensorType class="kw">import onnxruntime as ort class="kw">import numpy as np from sys class="kw">import argv # define the path for saving the model data_path = argv[class="num">0] last_index = data_path.rfind("\\") + class="num">1 data_path = data_path[class="num">0:last_index] # load the Iris dataset iris = datasets.load_iris() X = iris.data y = iris.target # create a NuSVC model nusvc_model = NuSVC(nu=class="num">0.5, kernel=&class="macro">#x27;linear&class="macro">#x27;) # train the model on the entire dataset nusvc_model.fit(X, y) # predict classes for the entire dataset y_pred = nusvc_model.predict(X) # evaluate the model&class="macro">#x27;s accuracy accuracy = accuracy_score(y, y_pred) print("Accuracy of NuSVC model:", accuracy) # display the classification report print("\nClassification Report:\n", classification_report(y, y_pred)) # define the input data type initial_type = [(&class="macro">#x27;float_input&class="macro">#x27;, FloatTensorType([None, X.shape[class="num">1]]))] # class="kw">export the model to ONNX format with class="type">float data type onnx_model = convert_sklearn(nusvc_model, initial_types=initial_type, target_opset=class="num">12) # save the model to a file onnx_filename = data_path + "nusvc_iris.onnx" with open(onnx_filename, "wb") as f: f.write(onnx_model.SerializeToString()) # print model path print(f"Model saved to {onnx_filename}") # load the ONNX model and make predictions onnx_session = ort.InferenceSession(onnx_filename) input_name = onnx_session.get_inputs()[class="num">0].name output_name = onnx_session.get_outputs()[class="num">0].name # display information about input tensors in ONNX print("\nInformation about input tensors in ONNX:") for i, input_tensor in enumerate(onnx_session.get_inputs()):
◍ 把 Python 端的 ONNX 结构搬进 MT5 实测
在 Python 里跑通 NuSVC 的 ONNX 模型后,先用几行把输入输出张量的名字、类型和形状打出来:输入一般是 4 维浮点(对应鸢尾花的四个特征),输出是类别索引或 3 维概率分布。用 X_float32 = X.astype(np.float32) 把数据转成 float32,再 onnx_session.run 拿到 y_pred_onnx,用 accuracy_score 算出的准确率就是一个可核对的数字,方便和 MT5 端结果比对。 MT5 这边用 #resource 把 nusvc_iris.onnx 以 uchar 数组形式编进 exe,省去外部文件路径依赖。TestSamples 函数先取 input_data.Range(0) 作为 batch_size,为 0 直接返回 false;再用 OnnxSetInputShape(model,0,input_shape) 把动态批大小喂给模型,输入形状就是 {batch_size, 4}。 输出侧准备了 output1[](类别 id)和 output2[][3](每类分数),都按 batch_size 用 ArrayResize 扩好。这样同一份 ONNX 在 MT5 里推理的结构就和 Python 端对齐了,打开 MT5 把 iris.mqh 和 onnx 资源挂上,就能直接验证两边预测是否一致。外汇与贵金属行情受杠杆影响大,这类模型仅作特征分类演示,实盘使用须自担高风险。
class="macro">#include "iris.mqh" class="macro">#resource "nusvc_iris.onnx" as const class="type">uchar ExtModel[]; class="type">bool TestSamples(class="type">long model,class="type">float &input_data[][class="num">4], class="type">int &model_classes_id[]) { class="type">ulong batch_size=input_data.Range(class="num">0); if(batch_size==class="num">0) class="kw">return(false); ArrayResize(model_classes_id,(class="type">int)batch_size); class="type">ulong input_shape[]= { batch_size, input_data.Range(class="num">1)}; OnnxSetInputShape(model,class="num">0,input_shape); class="type">int output1[]; class="type">float output2[][class="num">3]; ArrayResize(output1,(class="type">int)batch_size); ArrayResize(output2,(class="type">int)batch_size); class="type">ulong output_shape[]= {batch_size};
「用 IRIS 全集跑一遍 ONNX 分类准确率」
把 ONNX 模型的两个输出形状先钉死:第一个输出维度是 batch_size 加 0(类别 id 向量),第二个是 batch_size 配 3(三分类的辅助输出)。OnnxRun 带 ONNX_DEBUG_LOGS 跑完,output1[k] 里就是第 k 个样本的类别序号,直接塞进 model_classes_id 数组。 验证阶段走的是 TestAllIrisDataset:从文件读入 150 条 IRIS 样本,PrepareIrisDataset 没加载到数据会直接 Print 报错并返回 false。total_samples 为 0 时函数退出,不会往下白跑。 逐条测试时 batch 固定为 1,每条样本拆成 [1][4] 的 float 输入(花萼长宽、花瓣长宽四个特征)。TestSamples 返回 true 后比对的不是概率,而是 model_output_classes_id[0] 和样本自带 class_id 是否相等;相等则 correct_results 加一。
- 条全跑完,correct_results 除以 150 就是 model_accuracy 的实打实分母。外汇与贵金属行情受宏观事件驱动,ML 分类器在静态数据集上的准确率不能直接外推到实时报价,实盘使用前建议在 MT5 策略测试器里用历史 tick 重算误差分布。
OnnxSetOutputShape(model,class="num">0,output_shape); class=class="str">"cmt">//--- class="type">ulong output_shape2[]= {batch_size,class="num">3}; OnnxSetOutputShape(model,class="num">1,output_shape2); class=class="str">"cmt">//--- class="type">bool res=OnnxRun(model,ONNX_DEBUG_LOGS,input_data,output1,output2); class=class="str">"cmt">//--- classes are ready in output1[k]; if(res) { for(class="type">int k=class="num">0; k<(class="type">int)batch_size; k++) model_classes_id[k]=output1[k]; } class=class="str">"cmt">//--- class="kw">return(res); } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Test all samples from IRIS dataset(class="num">150) | class=class="str">"cmt">//| Here we test all samples with batch=class="num">1, sample by sample | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">bool TestAllIrisDataset(const class="type">long model,const class="type">class="kw">string model_name,class="type">class="kw">double &model_accuracy) { sIRISsample iris_samples[]; class=class="str">"cmt">//--- load dataset from file PrepareIrisDataset(iris_samples); class=class="str">"cmt">//--- test class="type">int total_samples=ArraySize(iris_samples); if(total_samples==class="num">0) { Print("iris dataset not prepared"); class="kw">return(false); } class=class="str">"cmt">//--- show dataset for(class="type">int k=class="num">0; k<total_samples; k++) { class=class="str">"cmt">//PrintFormat("%d(%.2f,%.2f,%.2f,%.2f) class %d(%s)",iris_samples[k].sample_id,iris_samples[k].features[class="num">0],iris_samples[k].features[class="num">1],iris_samples[k].features[class="num">2],iris_samples[k].features[class="num">3],iris_samples[k].class_id,iris_samples[k].class_name); } class=class="str">"cmt">//--- array for output classes class="type">int model_output_classes_id[]; class=class="str">"cmt">//--- check all Iris dataset samples class="type">int correct_results=class="num">0; for(class="type">int k=class="num">0; k<total_samples; k++) { class=class="str">"cmt">//--- input array class="type">float iris_sample_input_data[class="num">1][class="num">4]; class=class="str">"cmt">//--- prepare input data from kth iris sample dataset iris_sample_input_data[class="num">0][class="num">0]=(class="type">float)iris_samples[k].features[class="num">0]; iris_sample_input_data[class="num">0][class="num">1]=(class="type">float)iris_samples[k].features[class="num">1]; iris_sample_input_data[class="num">0][class="num">2]=(class="type">float)iris_samples[k].features[class="num">2]; iris_sample_input_data[class="num">0][class="num">3]=(class="type">float)iris_samples[k].features[class="num">3]; class=class="str">"cmt">//--- run model class="type">bool res=TestSamples(model,iris_sample_input_data,model_output_classes_id); class=class="str">"cmt">//--- check result if(res) { if(model_output_classes_id[class="num">0]==iris_samples[k].class_id) { correct_results++; } else {
用鸢尾花数据验证模型批量推理
把 ONNX 模型接进 MT5 后,最直接的验收办法是拿已知标签的小样本跑批量推理。下面这段逻辑用鸢尾花数据集的 3 条样本做第一批验证:特征维度固定为 4,正确类别分别是 0、1、2(对应 setosa、versicolor、virginica)。 代码里先定义 input_data_batch3[3][4],例如 {6.3f,2.5f,4.9f,1.5f} 是第 73 号样本,标签应为 1。调用 TestSamples 拿到 model_output_classes_id 数组后,逐条比对,命中就 correct_results++,未命中则 PrintFormat 打出特征四元组方便排查。 第二批扩到 10 条样本(input_data_batch10[10][4]),首条 {5.5f,3.5f,1.3f,0.2f} 来自第 37 号 setosa。两批跑完,准确率按 1.0*correct_results/total_results 计算,单批 3 样本全对即 100.00%,任意错分则按比例下修。外汇与贵金属行情的高风险属性类似:模型离线验证通过,不等于实盘推理稳健,参数漂移需持续回测。 让小布替你跑这套 把这两段 batch 数组直接贴进 MT5 脚本,改 model 句柄即可在专家日志里看逐条命中情况;若 3 样本批都错分,优先查特征归一化是否与训练时一致。
class="type">bool TestBatchExecution(const class="type">long model,const class="type">class="kw">string model_name,class="type">class="kw">double &model_accuracy) { model_accuracy=class="num">0; class=class="str">"cmt">//--- array for output classes class="type">int model_output_classes_id[]; class="type">int correct_results=class="num">0; class="type">int total_results=class="num">0; class="type">bool res=false; class=class="str">"cmt">//--- run batch with class="num">3 samples class="type">float input_data_batch3[class="num">3][class="num">4]= { {class="num">5.1f,class="num">3.5f,class="num">1.4f,class="num">0.2f}, class=class="str">"cmt">// iris dataset sample id=class="num">1, Iris-setosa {class="num">6.3f,class="num">2.5f,class="num">4.9f,class="num">1.5f}, class=class="str">"cmt">// iris dataset sample id=class="num">73, Iris-versicolor {class="num">6.3f,class="num">2.7f,class="num">4.9f,class="num">1.8f} class=class="str">"cmt">// iris dataset sample id=class="num">124, Iris-virginica }; class="type">int correct_classes_batch3[class="num">3]= {class="num">0,class="num">1,class="num">2}; class=class="str">"cmt">//--- run model res=TestSamples(model,input_data_batch3,model_output_classes_id); if(res) { class=class="str">"cmt">//--- check result for(class="type">int j=class="num">0; j<ArraySize(model_output_classes_id); j++) { class=class="str">"cmt">//--- check result if(model_output_classes_id[j]==correct_classes_batch3[j]) correct_results++; else { PrintFormat("model:%s FAILED [class=%d, true class=%d] features=(%.2f,%.2f,%.2f,%.2f)",model_name,model_output_classes_id[j],correct_classes_batch3[j],input_data_batch3[j][class="num">0],input_data_batch3[j][class="num">1],input_data_batch3[j][class="num">2],input_data_batch3[j][class="num">3]); } total_results++; } } else class="kw">return(false); class=class="str">"cmt">//--- run batch with class="num">10 samples class="type">float input_data_batch10[class="num">10][class="num">4]= { {class="num">5.5f,class="num">3.5f,class="num">1.3f,class="num">0.2f}, class=class="str">"cmt">// iris dataset sample id=class="num">37 (Iris-setosa)
◍ 用鸢尾花样本压一遍模型准确率
这段逻辑把 10 条已知分类的鸢尾花特征塞进训练好的模型里跑,用来验证分类器在 MT5 环境下的判别一致性。样本覆盖三个亚种:id=38~50 为 setosa(类标 0),id=51~52 为 versicolor(类标 1),id=101~104 为 virginica(类标 2),特征维度固定为花萼长、花萼宽、花瓣长、花瓣宽四个浮点值。 correct_classes_batch10 数组写死了真实类标序列 {0,0,0,0,1,1,2,2,2,2},和上面 10 行输入严格对齐。TestSamples 返回后逐条比对 model_output_classes_id 与真实类标,命中就累加 correct_results,错过则把四条特征打印出来方便肉眼排查。 最后 model_accuracy=correct_results/total_results 算出这批 10 样本的准确率。你在 EA 里接自己的行情特征向量时,可照这个结构先拿历史 labeled 数据跑一遍,看回测窗口内模型倾向给出多少误判;外汇与贵金属行情噪声远高于鸢尾花静态特征,实盘误判概率可能明显抬高,属高风险验证。
{class="num">4.9f,class="num">3.1f,class="num">1.5f,class="num">0.1f}, class=class="str">"cmt">// iris dataset sample id=class="num">38 (Iris-setosa)
{class="num">4.4f,class="num">3.0f,class="num">1.3f,class="num">0.2f}, class=class="str">"cmt">// iris dataset sample id=class="num">39 (Iris-setosa)
{class="num">5.0f,class="num">3.3f,class="num">1.4f,class="num">0.2f}, class=class="str">"cmt">// iris dataset sample id=class="num">50 (Iris-setosa)
{class="num">7.0f,class="num">3.2f,class="num">4.7f,class="num">1.4f}, class=class="str">"cmt">// iris dataset sample id=class="num">51 (Iris-versicolor)
{class="num">6.4f,class="num">3.2f,class="num">4.5f,class="num">1.5f}, class=class="str">"cmt">// iris dataset sample id=class="num">52 (Iris-versicolor)
{class="num">6.3f,class="num">3.3f,class="num">6.0f,class="num">2.5f}, class=class="str">"cmt">// iris dataset sample id=class="num">101 (Iris-virginica)
{class="num">5.8f,class="num">2.7f,class="num">5.1f,class="num">1.9f}, class=class="str">"cmt">// iris dataset sample id=class="num">102 (Iris-virginica)
{class="num">7.1f,class="num">3.0f,class="num">5.9f,class="num">2.1f}, class=class="str">"cmt">// iris dataset sample id=class="num">103 (Iris-virginica)
{class="num">6.3f,class="num">2.9f,class="num">5.6f,class="num">1.8f} class=class="str">"cmt">// iris dataset sample id=class="num">104 (Iris-virginica)
};
class=class="str">"cmt">//--- correct classes for all class="num">10 samples in the batch
class="type">int correct_classes_batch10[class="num">10]= {class="num">0,class="num">0,class="num">0,class="num">0,class="num">1,class="num">1,class="num">2,class="num">2,class="num">2,class="num">2};
class=class="str">"cmt">//--- run model
res=TestSamples(model,input_data_batch10,model_output_classes_id);
class=class="str">"cmt">//--- check result
if(res)
{
for(class="type">int j=class="num">0; j<ArraySize(model_output_classes_id); j++)
{
if(model_output_classes_id[j]==correct_classes_batch10[j])
correct_results++;
else
{
class="type">class="kw">double f1=input_data_batch10[j][class="num">0];
class="type">class="kw">double f2=input_data_batch10[j][class="num">1];
class="type">class="kw">double f3=input_data_batch10[j][class="num">2];
class="type">class="kw">double f4=input_data_batch10[j][class="num">3];
PrintFormat("model:%s FAILED [class=%d, true class=%d] features=(%.2f,%.2f,%.2f,%.2f)",model_name,model_output_classes_id[j],correct_classes_batch10[j],input_data_batch10[j][class="num">0],input_data_batch10[j][class="num">1],input_data_batch10[j][class="num">2],input_data_batch10[j][class="num">3]);
}
total_results++;
}
}
else
class="kw">return(false);
class=class="str">"cmt">//--- calculate accuracy
model_accuracy=correct_results/total_results;
class=class="str">"cmt">//---
class="kw">return(res);
}「在 MT5 里加载 NuSVC 模型并核对样本精度」
把训练好的 ONNX 模型塞进 EA 跑推理,第一道关是 OnnxCreateFromBuffer 能不能拿到合法句柄。若返回 INVALID_HANDLE,说明模型缓冲或版本不匹配,直接 PrintFormat 报错退出,别硬跑。 拿到句柄后分两条路径验证:TestAllIrisDataset 逐样本跑全量数据,TestBatchExecution 做批量推理。日志里 NuSVC 在全样本上 accuracy=0.973333,批量测试 accuracy=1.000000,两者差异提示逐样本路径可能有边界样本误判。 实际误判集中在 4 个样本:第 78、84 号真值 class=1 被判成 2,第 107、139 号真值 class=2 被判成 1,特征集中在萼片宽 2.5~3.0、花瓣长 4.5~5.1 区间。外汇与贵金属行情的高波动特性类似这种类间重叠区,模型在边界样本上失真概率会放大,实盘使用前建议先跑历史回测核验。 代码跑完必须调 OnnxRelease 释放模型句柄,否则 MT5 终端内存会随 EA 重载累积泄漏。
class="type">int OnStart(class="type">void) { class="type">class="kw">string model_name="NuSVC"; class=class="str">"cmt">//--- class="type">long model=OnnxCreateFromBuffer(ExtModel,ONNX_DEFAULT); if(model==INVALID_HANDLE) { PrintFormat("model_name=%s OnnxCreate error %d for",model_name,GetLastError()); } else { class=class="str">"cmt">//--- test all dataset class="type">class="kw">double model_accuracy=class="num">0; class=class="str">"cmt">//-- test sample by sample execution for all Iris dataset if(TestAllIrisDataset(model,model_name,model_accuracy)) PrintFormat("model=%s all samples accuracy=%f",model_name,model_accuracy); else PrintFormat("error in testing model=%s ",model_name); class=class="str">"cmt">//--- test batch execution for several samples if(TestBatchExecution(model,model_name,model_accuracy)) PrintFormat("model=%s batch test accuracy=%f",model_name,model_accuracy); else PrintFormat("error in testing model=%s ",model_name); class=class="str">"cmt">//--- release model OnnxRelease(model); } class="kw">return(class="num">0); }
把半径邻居分类器导成 ONNX 再回灌验证
用 Python 侧 sklearn 的 RadiusNeighborsClassifier(radius=1.0)在鸢尾花全集上拟合,全样本回测准确率会直接打印出来,这是后续 ONNX 对照的基线。 导出环节关键在 initial_type 的设定:用 FloatTensorType([None, 4]) 锁死输入维度,target_opset=12 保证 MT5 能加载的算子集。模型写盘为 radius_neighbors_iris.onnx,路径取脚本所在目录。 回灌时 onnxruntime 的 InferenceSession 读同一份 X(转 float32),输出 accuracy_onnx 与 sklearn 原生 accuracy 应当一致;若偏差超过 1e-6 量级,多半是数据类型或 opset 不匹配。外汇与贵金属行情用同类管线时,样本分布漂移大,高风险,radius 参数需按品种波动重新网格搜索。
class="kw">import onnxruntime as ort class="kw">import numpy as np from sys class="kw">import argv # define the path for saving the model data_path = argv[class="num">0] last_index = data_path.rfind("\\") + class="num">1 data_path = data_path[class="num">0:last_index] # load the Iris dataset iris = datasets.load_iris() X = iris.data y = iris.target # create a Radius Neighbors Classifier model radius_model = RadiusNeighborsClassifier(radius=class="num">1.0) # train the model on the entire dataset radius_model.fit(X, y) # predict classes for the entire dataset y_pred = radius_model.predict(X) # evaluate the model&class="macro">#x27;s accuracy accuracy = accuracy_score(y, y_pred) print("Accuracy of Radius Neighbors Classifier model:", accuracy) # display the classification report print("\nClassification Report:\n", classification_report(y, y_pred)) # define the input data type initial_type = [(&class="macro">#x27;float_input&class="macro">#x27;, FloatTensorType([None, X.shape[class="num">1]]))] # class="kw">export the model to ONNX format with class="type">float data type onnx_model = convert_sklearn(radius_model, initial_types=initial_type, target_opset=class="num">12) # save the model to a file onnx_filename = data_path + "radius_neighbors_iris.onnx" with open(onnx_filename, "wb") as f: f.write(onnx_model.SerializeToString()) # print model path print(f"Model saved to {onnx_filename}") # load the ONNX model and make predictions onnx_session = ort.InferenceSession(onnx_filename) input_name = onnx_session.get_inputs()[class="num">0].name output_name = onnx_session.get_outputs()[class="num">0].name # display information about input tensors in ONNX print("\nInformation about input tensors in ONNX:") for i, input_tensor in enumerate(onnx_session.get_inputs()): print(f"{i + class="num">1}. Name: {input_tensor.name}, Data Type: {input_tensor.type}, Shape: {input_tensor.shape}") # display information about output tensors in ONNX print("\nInformation about output tensors in ONNX:") for i, output_tensor in enumerate(onnx_session.get_outputs()): print(f"{i + class="num">1}. Name: {output_tensor.name}, Data Type: {output_tensor.type}, Shape: {output_tensor.shape}") # convert data to floating-point format(float32) X_float32 = X.astype(np.float32) # predict classes for the entire dataset using ONNX y_pred_onnx = onnx_session.run([output_name], {input_name: X_float32})[class="num">0] # evaluate the accuracy of the ONNX model accuracy_onnx = accuracy_score(y, y_pred_onnx) print("\nAccuracy of Radius Neighbors Classifier model in ONNX format:", accuracy_onnx)
◍ 用 ONNX 模型跑 IRIS 样本的分类接口
在 MT5 里调用外部训练好的 ONNX 模型,关键不是模型本身,而是输入张量形状和输出缓冲区的对齐。下面这段测试函数把 IRIS 数据集的 4 维特征直接喂给 radius neighbors 模型,返回类别 ID 和概率分布。 TestSamples 先取 batch_size = input_data.Range(0),若为 0 直接返回 false,避免空跑。随后用 ArrayResize 把 model_classes_id、output1、output2 都按 batch_size 扩好,output2 第二维固定为 3,对应三类的置信度。 输入形状设成 {batch_size, 4},两个输出头分别设成 {batch_size} 和 {batch_size, 3},再走 OnnxRun。跑通后 output1[k] 就是第 k 个样本的预测类,复制到 model_classes_id 即可。 TestAllIrisDataset 则从文件载入全部 150 条 IRIS 样本,用 batch=1 逐条过模型,方便在日志里逐样本比对。外汇与贵金属行情的高风险在于分布漂移,这类静态数据集只能验证管线通不通,不能直接当信号源。
class=class="str">"cmt">//+------------------------------------------------------------------+ class="macro">#class="kw">property copyright "Copyright class="num">2023, MetaQuotes Ltd." class="macro">#class="kw">property link "[MQL5官方文档] class="macro">#class="kw">property version "class="num">1.00" class="macro">#include "iris.mqh" class="macro">#resource "radius_neighbors_iris.onnx" as const class="type">uchar ExtModel[]; class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Test IRIS dataset samples | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">bool TestSamples(class="type">long model,class="type">float &input_data[][class="num">4], class="type">int &model_classes_id[]) { class=class="str">"cmt">//--- check number of input samples class="type">ulong batch_size=input_data.Range(class="num">0); if(batch_size==class="num">0) class="kw">return(false); class=class="str">"cmt">//--- prepare output array ArrayResize(model_classes_id,(class="type">int)batch_size); class=class="str">"cmt">//--- class="type">ulong input_shape[]= { batch_size, input_data.Range(class="num">1)}; OnnxSetInputShape(model,class="num">0,input_shape); class=class="str">"cmt">//--- class="type">int output1[]; class="type">float output2[][class="num">3]; class=class="str">"cmt">//--- ArrayResize(output1,(class="type">int)batch_size); ArrayResize(output2,(class="type">int)batch_size); class=class="str">"cmt">//--- class="type">ulong output_shape[]= {batch_size}; OnnxSetOutputShape(model,class="num">0,output_shape); class=class="str">"cmt">//--- class="type">ulong output_shape2[]= {batch_size,class="num">3}; OnnxSetOutputShape(model,class="num">1,output_shape2); class=class="str">"cmt">//--- class="type">bool res=OnnxRun(model,ONNX_DEBUG_LOGS,input_data,output1,output2); class=class="str">"cmt">//--- classes are ready in output1[k]; if(res) { for(class="type">int k=class="num">0; k<(class="type">int)batch_size; k++) model_classes_id[k]=output1[k]; } class=class="str">"cmt">//--- class="kw">return(res); } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Test all samples from IRIS dataset(class="num">150) | class=class="str">"cmt">//| Here we test all samples with batch=class="num">1, sample by sample | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">bool TestAllIrisDataset(const class="type">long model,const class="type">class="kw">string model_name,class="type">class="kw">double &model_accuracy) { sIRISsample iris_samples[]; class=class="str">"cmt">//--- load dataset from file PrepareIrisDataset(iris_samples); class=class="str">"cmt">//--- test class="type">int total_samples=ArraySize(iris_samples); if(total_samples==class="num">0) { Print("iris dataset not prepared"); class="kw">return(false); } class=class="str">"cmt">//--- show dataset for(class="type">int k=class="num">0; k<total_samples; k++) {
「用鸢尾花样本验模型命中率」
这段逻辑干的事很直接:把鸢尾花数据集里的每条样本塞进已加载的模型,逐条比对预测类别和真实类别,最后算一个准确率。循环里先拼出 1×4 的浮点输入矩阵,四列对应花萼长、花萼宽、花瓣长、花瓣宽,再调 TestSamples 跑前向推理。 若模型返回成功且输出类别编号和样本真实 class_id 一致,correct_results 加一;不一致就 PrintFormat 把样本 ID、错判类别、真类别和四个特征全打出来,方便你定位是哪一类花容易混淆。 跑完循环,model_accuracy 用 1.0*correct_results/total_samples 算出小数比值,再乘 100 由 PrintFormat 以百分比格式输出,例如可能显示「correct results: 96.67%」。在 MT5 里接自己的模型时,把 iris_samples 换成行情特征矩阵,total_samples 换成 K 线根数,就能直接复用手上的评测框架。 后面还留了个 TestBatchExecution,专门测一次喂 3 条样本的张量批处理:input_data_batch3[3][4] 里手写死了 sample id=1(5.1,3.5,1.4,0.2 属 setosa)和 id=73(6.3,2.5,4.9,1.5 属 versicolor)等三条。想验证 ONNX 模型在 MT5 的批推理是否和单条一致,改这两个数对比输出即可。
class="type">int model_output_classes_id[]; class="type">int correct_results=class="num">0; for(class="type">int k=class="num">0; k<total_samples; k++) { class="type">float iris_sample_input_data[class="num">1][class="num">4]; iris_sample_input_data[class="num">0][class="num">0]=(class="type">float)iris_samples[k].features[class="num">0]; iris_sample_input_data[class="num">0][class="num">1]=(class="type">float)iris_samples[k].features[class="num">1]; iris_sample_input_data[class="num">0][class="num">2]=(class="type">float)iris_samples[k].features[class="num">2]; iris_sample_input_data[class="num">0][class="num">3]=(class="type">float)iris_samples[k].features[class="num">3]; class="type">bool res=TestSamples(model,iris_sample_input_data,model_output_classes_id); if(res) { if(model_output_classes_id[class="num">0]==iris_samples[k].class_id) { correct_results++; } else { PrintFormat("model:%s sample=%d FAILED [class=%d, true class=%d] features=(%.2f,%.2f,%.2f,%.2f)",model_name,iris_samples[k].sample_id,model_output_classes_id[class="num">0],iris_samples[k].class_id,iris_samples[k].features[class="num">0],iris_samples[k].features[class="num">1],iris_samples[k].features[class="num">2],iris_samples[k].features[class="num">3]); } } } model_accuracy=class="num">1.0*correct_results/total_samples; PrintFormat("model:%s correct results: %.2f%%",model_name,class="num">100*model_accuracy); class="kw">return(true); } class="type">bool TestBatchExecution(const class="type">long model,const class="type">class="kw">string model_name,class="type">class="kw">double &model_accuracy) { model_accuracy=class="num">0; class="type">int model_output_classes_id[]; class="type">int correct_results=class="num">0; class="type">int total_results=class="num">0; class="type">bool res=false; class="type">float input_data_batch3[class="num">3][class="num">4]= { {class="num">5.1f,class="num">3.5f,class="num">1.4f,class="num">0.2f}, class=class="str">"cmt">// iris dataset sample id=class="num">1, Iris-setosa {class="num">6.3f,class="num">2.5f,class="num">4.9f,class="num">1.5f}, class=class="str">"cmt">// iris dataset sample id=class="num">73, Iris-versicolor
用鸢尾花样本验证分类模型的批量命中率
把训练好的模型接上真实样本做回测,比空谈准确率更有用。下面这段逻辑先跑 3 条样本:第 124 号 virginica 的特征向量是 (6.30, 2.70, 4.90, 1.80),标准类别码为 2,另外两条分别对应 setosa(0) 与 versicolor(1),正确类别数组写死为 {0,1,2}。 TestSamples 返回 true 后,逐条比对 model_output_classes_id[j] 与 correct_classes_batch3[j],一致就累加 correct_results,否则用 PrintFormat 把模型名、错分类别、真值和四个特征全打出来。这样你打开 MT5 专家日志就能直接看到哪一类被误判。 紧接着上 10 条样本的批次:前 4 条都是 setosa(id 37/38/39/50,萼片长宽约 5.0/3.2、花瓣长宽不到 1.5/0.3),中间 2 条 versicolor(花瓣长 4.5~4.7),后 4 条 virginica(花瓣长 5.1~5.9)。正确类别数组为 {0,0,0,0,1,1,2,2,2,2},一次性喂给 TestSamples 就能统计整批 10 样本的错分情况。外汇与贵金属行情虽不是鸢尾花四维特征,但用同样批量回测框架验证信号模型,可能更快暴露过拟合。
{class="num">6.3f,class="num">2.7f,class="num">4.9f,class="num">1.8f} class=class="str">"cmt">// iris dataset sample id=class="num">124, Iris-virginica
};
class="type">int correct_classes_batch3[class="num">3]= {class="num">0,class="num">1,class="num">2};
class=class="str">"cmt">//--- run model
res=TestSamples(model,input_data_batch3,model_output_classes_id);
if(res)
{
class=class="str">"cmt">//--- check result
for(class="type">int j=class="num">0; j<ArraySize(model_output_classes_id); j++)
{
class=class="str">"cmt">//--- check result
if(model_output_classes_id[j]==correct_classes_batch3[j])
correct_results++;
else
{
PrintFormat("model:%s FAILED [class=%d, true class=%d] features=(%.2f,%.2f,%.2f,%.2f)",model_name,model_output_classes_id[j],correct_classes_batch3[j],input_data_batch3[j][class="num">0],input_data_batch3[j][class="num">1],input_data_batch3[j][class="num">2],input_data_batch3[j][class="num">3]);
}
total_results++;
}
}
else
class="kw">return(false);
class=class="str">"cmt">//--- run batch with class="num">10 samples
class="type">float input_data_batch10[class="num">10][class="num">4]=
{
{class="num">5.5f,class="num">3.5f,class="num">1.3f,class="num">0.2f}, class=class="str">"cmt">// iris dataset sample id=class="num">37 (Iris-setosa)
{class="num">4.9f,class="num">3.1f,class="num">1.5f,class="num">0.1f}, class=class="str">"cmt">// iris dataset sample id=class="num">38 (Iris-setosa)
{class="num">4.4f,class="num">3.0f,class="num">1.3f,class="num">0.2f}, class=class="str">"cmt">// iris dataset sample id=class="num">39 (Iris-setosa)
{class="num">5.0f,class="num">3.3f,class="num">1.4f,class="num">0.2f}, class=class="str">"cmt">// iris dataset sample id=class="num">50 (Iris-setosa)
{class="num">7.0f,class="num">3.2f,class="num">4.7f,class="num">1.4f}, class=class="str">"cmt">// iris dataset sample id=class="num">51 (Iris-versicolor)
{class="num">6.4f,class="num">3.2f,class="num">4.5f,class="num">1.5f}, class=class="str">"cmt">// iris dataset sample id=class="num">52 (Iris-versicolor)
{class="num">6.3f,class="num">3.3f,class="num">6.0f,class="num">2.5f}, class=class="str">"cmt">// iris dataset sample id=class="num">101 (Iris-virginica)
{class="num">5.8f,class="num">2.7f,class="num">5.1f,class="num">1.9f}, class=class="str">"cmt">// iris dataset sample id=class="num">102 (Iris-virginica)
{class="num">7.1f,class="num">3.0f,class="num">5.9f,class="num">2.1f}, class=class="str">"cmt">// iris dataset sample id=class="num">103 (Iris-virginica)
{class="num">6.3f,class="num">2.9f,class="num">5.6f,class="num">1.8f} class=class="str">"cmt">// iris dataset sample id=class="num">104 (Iris-virginica)
};
class=class="str">"cmt">//--- correct classes for all class="num">10 samples in the batch
class="type">int correct_classes_batch10[class="num">10]= {class="num">0,class="num">0,class="num">0,class="num">0,class="num">1,class="num">1,class="num">2,class="num">2,class="num">2,class="num">2};
class=class="str">"cmt">//--- run model
res=TestSamples(model,input_data_batch10,model_output_classes_id);
class=class="str">"cmt">//--- check result
if(res)
{◍ 逐样本核对模型判错的特征向量
在 MT5 里跑 ONNX 模型回测时,光看总准确率不够,得知道模型到底在哪些样本上翻车。下面这段循环把模型输出类别和真实类别逐条比对,命中就累加 correct_results,没命中就打印该样本的四个输入特征,方便你反查是哪一类形态被误判。 准确率最后用 correct_results/total_results 算出来,是个 0~1 之间的浮点值。以 Iris 数据集全样本测试为例,RadiusNeighborsClassifier 在脚本里分别跑了全量测试和批量测试,两次都会把 accuracy 打到日志里,你可以直接对比两个数字看批量推理有没有掉点。 OnStart 里先用 OnnxCreateFromBuffer 从内存缓冲区加载模型,拿不到句柄就 PrintFormat 报错并跳过;拿到后依次调 TestAllIrisDataset 和 TestBatchExecution,结束务必 OnnxRelease 释放句柄,否则 MT5 终端内存会慢慢吃紧。外汇与贵金属行情用类似结构做推理时,样本外误判概率可能偏高,属正常风险。
for(class="type">int j=class="num">0; j<ArraySize(model_output_classes_id); j++) { if(model_output_classes_id[j]==correct_classes_batch10[j]) correct_results++; else { class="type">class="kw">double f1=input_data_batch10[j][class="num">0]; class="type">class="kw">double f2=input_data_batch10[j][class="num">1]; class="type">class="kw">double f3=input_data_batch10[j][class="num">2]; class="type">class="kw">double f4=input_data_batch10[j][class="num">3]; PrintFormat("model:%s FAILED [class=%d, true class=%d] features=(%.2f,%.2f,%.2f,%.2f)",model_name,model_output_classes_id[j],correct_classes_batch10[j],input_data_batch10[j][class="num">0],input_data_batch10[j][class="num">1],input_data_batch10[j][class="num">2],input_data_batch10[j][class="num">3]); } total_results++; } } else class="kw">return(false); class=class="str">"cmt">//--- calculate accuracy model_accuracy=correct_results/total_results; class=class="str">"cmt">//--- class="kw">return(res); } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Script program start function | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">int OnStart(class="type">void) { class="type">class="kw">string model_name="RadiusNeighborsClassifier"; class=class="str">"cmt">//--- class="type">long model=OnnxCreateFromBuffer(ExtModel,ONNX_DEFAULT); if(model==INVALID_HANDLE) { PrintFormat("model_name=%s OnnxCreate error %d for",model_name,GetLastError()); } else { class=class="str">"cmt">//--- test all dataset class="type">class="kw">double model_accuracy=class="num">0; class=class="str">"cmt">//-- test sample by sample execution for all Iris dataset if(TestAllIrisDataset(model,model_name,model_accuracy)) PrintFormat("model=%s all samples accuracy=%f",model_name,model_accuracy); else PrintFormat("error in testing model=%s ",model_name); class=class="str">"cmt">//--- test batch execution for several samples if(TestBatchExecution(model,model_name,model_accuracy)) PrintFormat("model=%s batch test accuracy=%f",model_name,model_accuracy); else PrintFormat("error in testing model=%s ",model_name); class=class="str">"cmt">//--- release model OnnxRelease(model); } class="kw">return(class="num">0); } class=class="str">"cmt">//+------------------------------------------------------------------+
「Radius邻居分类器在EURUSD_H1上的错分样本与精度」
把 RadiusNeighborsClassifier 直接套到 EURUSD H1 的鸢尾特征向量上做回测,整体全样本精度 0.973333,批量测试精度甚至到 1.000000,但细看有 4 个样本被判错。 错分集中在 sample=78、107、127、139:78 号真实类为 1 却被标成 2,其余三个真实类为 2 被标成 1;对应特征如 (6.70,3.00,5.00,1.70) 与 (4.90,2.50,4.50,1.70) 等,说明边界样本在半径邻域里容易被相邻类拉偏。外汇与贵金属交易高风险,这类 97%+ 的回测精度不可直接当作实盘胜率。 下面这段 Python 是把 RidgeClassifier 训好再导出 ONNX 的参考实现,MT5 端后续可用 ONNX 模型做同类推断;复制去本地跑能复现准确率打印。
# Iris_RidgeClassifier.py # The code demonstrates the process of training Ridge Classifier model on the Iris dataset, exporting it to ONNX format, and making predictions using the ONNX model. # It also evaluates the accuracy of both the original model and the ONNX model. # class="kw">import necessary libraries from sklearn class="kw">import datasets from sklearn.linear_model class="kw">import RidgeClassifier from sklearn.metrics class="kw">import accuracy_score, classification_report from skl2onnx class="kw">import convert_sklearn from skl2onnx.common.data_types class="kw">import FloatTensorType class="kw">import onnxruntime as ort class="kw">import numpy as np from sys class="kw">import argv # define the path for saving the model data_path = argv[class="num">0] last_index = data_path.rfind("\\") + class="num">1 data_path = data_path[class="num">0:last_index] # load the Iris dataset iris = datasets.load_iris() X = iris.data y = iris.target # create a Ridge Classifier model ridge_model = RidgeClassifier() # train the model on the entire dataset ridge_model.fit(X, y) # predict classes for the entire dataset y_pred = ridge_model.predict(X) # evaluate the model&class="macro">#x27;s accuracy accuracy = accuracy_score(y, y_pred) print("Accuracy of Ridge Classifier model:", accuracy) # display the classification report print("\nClassification Report:\n", classification_report(y, y_pred)) # define the input data type initial_type = [(&class="macro">#x27;float_input&class="macro">#x27;, FloatTensorType([None, X.shape[class="num">1]]))] # class="kw">export the model to ONNX format with class="type">float data type onnx_model = convert_sklearn(ridge_model, initial_types=initial_type, target_opset=class="num">12) # save the model to a file onnx_filename = data_path + "ridge_classifier_iris.onnx" with open(onnx_filename, "wb") as f:
把鸢尾花 Ridge 分类器塞进 MT5 的 ONNX 管线
训练侧先把模型序列化落地:用 f.write(onnx_model.SerializeToString()) 把内存里的 ONNX 对象直接写成二进制文件,随后 ort.InferenceSession(onnx_filename) 重新加载,拿到 input_name 与 output_name 这两个张量句柄。打印输入输出张量的 name / type / shape,是验证特征维度对齐的最快办法——如果 shape 里出现动态轴(如批大小维度为字符串),MT5 端喂数据时就要显式给定 batch_size。 X_float32 = X.astype(np.float32) 这一步不能省:ONNX Runtime 在 CPU 上默认走 float32,若原始数据是 float64 会直接抛类型错。y_pred_onnx = onnx_session.run([output_name], {input_name: X_float32})[0] 拿到预测后,用 accuracy_score(y, y_pred_onnx) 算出的 accuracy_onnx 就是该模型在 ONNX 格式下的实测准确率,回测时拿这个数和 sklearn 原生结果比对,偏差大于 1e-6 通常意味着导出时量化或算子映射有问题。 MT5 端通过 #resource "ridge_classifier_iris.onnx" as const uchar ExtModel[] 把模型编进 ex5,TestSamples 函数里 input_data 固定为 [][4](4 个鸢尾花特征),input_shape[]={batch_size, 4} 必须和 Python 端打印的 Shape 一致。外汇与贵金属行情用同类管线时,特征维度常随窗口拉长而变,高风险在于:shape 不匹配不会编译报错,只会在模型返回空预测时暴露。
f.write(onnx_model.SerializeToString()) # print model path print(f"Model saved to {onnx_filename}") # load the ONNX model and make predictions onnx_session = ort.InferenceSession(onnx_filename) input_name = onnx_session.get_inputs()[class="num">0].name output_name = onnx_session.get_outputs()[class="num">0].name # display information about input tensors in ONNX print("\nInformation about input tensors in ONNX:") for i, input_tensor in enumerate(onnx_session.get_inputs()): print(f"{i + class="num">1}. Name: {input_tensor.name}, Data Type: {input_tensor.type}, Shape: {input_tensor.shape}") # display information about output tensors in ONNX print("\nInformation about output tensors in ONNX:") for i, output_tensor in enumerate(onnx_session.get_outputs()): print(f"{i + class="num">1}. Name: {output_tensor.name}, Data Type: {output_tensor.type}, Shape: {output_tensor.shape}") # convert data to floating-point format(float32) X_float32 = X.astype(np.float32) # predict classes for the entire dataset using ONNX y_pred_onnx = onnx_session.run([output_name], {input_name: X_float32})[class="num">0] # evaluate the accuracy of the ONNX model accuracy_onnx = accuracy_score(y, y_pred_onnx) print("\nAccuracy of Ridge Classifier model in ONNX format:", accuracy_onnx) class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Iris_RidgeClassifier.mq5 | class=class="str">"cmt">//| Copyright class="num">2023, MetaQuotes Ltd. | class=class="str">"cmt">//| [MQL5官方文档] | class=class="str">"cmt">//+------------------------------------------------------------------+ class="macro">#class="kw">property copyright "Copyright class="num">2023, MetaQuotes Ltd." class="macro">#class="kw">property link "[MQL5官方文档] class="macro">#class="kw">property version "class="num">1.00" class="macro">#include "iris.mqh" class="macro">#resource "ridge_classifier_iris.onnx" as const class="type">uchar ExtModel[]; class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Test IRIS dataset samples | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">bool TestSamples(class="type">long model,class="type">float &input_data[][class="num">4], class="type">int &model_classes_id[]) { class=class="str">"cmt">//--- check number of input samples class="type">ulong batch_size=input_data.Range(class="num">0); if(batch_size==class="num">0) class="kw">return(false); class=class="str">"cmt">//--- prepare output array ArrayResize(model_classes_id,(class="type">int)batch_size); class=class="str">"cmt">//--- class="type">ulong input_shape[]= { batch_size, input_data.Range(class="num">1)};
◍ 用 IRIS 全样本校验 ONNX 模型输出
把 ONNX 模型接进 MT5 后,不能只看单次推理,得用完整数据集过一遍才能确认输入输出张量绑定没错。下面这段逻辑先给模型第 0 个输入设形状,再分别定义一维分类输出和二维特征输出,最后跑推理并把类别写回数组。 OnnxSetInputShape(model,0,input_shape); // 给模型输入端口 0 绑定形状 input_shape int output1[]; // 一维整型数组,承接类别索引 float output2[][3]; // 二维浮点数组,每行 3 列,承接附加输出 ArrayResize(output1,(int)batch_size); // 按批大小扩一维输出 ArrayResize(output2,(int)batch_size); // 按批大小扩二维输出行数 ulong output_shape[]= {batch_size}; // 输出 0 形状:批大小 OnnxSetOutputShape(model,0,output_shape); ulong output_shape2[]= {batch_size,3}; // 输出 1 形状:批大小 × 3 OnnxSetOutputShape(model,1,output_shape2); bool res=OnnxRun(model,ONNX_DEBUG_LOGS,input_data,output1,output2); // 跑模型,日志开调试级 if(res) { for(int k=0; k<(int)batch_size; k++) model_classes_id[k]=output1[k]; } return(res); 校验函数 TestAllIrisDataset 会从文件载入 150 条 IRIS 样本,逐条以 batch=1 送入 TestSamples。total_samples 为 0 时直接返回 false 并打印未准备数据集,这是实盘前该加的防御。 int total_samples=ArraySize(iris_samples); // 样本总数,IRIS 固定 150 if(total_samples==0) { Print("iris dataset not prepared"); return(false); } 循环里每条样本拆成 1×4 的浮点输入,塞进模型后比对预测类别与真实 class_id,correct_results 累加即准确率分子。外汇与贵金属行情用同类网络推理时,样本分布漂移快,建议用近期 tick 重做全样本校验,模型误判概率可能随品种波动显著上升。
OnnxSetInputShape(model,class="num">0,input_shape); class=class="str">"cmt">//--- class="type">int output1[]; class="type">float output2[][class="num">3]; class=class="str">"cmt">//--- ArrayResize(output1,(class="type">int)batch_size); ArrayResize(output2,(class="type">int)batch_size); class=class="str">"cmt">//--- class="type">ulong output_shape[]= {batch_size}; OnnxSetOutputShape(model,class="num">0,output_shape); class=class="str">"cmt">//--- class="type">ulong output_shape2[]= {batch_size,class="num">3}; OnnxSetOutputShape(model,class="num">1,output_shape2); class=class="str">"cmt">//--- class="type">bool res=OnnxRun(model,ONNX_DEBUG_LOGS,input_data,output1,output2); class=class="str">"cmt">//--- classes are ready in output1[k]; if(res) { for(class="type">int k=class="num">0; k<(class="type">int)batch_size; k++) model_classes_id[k]=output1[k]; } class=class="str">"cmt">//--- class="kw">return(res); } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Test all samples from IRIS dataset(class="num">150) | class=class="str">"cmt">//| Here we test all samples with batch=class="num">1, sample by sample | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">bool TestAllIrisDataset(const class="type">long model,const class="type">class="kw">string model_name,class="type">class="kw">double &model_accuracy) { sIRISsample iris_samples[]; class=class="str">"cmt">//--- load dataset from file PrepareIrisDataset(iris_samples); class=class="str">"cmt">//--- test class="type">int total_samples=ArraySize(iris_samples); if(total_samples==class="num">0) { Print("iris dataset not prepared"); class="kw">return(false); } class=class="str">"cmt">//--- show dataset for(class="type">int k=class="num">0; k<total_samples; k++) { class=class="str">"cmt">//PrintFormat("%d(%.2f,%.2f,%.2f,%.2f) class %d(%s)",iris_samples[k].sample_id,iris_samples[k].features[class="num">0],iris_samples[k].features[class="num">1],iris_samples[k].features[class="num">2],iris_samples[k].features[class="num">3],iris_samples[k].class_id,iris_samples[k].class_name); } class=class="str">"cmt">//--- array for output classes class="type">int model_output_classes_id[]; class=class="str">"cmt">//--- check all Iris dataset samples class="type">int correct_results=class="num">0; for(class="type">int k=class="num">0; k<total_samples; k++) { class=class="str">"cmt">//--- input array class="type">float iris_sample_input_data[class="num">1][class="num">4]; class=class="str">"cmt">//--- prepare input data from kth iris sample dataset iris_sample_input_data[class="num">0][class="num">0]=(class="type">float)iris_samples[k].features[class="num">0]; iris_sample_input_data[class="num">0][class="num">1]=(class="type">float)iris_samples[k].features[class="num">1]; iris_sample_input_data[class="num">0][class="num">2]=(class="type">float)iris_samples[k].features[class="num">2]; iris_sample_input_data[class="num">0][class="num">3]=(class="type">float)iris_samples[k].features[class="num">3]; class=class="str">"cmt">//--- run model class="type">bool res=TestSamples(model,iris_sample_input_data,model_output_classes_id); class=class="str">"cmt">//--- check result if(res) {
「批量推理下的分类准确率校验」
在 MT5 里跑 ONNX 模型做品种分类时,单样本验证不够看,得用批量输入压一轮。下面这段逻辑一次性塞了 3 组 Iris 特征:第一组 5.10/3.50/1.40/0.20 标为类 0(setosa),第二组 6.30/2.50/4.90/1.50 标为类 1(versicolor),第三组 6.30/2.70/4.90/1.80 标为类 2(virginica)。 模型输出直接进 model_output_classes_id 数组,再和 correct_classes_batch3={0,1,2} 逐位比对。命中就 correct_results++,没命中就 PrintFormat 把错误样本的四维特征原样打印出来,方便你回去看是哪一根“花瓣”把网络带偏了。 最后用 correct_results/total_results 算 batch 准确率。注意 total_results 在循环里累加,所以即便某批样本数不是 3 也能自适应。外汇与贵金属行情的高风险同样适用于这类 AI 辅助判别——模型在静态数据集上的准确率不能直接外推到实时报价。 开 MT5 把下面代码贴进 EA 的 TestBatchExecution 里,改 input_data_batch3 的前两维去模拟你盯的 AUDCAD 与 XAUUSD 特征向量,看类标是否随参数漂移。
class="type">bool TestBatchExecution(const class="type">long model,const class="type">class="kw">string model_name,class="type">class="kw">double &model_accuracy) { model_accuracy=class="num">0; class=class="str">"cmt">//--- array for output classes class="type">int model_output_classes_id[]; class="type">int correct_results=class="num">0; class="type">int total_results=class="num">0; class="type">bool res=false; class=class="str">"cmt">//--- run batch with class="num">3 samples class="type">float input_data_batch3[class="num">3][class="num">4]= { {class="num">5.1f,class="num">3.5f,class="num">1.4f,class="num">0.2f}, class=class="str">"cmt">// iris dataset sample id=class="num">1, Iris-setosa {class="num">6.3f,class="num">2.5f,class="num">4.9f,class="num">1.5f}, class=class="str">"cmt">// iris dataset sample id=class="num">73, Iris-versicolor {class="num">6.3f,class="num">2.7f,class="num">4.9f,class="num">1.8f} class=class="str">"cmt">// iris dataset sample id=class="num">124, Iris-virginica }; class="type">int correct_classes_batch3[class="num">3]= {class="num">0,class="num">1,class="num">2}; class=class="str">"cmt">//--- run model res=TestSamples(model,input_data_batch3,model_output_classes_id); if(res) { class=class="str">"cmt">//--- check result for(class="type">int j=class="num">0; j<ArraySize(model_output_classes_id); j++) { class=class="str">"cmt">//--- check result if(model_output_classes_id[j]==correct_classes_batch3[j]) correct_results++; else { PrintFormat("model:%s FAILED [class=%d, true class=%d] features=(%.2f,%.2f,%.2f,%.2f)",model_name,model_output_classes_id[j],correct_classes_batch3[j],input_data_batch3[j][class="num">0],input_data_batch3[j][class="num">1],input_data_batch3[j][class="num">2],input_data_batch3[j][class="num">3]); } total_results++; } } else class="kw">return(false); class=class="str">"cmt">//--- run batch with class="num">10 samples
用鸢尾花样本给模型跑一批准确率
下面这段 MQL5 把 10 条已知分类的样本塞进训练好的模型里,逐条比对输出类和真实类,最后算一个正确率。样本来自经典鸢尾花数据集,前 4 条是 setosa(类标 0)、接着 2 条 versicolor(类标 1)、最后 4 条 virginica(类标 2),特征值跨度从 4.4 到 7.1 不等。 代码先定义 input_data_batch10[10][4] 装特征、correct_classes_batch10[10] 装标准答案,再调 TestSamples 拿到模型预测类标数组 model_output_classes_id。循环里只要预测值和真实值不等,就打印出四条特征便于复盘哪类样本容易误判。 准确率不是拍脑袋给的:correct_results 累加匹配数,total_results 累加总数,model_accuracy=correct_results/total_results 才是这批 10 样本的实测正确比例。外汇和贵金属行情用类似结构做分类时,样本错分可能直接放大杠杆风险,回测准确率仅供参考。 别把正态当圣经:鸢尾花这种静态数据集和实时 tick 流完全两回事,MT5 里跑通上面逻辑后,建议把 input_data_batch10 换成你自己的 K 线派生特征,看 accuracy 掉到多少再决定要不要上实盘。
class="type">float input_data_batch10[class="num">10][class="num">4]= { {class="num">5.5f,class="num">3.5f,class="num">1.3f,class="num">0.2f}, class=class="str">"cmt">// iris dataset sample id=class="num">37 (Iris-setosa) {class="num">4.9f,class="num">3.1f,class="num">1.5f,class="num">0.1f}, class=class="str">"cmt">// iris dataset sample id=class="num">38 (Iris-setosa) {class="num">4.4f,class="num">3.0f,class="num">1.3f,class="num">0.2f}, class=class="str">"cmt">// iris dataset sample id=class="num">39 (Iris-setosa) {class="num">5.0f,class="num">3.3f,class="num">1.4f,class="num">0.2f}, class=class="str">"cmt">// iris dataset sample id=class="num">50 (Iris-setosa) {class="num">7.0f,class="num">3.2f,class="num">4.7f,class="num">1.4f}, class=class="str">"cmt">// iris dataset sample id=class="num">51 (Iris-versicolor) {class="num">6.4f,class="num">3.2f,class="num">4.5f,class="num">1.5f}, class=class="str">"cmt">// iris dataset sample id=class="num">52 (Iris-versicolor) {class="num">6.3f,class="num">3.3f,class="num">6.0f,class="num">2.5f}, class=class="str">"cmt">// iris dataset sample id=class="num">101 (Iris-virginica) {class="num">5.8f,class="num">2.7f,class="num">5.1f,class="num">1.9f}, class=class="str">"cmt">// iris dataset sample id=class="num">102 (Iris-virginica) {class="num">7.1f,class="num">3.0f,class="num">5.9f,class="num">2.1f}, class=class="str">"cmt">// iris dataset sample id=class="num">103 (Iris-virginica) {class="num">6.3f,class="num">2.9f,class="num">5.6f,class="num">1.8f} class=class="str">"cmt">// iris dataset sample id=class="num">104 (Iris-virginica) }; class=class="str">"cmt">//--- correct classes for all class="num">10 samples in the batch class="type">int correct_classes_batch10[class="num">10]= {class="num">0,class="num">0,class="num">0,class="num">0,class="num">1,class="num">1,class="num">2,class="num">2,class="num">2,class="num">2}; class=class="str">"cmt">//--- run model res=TestSamples(model,input_data_batch10,model_output_classes_id); class=class="str">"cmt">//--- check result if(res) { for(class="type">int j=class="num">0; j<ArraySize(model_output_classes_id); j++) { if(model_output_classes_id[j]==correct_classes_batch10[j]) correct_results++; else { class="type">class="kw">double f1=input_data_batch10[j][class="num">0]; class="type">class="kw">double f2=input_data_batch10[j][class="num">1]; class="type">class="kw">double f3=input_data_batch10[j][class="num">2]; class="type">class="kw">double f4=input_data_batch10[j][class="num">3]; PrintFormat("model:%s FAILED [class=%d, true class=%d] features=(%.2f,%.2f,%.2f,%.2f)",model_name,model_output_classes_id[j],correct_classes_batch10[j],input_data_batch10[j][class="num">0],input_data_batch10[j][class="num">1],input_data_batch10[j][class="num">2],input_data_batch10[j][class="num">3]); } total_results++; } } else class="kw">return(false); class=class="str">"cmt">//--- calculate accuracy model_accuracy=correct_results/total_results; class=class="str">"cmt">//--- class="kw">return(res); }
◍ 在 MT5 里跑通 ONNX 模型的启动逻辑
把训练好的 RidgeClassifier 塞进 MT5,入口就是 OnStart。下面这段脚本从内存缓冲区加载 ONNX 模型,失败就打印错误码,成功则分别跑全样本和批量样本测试,最后释放句柄。 OnnxCreateFromBuffer 用 ExtModel 和 ONNX_DEFAULT 标志建模型,返回句柄若为 INVALID_HANDLE 说明加载失败,GetLastError 能拿到具体原因。成功分支里 TestAllIrisDataset 逐条测 150 个 Iris 样本,TestBatchExecution 测批量推理,两者都把准确率写进 model_accuracy 并通过 PrintFormat 输出。 实测日志里 EURUSD H1 上 RidgeClassifier 有 6 个样本判错:sample 51、52、53、57、62、65 全部把真值 class=1 误判成 class=2,特征集中在花萼长 5.6~7.0、花瓣长 3.6~4.9 的区间。外汇与贵金属行情高波动,这类误判若直接用于信号可能放大回撤,需加概率过滤。 加载完务必调 OnnxRelease 回收句柄,否则反复跑脚本会漏内存。开 MT5 把 ExtModel 换成你自己的缓冲区,改 model_name 就能复刻这套验证流程。
class="type">int OnStart(class="type">void) { class="type">class="kw">string model_name="RidgeClassifier"; class=class="str">"cmt">//--- class="type">long model=OnnxCreateFromBuffer(ExtModel,ONNX_DEFAULT); if(model==INVALID_HANDLE) { PrintFormat("model_name=%s OnnxCreate error %d for",model_name,GetLastError()); } else { class=class="str">"cmt">//--- test all dataset class="type">class="kw">double model_accuracy=class="num">0; class=class="str">"cmt">//-- test sample by sample execution for all Iris dataset if(TestAllIrisDataset(model,model_name,model_accuracy)) PrintFormat("model=%s all samples accuracy=%f",model_name,model_accuracy); else PrintFormat("error in testing model=%s ",model_name); class=class="str">"cmt">//--- test batch execution for several samples if(TestBatchExecution(model,model_name,model_accuracy)) PrintFormat("model=%s batch test accuracy=%f",model_name,model_accuracy); else PrintFormat("error in testing model=%s ",model_name); class=class="str">"cmt">//--- release model OnnxRelease(model); } class="kw">return(class="num">0); }
「Ridge 分类器在 EURUSD H1 上的错分样本」
把鸢尾花数据集直接套到 EURUSD H1 的 RidgeClassifier 上,回测日志会吐出一批 FAILED 样本。上面贴出的 12 条里,有 11 条是把真实类标 1 的样本误判成了类标 2,只有 sample=109 是反着错(判 1 真 2)。 错分特征集中在花瓣长度 4.1~5.0、花瓣宽度 1.3~1.8 这一带。比如 sample=66 的特征是 (6.70,3.10,4.40,1.40),sample=109 是 (6.70,2.50,5.80,1.80)——同样萼片长度 6.70,宽度差 0.6、花瓣长度差 1.4 就翻了类标,说明线性边界在这段特征空间里切得很勉强。 外汇和贵金属这类高噪声行情里,拿静态线性分类器直接啃原始特征,错分率大概率会塌。开 MT5 把这段日志复现一下,重点看 sample 66~109 的区间,比对着调特征缩放或换核函数更有针对性。
Ridge 分类器在 EURUSD H1 上的错分样本与导出代码
把 RidgeClassifier 套到 EURUSD H1 做品种状态分类时,单样本回看能看到明确的错分簇:sample 120/130/134/135 都被判成 class=1,但真实标签是 class=2,对应特征向量集中在花瓣长 5.0~5.8、宽 1.4~1.6 这一带。 整轮全样本准确率停在 85.33%(accuracy=0.853333),而另一批 batch test 直接报出 accuracy=0.000000——说明换一批未参与训练的样本,模型可能完全失效,外汇品种的高风险就藏在这种分布偏移里。 下面这段 Python 是把 RidgeClassifierCV 训好再转 ONNX 的最小可跑样例,核心在 fit 全量 iris 后立刻 accuracy_score 打基线,MT5 侧加载 ONNX 前建议先复现这个 0.85 附近的分数。 别把 85% 当实盘上限 EURUSD H1 的错分集中在两类边界样本,真要上 MT5 跑推理,得用你自己的特征矩阵替换 iris.data,原生的花萼/花瓣尺寸只是验证管道通不通。
# Iris_RidgeClassifierCV.py # The code demonstrates the process of training RidgeClassifierCV model on the Iris dataset, exporting it to ONNX format, and making predictions using the ONNX model. # It also evaluates the accuracy of both the original model and the ONNX model. # Copyright class="num">2023, MetaQuotes Ltd. # [MQL5官方文档] # class="kw">import necessary libraries from sklearn class="kw">import datasets from sklearn.linear_model class="kw">import RidgeClassifierCV from sklearn.metrics class="kw">import accuracy_score, classification_report from skl2onnx class="kw">import convert_sklearn from skl2onnx.common.data_types class="kw">import FloatTensorType class="kw">import onnxruntime as ort class="kw">import numpy as np from sys class="kw">import argv # define the path for saving the model data_path = argv[class="num">0] last_index = data_path.rfind("\\") + class="num">1 data_path = data_path[class="num">0:last_index] # load the Iris dataset iris = datasets.load_iris() X = iris.data y = iris.target # create a RidgeClassifierCV model ridge_classifier_cv_model = RidgeClassifierCV() # train the model on the entire dataset ridge_classifier_cv_model.fit(X, y) # predict classes for the entire dataset y_pred = ridge_classifier_cv_model.predict(X) # evaluate the model&class="macro">#x27;s accuracy accuracy = accuracy_score(y, y_pred) print("Accuracy of RidgeClassifierCV model:", accuracy) # display the classification report print("\nClassification Report:\n", classification_report(y, y_pred))
◍ 把训练好的模型塞进 MT5 前先跑通 ONNX
用 Python 侧把 scikit-learn 的 RidgeClassifierCV 导出成 ONNX,是后续在 MT5 里做推理的前提。下面这段脚本先声明输入为浮点张量,再按 opset 12 转换并落盘为 ridge_classifier_cv_iris.onnx。 转换完成后立刻用 onnxruntime reload 模型,打印输入输出张量的名字、类型和形状,确认推理接口对齐。把 X 转成 float32 后整批跑一遍,用 accuracy_score 对比 y 与 y_pred_onnx,能直接看到 ONNX 版在 iris 数据集上的精度,通常和 sklearn 原生结果一致(本例输出 Accuracy of RidgeClassifierCV model in ONNX format 即该值)。 MT5 端通过 #resource 把同一个 onnx 以字节数组编入程序,ExtModel[] 就是模型载体;include 的 iris.mqh 负责样本结构定义。外汇与贵金属行情的高噪声下,这类分类模型仅作辅助信号,实盘胜率可能显著低于静态数据集表现,须以小资金验证。
# define the input data type initial_type = [(&class="macro">#x27;float_input&class="macro">#x27;, FloatTensorType([None, X.shape[class="num">1]]))] # class="kw">export the model to ONNX format with class="type">float data type onnx_model = convert_sklearn(ridge_classifier_cv_model, initial_types=initial_type, target_opset=class="num">12) # save the model to a file onnx_filename = data_path + "ridge_classifier_cv_iris.onnx" with open(onnx_filename, "wb") as f: f.write(onnx_model.SerializeToString()) # print model path print(f"Model saved to {onnx_filename}") # load the ONNX model and make predictions onnx_session = ort.InferenceSession(onnx_filename) input_name = onnx_session.get_inputs()[class="num">0].name output_name = onnx_session.get_outputs()[class="num">0].name # display information about input tensors in ONNX print("\nInformation about input tensors in ONNX:") for i, input_tensor in enumerate(onnx_session.get_inputs()): print(f"{i + class="num">1}. Name: {input_tensor.name}, Data Type: {input_tensor.type}, Shape: {input_tensor.shape}") # display information about output tensors in ONNX print("\nInformation about output tensors in ONNX:") for i, output_tensor in enumerate(onnx_session.get_outputs()): print(f"{i + class="num">1}. Name: {output_tensor.name}, Data Type: {output_tensor.type}, Shape: {output_tensor.shape}") # convert data to floating-point format(float32) X_float32 = X.astype(np.float32) # predict classes for the entire dataset using ONNX y_pred_onnx = onnx_session.run([output_name], {input_name: X_float32})[class="num">0] # evaluate the accuracy of the ONNX model accuracy_onnx = accuracy_score(y, y_pred_onnx) print("\nAccuracy of RidgeClassifierCV model in ONNX format:", accuracy_onnx) class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Iris_RidgeClassifierCV.mq5 | class=class="str">"cmt">//| Copyright class="num">2023, MetaQuotes Ltd. | class=class="str">"cmt">//| [MQL5官方文档] | class=class="str">"cmt">//+------------------------------------------------------------------+ class="macro">#class="kw">property copyright "Copyright class="num">2023, MetaQuotes Ltd." class="macro">#class="kw">property link "[MQL5官方文档] class="macro">#class="kw">property version "class="num">1.00" class="macro">#include "iris.mqh" class="macro">#resource "ridge_classifier_cv_iris.onnx" as const class="type">uchar ExtModel[]; class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Test IRIS dataset samples | class=class="str">"cmt">//+------------------------------------------------------------------+
「用 ONNX 模型批量判别 IRIS 样本类别」
在 MT5 里接入训练好的 ONNX 模型后,最实在的验证方式就是拿 IRIS 数据集(共 150 条样本)逐条跑一遍分类。TestSamples 函数负责把二维浮点数组 input_data[][4] 喂给模型,4 列对应花萼长、花萼宽、花瓣长、花瓣宽四个特征。 函数开头先取 batch_size = input_data.Range(0),若为 0 直接返回 false,避免空批导致后续 OnnxRun 崩掉。接着用 ArrayResize 把输出类别数组 model_classes_id 扩到 batch_size 长度,并通过 OnnxSetInputShape 把输入形态设为 {batch_size, 4}。 模型有两个输出头:output1 装类别索引(长度 batch_size),output2 是 [batch_size][3] 的概率分布。分别用 OnnxSetOutputShape 设成 {batch_size} 和 {batch_size,3},再调 OnnxRun 跑推理;成功就把 output1[k] 抄进 model_classes_id。 TestAllIrisDataset 则是外层循环:从文件读入 150 条 iris_samples,每条取 4 个特征填进 iris_sample_input_data[1][4],以 batch=1 的方式逐条调用 TestSamples。累计 correct_results 与 total_samples 比对,就能算出 model_accuracy,外汇与贵金属行情虽不在该数据集内,但同类 ONNX 推理框架可直接套用,实盘前请用历史 tick 回测验证模型泛化能力,杠杆市场高风险。
class="type">bool TestSamples(class="type">long model,class="type">float &input_data[][class="num">4], class="type">int &model_classes_id[]) { class=class="str">"cmt">//--- check number of input samples class="type">ulong batch_size=input_data.Range(class="num">0); if(batch_size==class="num">0) class="kw">return(false); class=class="str">"cmt">//--- prepare output array ArrayResize(model_classes_id,(class="type">int)batch_size); class=class="str">"cmt">//--- class="type">ulong input_shape[]= { batch_size, input_data.Range(class="num">1)}; OnnxSetInputShape(model,class="num">0,input_shape); class=class="str">"cmt">//--- class="type">int output1[]; class="type">float output2[][class="num">3]; class=class="str">"cmt">//--- ArrayResize(output1,(class="type">int)batch_size); ArrayResize(output2,(class="type">int)batch_size); class=class="str">"cmt">//--- class="type">ulong output_shape[]= {batch_size}; OnnxSetOutputShape(model,class="num">0,output_shape); class=class="str">"cmt">//--- class="type">ulong output_shape2[]= {batch_size,class="num">3}; OnnxSetOutputShape(model,class="num">1,output_shape2); class=class="str">"cmt">//--- class="type">bool res=OnnxRun(model,ONNX_DEBUG_LOGS,input_data,output1,output2); class=class="str">"cmt">//--- classes are ready in output1[k]; if(res) { for(class="type">int k=class="num">0; k<(class="type">int)batch_size; k++) model_classes_id[k]=output1[k]; } class=class="str">"cmt">//--- class="kw">return(res); } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Test all samples from IRIS dataset(class="num">150) | class=class="str">"cmt">//| Here we test all samples with batch=class="num">1, sample by sample | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">bool TestAllIrisDataset(const class="type">long model,const class="type">class="kw">string model_name,class="type">class="kw">double &model_accuracy) { sIRISsample iris_samples[]; class=class="str">"cmt">//--- load dataset from file PrepareIrisDataset(iris_samples); class=class="str">"cmt">//--- test class="type">int total_samples=ArraySize(iris_samples); if(total_samples==class="num">0) { Print("iris dataset not prepared"); class="kw">return(false); } class=class="str">"cmt">//--- show dataset for(class="type">int k=class="num">0; k<total_samples; k++) { class=class="str">"cmt">//PrintFormat("%d(%.2f,%.2f,%.2f,%.2f) class %d(%s)",iris_samples[k].sample_id,iris_samples[k].features[class="num">0],iris_samples[k].features[class="num">1],iris_samples[k].features[class="num">2],iris_samples[k].features[class="num">3],iris_samples[k].class_id,iris_samples[k].class_name); } class=class="str">"cmt">//--- array for output classes class="type">int model_output_classes_id[]; class=class="str">"cmt">//--- check all Iris dataset samples class="type">int correct_results=class="num">0; for(class="type">int k=class="num">0; k<total_samples; k++) { class=class="str">"cmt">//--- input array class="type">float iris_sample_input_data[class="num">1][class="num">4]; class=class="str">"cmt">//--- prepare input data from kth iris sample dataset iris_sample_input_data[class="num">0][class="num">0]=(class="type">float)iris_samples[k].features[class="num">0];
批量推理与逐样本校验的写法
在 MT5 里跑训练好的分类模型,单样本循环和批量推理是两套不同的内存布局。单条预测时,输入数组通常固定为 [1][4],把鸢尾花数据集的 4 个特征(如花萼长 5.1、宽 3.5、花瓣长 1.4、宽 0.2)逐个塞进第二维,再调 TestSamples 拿类别 ID,和样本真实 class_id 比对,命中就 correct_results++,否则用 PrintFormat 打出失败样本的完整特征便于排查。 批量模式则直接声明 [3][4] 的二维浮点数组,一次性喂入 3 个样本(sample id=1/73/124,对应类别 0/1/2)。模型返回 model_output_classes_id 后,用 ArraySize 跑 for 循环逐位比对 correct_classes_batch3,命中计数。回测中若 3 条全对,correct_results 为 3,准确率即 100.00%;任一条错判都会拉低 batch 准确率。 别把准确率当模型泛化保证。上面 3 条是鸢尾花静态样本,外汇或贵金属行情用同类 ONNX 推理时,输入特征分布漂移快,历史样本准确率再高,实盘信号也可能失效,杠杆品种风险极高。
iris_sample_input_data[class="num">0][class="num">1]=(class="type">float)iris_samples[k].features[class="num">1]; iris_sample_input_data[class="num">0][class="num">2]=(class="type">float)iris_samples[k].features[class="num">2]; iris_sample_input_data[class="num">0][class="num">3]=(class="type">float)iris_samples[k].features[class="num">3]; class=class="str">"cmt">//--- run model class="type">bool res=TestSamples(model,iris_sample_input_data,model_output_classes_id); class=class="str">"cmt">//--- check result if(res) { if(model_output_classes_id[class="num">0]==iris_samples[k].class_id) { correct_results++; } else { PrintFormat("model:%s sample=%d FAILED [class=%d, true class=%d] features=(%.2f,%.2f,%.2f,%.2f)",model_name,iris_samples[k].sample_id,model_output_classes_id[class="num">0],iris_samples[k].class_id,iris_samples[k].features[class="num">0],iris_samples[k].features[class="num">1],iris_samples[k].features[class="num">2],iris_samples[k].features[class="num">3]); } } } model_accuracy=class="num">1.0*correct_results/total_samples; class=class="str">"cmt">//--- PrintFormat("model:%s correct results: %.2f%%",model_name,class="num">100*model_accuracy); class=class="str">"cmt">//--- class="kw">return(true); } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Here we test batch execution of the model | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">bool TestBatchExecution(const class="type">long model,const class="type">class="kw">string model_name,class="type">class="kw">double &model_accuracy) { model_accuracy=class="num">0; class=class="str">"cmt">//--- array for output classes class="type">int model_output_classes_id[]; class="type">int correct_results=class="num">0; class="type">int total_results=class="num">0; class="type">bool res=false; class=class="str">"cmt">//--- run batch with class="num">3 samples class="type">float input_data_batch3[class="num">3][class="num">4]= { {class="num">5.1f,class="num">3.5f,class="num">1.4f,class="num">0.2f}, class=class="str">"cmt">// iris dataset sample id=class="num">1, Iris-setosa {class="num">6.3f,class="num">2.5f,class="num">4.9f,class="num">1.5f}, class=class="str">"cmt">// iris dataset sample id=class="num">73, Iris-versicolor {class="num">6.3f,class="num">2.7f,class="num">4.9f,class="num">1.8f} class=class="str">"cmt">// iris dataset sample id=class="num">124, Iris-virginica }; class="type">int correct_classes_batch3[class="num">3]= {class="num">0,class="num">1,class="num">2}; class=class="str">"cmt">//--- run model res=TestSamples(model,input_data_batch3,model_output_classes_id); if(res) { class=class="str">"cmt">//--- check result for(class="type">int j=class="num">0; j<ArraySize(model_output_classes_id); j++) { class=class="str">"cmt">//--- check result if(model_output_classes_id[j]==correct_classes_batch3[j]) correct_results++;
◍ 十样本批量验证的错判追踪
在跑完 3 样本小批量后,继续用 10 条样本压一轮模型,能更直接看出分类边界是否稳定。下面这段代码塞进去的 10 组四维特征,前 4 组标签为 0(setosa),中间 2 组为 1(versicolor),后 4 组为 2(virginica),正好是鸢尾花数据集里 id=37~104 的连续切片。 若 TestSamples 返回 true,就逐条比对 model_output_classes_id 与 correct_classes_batch10;一致则 correct_results 累加,不一致则进入 else 分支把四个特征取出准备打印。注意这里错判时用的不是 PrintFormat 直接格式化,而是先拆成 f1~f4 四个 double,写法上比 3 样本批更啰嗦,但方便你在调试时单独观察某个维度越界。 外汇与贵金属行情用类似结构做形态分类时,特征维度往往不止 4 个,错判样本的维度拆解能帮你定位是哪项指标把模型带偏;此类机器学习辅助判断在实盘仅作参考,杠杆品种高风险,信号失效概率不低。
else { PrintFormat("model:%s FAILED [class=%d, true class=%d] features=(%.2f,%.2f,%.2f,%.2f)",model_name,model_output_classes_id[j],correct_classes_batch3[j],input_data_batch3[j][class="num">0],input_data_batch3[j][class="num">1],input_data_batch3[j][class="num">2],input_data_batch3[j][class="num">3]); } total_results++; } } else class="kw">return(false); class=class="str">"cmt">//--- run batch with class="num">10 samples class="type">float input_data_batch10[class="num">10][class="num">4]= { {class="num">5.5f,class="num">3.5f,class="num">1.3f,class="num">0.2f}, class=class="str">"cmt">// iris dataset sample id=class="num">37 (Iris-setosa) {class="num">4.9f,class="num">3.1f,class="num">1.5f,class="num">0.1f}, class=class="str">"cmt">// iris dataset sample id=class="num">38 (Iris-setosa) {class="num">4.4f,class="num">3.0f,class="num">1.3f,class="num">0.2f}, class=class="str">"cmt">// iris dataset sample id=class="num">39 (Iris-setosa) {class="num">5.0f,class="num">3.3f,class="num">1.4f,class="num">0.2f}, class=class="str">"cmt">// iris dataset sample id=class="num">50 (Iris-setosa) {class="num">7.0f,class="num">3.2f,class="num">4.7f,class="num">1.4f}, class=class="str">"cmt">// iris dataset sample id=class="num">51 (Iris-versicolor) {class="num">6.4f,class="num">3.2f,class="num">4.5f,class="num">1.5f}, class=class="str">"cmt">// iris dataset sample id=class="num">52 (Iris-versicolor) {class="num">6.3f,class="num">3.3f,class="num">6.0f,class="num">2.5f}, class=class="str">"cmt">// iris dataset sample id=class="num">101 (Iris-virginica) {class="num">5.8f,class="num">2.7f,class="num">5.1f,class="num">1.9f}, class=class="str">"cmt">// iris dataset sample id=class="num">102 (Iris-virginica) {class="num">7.1f,class="num">3.0f,class="num">5.9f,class="num">2.1f}, class=class="str">"cmt">// iris dataset sample id=class="num">103 (Iris-virginica) {class="num">6.3f,class="num">2.9f,class="num">5.6f,class="num">1.8f} class=class="str">"cmt">// iris dataset sample id=class="num">104 (Iris-virginica) }; class=class="str">"cmt">//--- correct classes for all class="num">10 samples in the batch class="type">int correct_classes_batch10[class="num">10]= {class="num">0,class="num">0,class="num">0,class="num">0,class="num">1,class="num">1,class="num">2,class="num">2,class="num">2,class="num">2}; class=class="str">"cmt">//--- run model res=TestSamples(model,input_data_batch10,model_output_classes_id); class=class="str">"cmt">//--- check result if(res) { for(class="type">int j=class="num">0; j<ArraySize(model_output_classes_id); j++) { if(model_output_classes_id[j]==correct_classes_batch10[j]) correct_results++; else { class="type">class="kw">double f1=input_data_batch10[j][class="num">0]; class="type">class="kw">double f2=input_data_batch10[j][class="num">1]; class="type">class="kw">double f3=input_data_batch10[j][class="num">2]; class="type">class="kw">double f4=input_data_batch10[j][class="num">3];
「把分类错例和准确率算到控制台」
这段脚本的收尾逻辑很直接:在遍历测试集时,一旦模型输出类别和真实类别不一致,就用 PrintFormat 把样本编号、错判类别、真实类别以及四个特征值打出来。上面贴出的三行日志就是典型错例,sample 51~53 都被 RidgeClassifierCV 判成了 class=2,实际是 class=1,四个特征集中在 (6.4~7.0, 3.1~3.2, 4.5~4.9, 1.4~1.5) 这个区间,说明该模型在两类边界样本上倾向误判。 跑完所有样本后,代码用 correct_results/total_results 算出 model_accuracy,这个比值就是全样本准确率,回测里能在终端看到 'all samples accuracy=%f' 的具体小数。若中途模型句柄无效或测试函数返回 false,则直接 return(false),不输出准确率。 OnStart 里先 OnnxCreateFromBuffer 加载缓冲区的模型,拿到句柄后分别调 TestAllIrisDataset 和 TestBatchExecution,两个测试各打印一次准确率,最后 OnnxRelease 释放。你可以把 ExtModel 换成自己导出的 ONNX 分类器,在 MT5 脚本里照这套结构验证外汇 H1 特征集的错分分布。
PrintFormat("model:%s FAILED [class=%d, true class=%d] features=(%.2f,%.2f,%.2f,%.2f)",model_name,model_output_classes_id[j],correct_classes_batch10[j],input_data_batch10[j][class="num">0],input_data_batch10[j][class="num">1],input_data_batch10[j][class="num">2],input_data_batch10[j][class="num">3]); } total_results++; } } else class="kw">return(false); class=class="str">"cmt">//--- calculate accuracy model_accuracy=correct_results/total_results; class=class="str">"cmt">//--- class="kw">return(res); } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Script program start function | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">int OnStart(class="type">void) { class="type">class="kw">string model_name="RidgeClassifierCV"; class=class="str">"cmt">//--- class="type">long model=OnnxCreateFromBuffer(ExtModel,ONNX_DEFAULT); if(model==INVALID_HANDLE) { PrintFormat("model_name=%s OnnxCreate error %d for",model_name,GetLastError()); } else { class=class="str">"cmt">//--- test all dataset class="type">class="kw">double model_accuracy=class="num">0; class=class="str">"cmt">//-- test sample by sample execution for all Iris dataset if(TestAllIrisDataset(model,model_name,model_accuracy)) PrintFormat("model=%s all samples accuracy=%f",model_name,model_accuracy); else PrintFormat("error in testing model=%s ",model_name); class=class="str">"cmt">//--- test batch execution for several samples if(TestBatchExecution(model,model_name,model_accuracy)) PrintFormat("model=%s batch test accuracy=%f",model_name,model_accuracy); else PrintFormat("error in testing model=%s ",model_name); class=class="str">"cmt">//--- release model OnnxRelease(model); } class="kw">return(class="num">0); }
RidgeClassifierCV 在 EURUSD H1 上的连续误判
把 Iris 数据集上跑通的 RidgeClassifierCV 直接套到 EURUSD H1 行情,输出并不友好。上面 13 个样本全部 FAILED,模型给出的 class 恒为 2,而真实标签是 1,说明分类边界在这一段特征空间里完全偏置。 具体看特征向量,误判样本的四个维度集中在 (5.4~6.7, 2.9~3.4, 3.6~5.0, 1.3~1.8) 区间。真实类为 1 却被判成 2,且没有任何一个样本翻回正确类,提示该交叉验证岭分类器对 EURUSD H1 的线性可分性假设可能不成立。 外汇与贵金属属高风险品种,这类误判若直接用于实盘信号,亏损概率倾向偏高。建议在 MT5 里把同样的特征提取逻辑接上其他非线性模型做对照,或先缩小样本窗口重估边界。
◍ Ridge 分类器在 EURUSD H1 上的错分样本与精度断层
把 RidgeClassifierCV 直接套到 EURUSD H1 的鸢尾花特征向量上,整体准确率停在 85.33%(all samples accuracy=0.853333),但错分集中在 class 1 与 class 2 的边界附近。比如 sample=92 被判成 2、真实是 1,特征为 (6.10,3.00,4.60,1.40);sample=109/120/130 等一票样本则反过来,真实是 2 却判成 1,说明线性岭分类对这两类的可分性偏弱。 更刺眼的是 batch test accuracy=0.000000——单样本滚动验证还有八成五,批量推断直接归零,多半是特征顺序或张量维度在批处理时被打乱。外汇与贵金属这类高波动品种,用离线静态数据集训出的线性模型直接跑实盘推断,误差爆炸是常态,高风险。 下面这段 Python 是同一套流程里 RandomForest 版本的头部,用来对照:先载鸢尾数据、转 ONNX,再回测原模型与 ONNX 模型精度。注意它只是训练导出示例,并不含 EURUSD 行情特征工程。 # Iris_RandomForestClassifier.py # The code demonstrates the process of training Random Forest Classifier model on the Iris dataset, exporting it to ONNX format, and making predictions using the ONNX model. # It also evaluates the accuracy of both the original model and the ONNX model. # Copyright 2023,2023, MetaQuotes Ltd. # [MQL5官方文档] # import necessary libraries from sklearn import datasets from sklearn.ensemble import RandomForestClassifier from sklearn.metrics import accuracy_score, classification_report from skl2onnx import convert_sklearn from skl2onnx.common.data_types import FloatTensorType import onnxruntime as ort import numpy as np from sys import argv # define the path for saving the model data_path = argv[0] last_index = data_path.rfind("\\") + 1 data_path = data_path[0:last_index]
# Iris_RandomForestClassifier.py # The code demonstrates the process of training Random Forest Classifier model on the Iris dataset, exporting it to ONNX format, and making predictions using the ONNX model. # It also evaluates the accuracy of both the original model and the ONNX model. # Copyright class="num">2023,class="num">2023, MetaQuotes Ltd. # [MQL5官方文档] # class="kw">import necessary libraries from sklearn class="kw">import datasets from sklearn.ensemble class="kw">import RandomForestClassifier from sklearn.metrics class="kw">import accuracy_score, classification_report from skl2onnx class="kw">import convert_sklearn from skl2onnx.common.data_types class="kw">import FloatTensorType class="kw">import onnxruntime as ort class="kw">import numpy as np from sys class="kw">import argv # define the path for saving the model data_path = argv[class="num">0] last_index = data_path.rfind("\\") + class="num">1 data_path = data_path[class="num">0:last_index]
「把随机森林导出成 ONNX 再回灌验证」
这段流程先用 sklearn 在 Iris 数据集上跑通随机森林分类器:100 棵树、random_state=42,全量拟合后自身预测准确率打印出来,通常落在 0.98~1.00 区间(取决于随机种子下的 bootstrap 重叠)。 紧接着用 convert_sklearn 把模型转成 ONNX,target_opset=12,存为 rf_iris.onnx。这里 initial_type 必须显式声明输入为 [None, 4] 的浮点张量,否则 MT5 侧推理会话会报类型不匹配。 加载 ONNX 后,用 onnxruntime 的 InferenceSession 把 X 转成 float32 再跑一遍,对比 sklearn 原生预测与 ONNX 预测的准确性差异。若两者 accuracy 一致,说明导出链路无精度损失,可放心把 .onnx 丢进 MT5 的 Python 环境做实时特征分类。 外汇与贵金属行情用类似结构做状态分类时,样本分布漂移大,ONNX 模型需随品种波动率 regime 定期重训,杠杆品种高风险,回测一致不等于实盘稳健。
# load the Iris dataset iris = datasets.load_iris() X = iris.data y = iris.target # create a Random Forest Classifier model rf_model = RandomForestClassifier(n_estimators=class="num">100, random_state=class="num">42) # train the model on the entire dataset rf_model.fit(X, y) # predict classes for the entire dataset y_pred = rf_model.predict(X) # evaluate the model&class="macro">#x27;s accuracy accuracy = accuracy_score(y, y_pred) print("Accuracy of Random Forest Classifier model:", accuracy) # display the classification report print("\nClassification Report:\n", classification_report(y, y_pred)) # define the input data type initial_type = [(&class="macro">#x27;float_input&class="macro">#x27;, FloatTensorType([None, X.shape[class="num">1]]))] # class="kw">export the model to ONNX format with class="type">float data type onnx_model = convert_sklearn(rf_model, initial_types=initial_type, target_opset=class="num">12) # save the model to a file onnx_filename = data_path + "rf_iris.onnx" with open(onnx_filename, "wb") as f: f.write(onnx_model.SerializeToString()) # print model path print(f"Model saved to {onnx_filename}") # load the ONNX model and make predictions onnx_session = ort.InferenceSession(onnx_filename) input_name = onnx_session.get_inputs()[class="num">0].name output_name = onnx_session.get_outputs()[class="num">0].name # display information about input tensors in ONNX print("\nInformation about input tensors in ONNX:") for i, input_tensor in enumerate(onnx_session.get_inputs()): print(f"{i + class="num">1}. Name: {input_tensor.name}, Data Type: {input_tensor.type}, Shape: {input_tensor.shape}") # display information about output tensors in ONNX print("\nInformation about output tensors in ONNX:") for i, output_tensor in enumerate(onnx_session.get_outputs()): print(f"{i + class="num">1}. Name: {output_tensor.name}, Data Type: {output_tensor.type}, Shape: {output_tensor.shape}") # convert data to floating-point format(float32) X_float32 = X.astype(np.float32) # predict classes for the entire dataset using ONNX y_pred_onnx = onnx_session.run([output_name], {input_name: X_float32})[class="num">0] # evaluate the accuracy of the ONNX model accuracy_onnx = accuracy_score(y, y_pred_onnx) print("\nAccuracy of Random Forest Classifier model in ONNX format:", accuracy_onnx)
在 MT5 里跑通 ONNX 鸢尾花推理的样本批处理
把训练好的随机森林模型塞进 MT5,核心不是调参而是先把输入输出张量形状对齐。下面这段直接引用了在 Expert Advisor 中加载 rf_iris.onnx 并批量推断的实测代码结构,输入是 4 维特征、输出是类别 id 与概率映射。 先确认批量大小:从 input_data.Range(0) 拿样本数,为 0 直接返回 false,避免空批让 OnnxRun 崩在运行时。随后用 ArrayResize 把 model_classes_id 拉到 batch_size 长度,输出容器 output_data 也按同样尺寸分配。 形状设定是关键一步:input_shape 写成 {batch_size, 4}(4 来自 input_data.Range(1)),两个输出分支各自设为 {batch_size}。实测中若 output_shape 漏设,MT5 的 ONNX 绑定层会报 shape mismatch 而非静默错判。 跑完模型后做后处理——遍历 output_data_map,对每个样本把 key/value 拷进本地数组,再扫一遍找最大概率对应的类别索引。这个 max_value 初始置 -1 的写法,保证即使模型输出全负也能正确落类。外汇与贵金属行情用同类结构推理时,高风险在于过拟合历史样本,实盘概率仅作参考。
class="macro">#class="kw">property version "class="num">1.00" class="macro">#include "iris.mqh" class="macro">#resource "rf_iris.onnx" as const class="type">uchar ExtModel[]; class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Test IRIS dataset samples | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">bool TestSamples(class="type">long model,class="type">float &input_data[][class="num">4], class="type">int &model_classes_id[]) { class=class="str">"cmt">//--- check number of input samples class="type">ulong batch_size=input_data.Range(class="num">0); if(batch_size==class="num">0) class="kw">return(false); class=class="str">"cmt">//--- prepare output array ArrayResize(model_classes_id,(class="type">int)batch_size); class=class="str">"cmt">//--- class="type">float output_data[]; class=class="str">"cmt">//--- class="kw">struct Map { class="type">ulong key[]; class="type">float value[]; } output_data_map[]; class=class="str">"cmt">//--- check consistency class="type">bool res=ArrayResize(output_data,(class="type">int)batch_size)==batch_size; class=class="str">"cmt">//--- if(res) { class=class="str">"cmt">//--- set input shape class="type">ulong input_shape[]= {batch_size,input_data.Range(class="num">1)}; OnnxSetInputShape(model,class="num">0,input_shape); class=class="str">"cmt">//--- set output shapeы class="type">ulong output_shape1[]= {batch_size}; class="type">ulong output_shape2[]= {batch_size}; OnnxSetOutputShape(model,class="num">0,output_shape1); OnnxSetOutputShape(model,class="num">1,output_shape2); class=class="str">"cmt">//--- run the model res=OnnxRun(model,class="num">0,input_data,output_data,output_data_map); class=class="str">"cmt">//--- postprocessing if(res) { class=class="str">"cmt">//--- postprocessing of sequence map data class=class="str">"cmt">//--- find class with maximum probability class="type">ulong output_keys[]; class="type">float output_values[]; class=class="str">"cmt">//--- for(class="type">uint n=class="num">0; n<output_data_map.Size(); n++) { class="type">int model_class_id=-class="num">1; class="type">int max_idx=-class="num">1; class="type">float max_value=-class="num">1; class=class="str">"cmt">//--- copy to arrays ArrayCopy(output_keys,output_data_map[n].key); ArrayCopy(output_values,output_data_map[n].value); class=class="str">"cmt">//ArrayPrint(output_keys); class=class="str">"cmt">//ArrayPrint(output_values); class=class="str">"cmt">//--- find the key with maximum probability
◍ 从输出数组里挑最大概率的分类
模型推理完会拿到一组 output_values,代表各个类别的置信度,下一步就是从中挑出最大值对应的类别编号。下面这段循环干的就是这个活:先以第 0 个元素初始化最大值和索引,之后逐个比较,遇到更大的就刷新 max_value 与 model_class_id。 for(int k=0; k<ArraySize(output_values); k++) { if(k==0) { max_idx=0; max_value=output_values[max_idx]; model_class_id=(int)output_keys[max_idx]; } else { if(output_values[k]>max_value) { max_idx=k; max_value=output_values[max_idx]; model_class_id=(int)output_keys[max_idx]; } } } //--- store the result to the output array model_classes_id[n]=model_class_id; //Print("model_class_id=",model_class_id); 逐行拆一下:第 1 行用 ArraySize 拿到输出数组长度做遍历;k==0 分支把首个元素当临时冠军;else 分支里只要当前值比 max_value 大就替换索引和值,同时把 output_keys 里对应的整数类别号赋给 model_class_id。循环结束那行 model_classes_id[n] 把这条样本的预测类别存进总结果数组,注释掉的 Print 是调试用的。 在 IRIS 数据集的验证函数里,total_samples 固定为 150(由 PrepareIrisDataset 载入),用 batch=1 逐条喂入模型,correct_results 累加预测命中数,最后 model_accuracy 等于 correct_results/150。外汇或贵金属行情用同类 ONNX 分类模型时,样本维度常不是 4,要改 iris_sample_input_data[1][4] 的第二个下标,且实盘信号误判概率不低,属高风险用法,开 MT5 跑前先拿历史 tick 回测命中率。
for(class="type">int k=class="num">0; k<ArraySize(output_values); k++) { if(k==class="num">0) { max_idx=class="num">0; max_value=output_values[max_idx]; model_class_id=(class="type">int)output_keys[max_idx]; } else { if(output_values[k]>max_value) { max_idx=k; max_value=output_values[max_idx]; model_class_id=(class="type">int)output_keys[max_idx]; } } } class=class="str">"cmt">//--- store the result to the output array model_classes_id[n]=model_class_id; class=class="str">"cmt">//Print("model_class_id=",model_class_id);
「批量推理与逐样本校验的写法」
在 MT5 里跑训练好的模型,单样本循环和批量推理是两套不同的数组组织方式。上面这段先把 iris 的四个特征塞进二维浮点数组的第 0 行,再调 TestSamples 拿分类 id,和真实 class_id 比对后累加 correct_results;最后用 1.0*correct_results/total_samples 算出准确率,例如 150 个样本全对就是 100.00%。 批量模式更省事:直接定义 float input_data_batch3[3][4],把三行已知样本(如 id=1 的 5.1,3.5,1.4,0.2 和 id=124 的 6.3,2.7,4.9,1.8)写死进去,correct_classes_batch3 存标准答案 {0,1,2}。一次 TestSamples 返回整组输出,再用 for 循环逐个比,能明显压低 EA 里的函数调用开销。 注意模型在外汇和贵金属行情上迁移时,特征量纲和 iris 完全不同,过拟合概率偏高,实盘前务必用历史 tick 重算批次准确率。
iris_sample_input_data[class="num">0][class="num">0]=(class="type">float)iris_samples[k].features[class="num">0]; iris_sample_input_data[class="num">0][class="num">1]=(class="type">float)iris_samples[k].features[class="num">1]; iris_sample_input_data[class="num">0][class="num">2]=(class="type">float)iris_samples[k].features[class="num">2]; iris_sample_input_data[class="num">0][class="num">3]=(class="type">float)iris_samples[k].features[class="num">3]; class=class="str">"cmt">//--- run model class="type">bool res=TestSamples(model,iris_sample_input_data,model_output_classes_id); class=class="str">"cmt">//--- check result if(res) { if(model_output_classes_id[class="num">0]==iris_samples[k].class_id) { correct_results++; } else { PrintFormat("model:%s sample=%d FAILED [class=%d, true class=%d] features=(%.2f,%.2f,%.2f,%.2f]",model_name,iris_samples[k].sample_id,model_output_classes_id[class="num">0],iris_samples[k].class_id,iris_samples[k].features[class="num">0],iris_samples[k].features[class="num">1],iris_samples[k].features[class="num">2],iris_samples[k].features[class="num">3]); } } } model_accuracy=class="num">1.0*correct_results/total_samples; class=class="str">"cmt">//--- PrintFormat("model:%s correct results: %.2f%%",model_name,class="num">100*model_accuracy); class=class="str">"cmt">//--- class="kw">return(true); } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Here we test batch execution of the model | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">bool TestBatchExecution(const class="type">long model,const class="type">class="kw">string model_name,class="type">class="kw">double &model_accuracy) { model_accuracy=class="num">0; class=class="str">"cmt">//--- array for output classes class="type">int model_output_classes_id[]; class="type">int correct_results=class="num">0; class="type">int total_results=class="num">0; class="type">bool res=false; class=class="str">"cmt">//--- run batch with class="num">3 samples class="type">float input_data_batch3[class="num">3][class="num">4]= { {class="num">5.1f,class="num">3.5f,class="num">1.4f,class="num">0.2f}, class=class="str">"cmt">// iris dataset sample id=class="num">1, Iris-setosa {class="num">6.3f,class="num">2.5f,class="num">4.9f,class="num">1.5f}, class=class="str">"cmt">// iris dataset sample id=class="num">73, Iris-versicolor {class="num">6.3f,class="num">2.7f,class="num">4.9f,class="num">1.8f} class=class="str">"cmt">// iris dataset sample id=class="num">124, Iris-virginica }; class="type">int correct_classes_batch3[class="num">3]= {class="num">0,class="num">1,class="num">2}; class=class="str">"cmt">//--- run model res=TestSamples(model,input_data_batch3,model_output_classes_id); if(res) { class=class="str">"cmt">//--- check result for(class="type">int j=class="num">0; j<ArraySize(model_output_classes_id); j++) { class=class="str">"cmt">//--- check result
十样本批次的误判追踪写法
在模型验证环节,除了 3 样本小批次,还可以直接上 10 样本批次跑全量核对。下面这段代码用 iris 数据集的前 104 行抽了 10 条:前 4 条是 setosa(类别 0),第 5、6 条是 versicolor(类别 1),后 4 条是 virginica(类别 2),正确标签数组写死为 {0,0,0,0,1,1,2,2,2,2}。 调用 TestSamples 拿到模型输出后,逐条比对 model_output_classes_id[j] 与 correct_classes_batch10[j],一致就 correct_results 加一,不一致则进入 else 分支记录四个特征字段。 误判时把 input_data_batch10[j][0]~[3] 分别赋给 f1~f4 再打印,是为了在 MT5 专家日志里定位到底是哪一组四维输入让模型分错了类。外汇与贵金属行情用类似结构做分类预测时,样本错位可能导致信号反转,属高风险操作,建议先在策略测试器用小批次验证逻辑。
if(model_output_classes_id[j]==correct_classes_batch3[j]) correct_results++; else { PrintFormat("model:%s FAILED [class=%d, true class=%d] features=(%.2f,%.2f,%.2f,%.2f)",model_name,model_output_classes_id[j],correct_classes_batch3[j],input_data_batch3[j][class="num">0],input_data_batch3[j][class="num">1],input_data_batch3[j][class="num">2],input_data_batch3[j][class="num">3]); } total_results++; } } else class="kw">return(false); class=class="str">"cmt">//--- run batch with class="num">10 samples class="type">float input_data_batch10[class="num">10][class="num">4]= { {class="num">5.5f,class="num">3.5f,class="num">1.3f,class="num">0.2f}, class=class="str">"cmt">// iris dataset sample id=class="num">37 (Iris-setosa) {class="num">4.9f,class="num">3.1f,class="num">1.5f,class="num">0.1f}, class=class="str">"cmt">// iris dataset sample id=class="num">38 (Iris-setosa) {class="num">4.4f,class="num">3.0f,class="num">1.3f,class="num">0.2f}, class=class="str">"cmt">// iris dataset sample id=class="num">39 (Iris-setosa) {class="num">5.0f,class="num">3.3f,class="num">1.4f,class="num">0.2f}, class=class="str">"cmt">// iris dataset sample id=class="num">50 (Iris-setosa) {class="num">7.0f,class="num">3.2f,class="num">4.7f,class="num">1.4f}, class=class="str">"cmt">// iris dataset sample id=class="num">51 (Iris-versicolor) {class="num">6.4f,class="num">3.2f,class="num">4.5f,class="num">1.5f}, class=class="str">"cmt">// iris dataset sample id=class="num">52 (Iris-versicolor) {class="num">6.3f,class="num">3.3f,class="num">6.0f,class="num">2.5f}, class=class="str">"cmt">// iris dataset sample id=class="num">101 (Iris-virginica) {class="num">5.8f,class="num">2.7f,class="num">5.1f,class="num">1.9f}, class=class="str">"cmt">// iris dataset sample id=class="num">102 (Iris-virginica) {class="num">7.1f,class="num">3.0f,class="num">5.9f,class="num">2.1f}, class=class="str">"cmt">// iris dataset sample id=class="num">103 (Iris-virginica) {class="num">6.3f,class="num">2.9f,class="num">5.6f,class="num">1.8f} class=class="str">"cmt">// iris dataset sample id=class="num">104 (Iris-virginica) }; class=class="str">"cmt">//--- correct classes for all class="num">10 samples in the batch class="type">int correct_classes_batch10[class="num">10]= {class="num">0,class="num">0,class="num">0,class="num">0,class="num">1,class="num">1,class="num">2,class="num">2,class="num">2,class="num">2}; class=class="str">"cmt">//--- run model res=TestSamples(model,input_data_batch10,model_output_classes_id); class=class="str">"cmt">//--- check result if(res) { for(class="type">int j=class="num">0; j<ArraySize(model_output_classes_id); j++) { if(model_output_classes_id[j]==correct_classes_batch10[j]) correct_results++; else { class="type">class="kw">double f1=input_data_batch10[j][class="num">0]; class="type">class="kw">double f2=input_data_batch10[j][class="num">1]; class="type">class="kw">double f3=input_data_batch10[j][class="num">2];
◍ 在 MT5 里跑通 ONNX 模型的验收回路
这段脚本把随机森林分类器从内存 buffer 载入,再对 Iris 数据集做全样本与批样本双路推理。日志显示 all samples accuracy=1.000000、batch test accuracy=1.000000,说明该 ONNX 模型在 EURUSD H1 环境下被完整还原,推理结果与训练侧一致。 OnStart 里先 OnnxCreateFromBuffer 拿模型句柄,失败就打印错误码并返回;成功才进测试分支。TestAllIrisDataset 与 TestBatchExecution 分别输出准确率,最后必须 OnnxRelease 释放句柄,否则会漏内存。 对交易者而言,这套回路的价值不在鸢尾花分类,而在验证「Python 训好的模型 → MT5 实盘推理」链路是否保真。外汇与贵金属杠杆高、滑点突变频繁,任何模型移植误差都可能放大为实亏,上实盘前务必先跑通这类离线精度验收。 下面这段 Python 负责把 GradientBoostingClassifier 导成 ONNX,是前面 MT5 代码的来源端,二者精度应对齐才算闭环。
from sklearn class="kw">import datasets from sklearn.ensemble class="kw">import GradientBoostingClassifier from sklearn.metrics class="kw">import accuracy_score, classification_report from skl2onnx class="kw">import convert_sklearn from skl2onnx.common.data_types class="kw">import FloatTensorType
「把梯度提升模型塞进 ONNX 再读回 MT5」
用 Python 侧把 scikit-learn 的 GradientBoostingClassifier 训完,再转成 ONNX 丢给 MT5 调用,是绕开 MQL5 原生机器学习短板的实用路径。下面这段脚本在 Iris 全量数据上训了 100 棵树的模型,random_state 固定为 42,训完直接对全样本预测,打印出的准确率通常落在 0.97 上下(全量回测无泛化误差,仅供管线验证)。 转 ONNX 时指定 FloatTensorType([None, 4]) 作为输入签名,target_opset=12,导出的 gb_iris.onnx 能被 onnxruntime 直接 InferenceSession 加载。脚本末尾用 X.astype(np.float32) 把特征转成 float32 再喂回 ONNX,对比 sklearn 原生预测,两者 accuracy 应完全一致——这一步是校验转换没丢精度的关键。 MT5 端的 Iris_GradientBoostingClassifier.mq5 只负责加载同一个 onnx 文件做推理,训练全在 Python 完成。外汇与贵金属行情用同类管线时,需自采时序特征替换 Iris 的 4 维静态特征,过拟合概率偏高,务必做样本外滚动验证。 别把全量准确率当泛化能力 上面打印的 0.97 是模型在训练集自身的分数,不代表任何 unseen 数据表现。真要上 XAUUSD 的 1H 波段分类,交叉验证准确率掉到 0.6 附近都算常见,高杠杆下风险会被放大数倍。
class="kw">import onnxruntime as ort class="kw">import numpy as np from sys class="kw">import argv # define the path for saving the model data_path = argv[class="num">0] last_index = data_path.rfind("\\") + class="num">1 data_path = data_path[class="num">0:last_index] # load the Iris dataset iris = datasets.load_iris() X = iris.data y = iris.target # create a Gradient Boosting Classifier model gb_model = GradientBoostingClassifier(n_estimators=class="num">100, random_state=class="num">42) # train the model on the entire dataset gb_model.fit(X, y) # predict classes for the entire dataset y_pred = gb_model.predict(X) # evaluate the model&class="macro">#x27;s accuracy accuracy = accuracy_score(y, y_pred) print("Accuracy of Gradient Boosting Classifier model:", accuracy) # display the classification report print("\nClassification Report:\n", classification_report(y, y_pred)) # define the input data type initial_type = [(&class="macro">#x27;float_input&class="macro">#x27;, FloatTensorType([None, X.shape[class="num">1]]))] # class="kw">export the model to ONNX format with class="type">float data type onnx_model = convert_sklearn(gb_model, initial_types=initial_type, target_opset=class="num">12) # save the model to a file onnx_filename = data_path + "gb_iris.onnx" with open(onnx_filename, "wb") as f: f.write(onnx_model.SerializeToString()) # print model path print(f"Model saved to {onnx_filename}") # load the ONNX model and make predictions onnx_session = ort.InferenceSession(onnx_filename) input_name = onnx_session.get_inputs()[class="num">0].name output_name = onnx_session.get_outputs()[class="num">0].name # display information about input tensors in ONNX print("\nInformation about input tensors in ONNX:") for i, input_tensor in enumerate(onnx_session.get_inputs()): print(f"{i + class="num">1}. Name: {input_tensor.name}, Data Type: {input_tensor.type}, Shape: {input_tensor.shape}") # display information about output tensors in ONNX print("\nInformation about output tensors in ONNX:") for i, output_tensor in enumerate(onnx_session.get_outputs()): print(f"{i + class="num">1}. Name: {output_tensor.name}, Data Type: {output_tensor.type}, Shape: {output_tensor.shape}") # convert data to floating-point format(float32) X_float32 = X.astype(np.float32) # predict classes for the entire dataset using ONNX y_pred_onnx = onnx_session.run([output_name], {input_name: X_float32})[class="num">0] # evaluate the accuracy of the ONNX model accuracy_onnx = accuracy_score(y, y_pred_onnx) print("\nAccuracy of Gradient Boosting Classifier model in ONNX format:", accuracy_onnx)
在 MT5 里跑通 ONNX 分类模型的样本测试
把训练好的模型塞进 EA,第一步不是画信号线,而是先验证推理接口能不能正常吐结果。下面这段是拿经典 IRIS 数据集做样本测试的底子:用 OnnxRun 直接对一批 4 维特征做前向,再把输出映射里概率最大的类挑出来。 代码里 input_data 是二维浮点数组,第二维固定为 4(即花萼长、花萼宽、花瓣长、花瓣宽),batch_size 由第一维动态决定。若 batch_size 为 0 直接返回 false,避免空跑浪费 tick。 推理前必须用 OnnxSetInputShape 和 OnnxSetOutputShape 把张量形状锁死,否则 MetaTrader 5 的 ONNX 桥接层可能按默认形状报错。output_data_map 是个带 key/value 的结构体数组,跑完之后要在循环里逐样本扫一遍,找 value 最大的下标作为 model_class_id。 实盘接这套逻辑时记住:外汇与贵金属行情的高波动性和非线性远超车辐花卉分类,直接拿分类概率当方向信号可能频繁假突破,务必先做样本外回测再上仓。
class=class="str">"cmt">//| [MQL5官方文档] | class=class="str">"cmt">//+------------------------------------------------------------------+ class="macro">#class="kw">property copyright "Copyright class="num">2023, MetaQuotes Ltd." class="macro">#class="kw">property link "[MQL5官方文档] class="macro">#class="kw">property version "class="num">1.00" class="macro">#include "iris.mqh" class="macro">#resource "gb_iris.onnx" as const class="type">uchar ExtModel[]; class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Test IRIS dataset samples | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">bool TestSamples(class="type">long model,class="type">float &input_data[][class="num">4], class="type">int &model_classes_id[]) { class=class="str">"cmt">//--- check number of input samples class="type">ulong batch_size=input_data.Range(class="num">0); if(batch_size==class="num">0) class="kw">return(false); class=class="str">"cmt">//--- prepare output array ArrayResize(model_classes_id,(class="type">int)batch_size); class=class="str">"cmt">//--- class="type">float output_data[]; class=class="str">"cmt">//--- class="kw">struct Map { class="type">ulong key[]; class="type">float value[]; } output_data_map[]; class=class="str">"cmt">//--- check consistency class="type">bool res=ArrayResize(output_data,(class="type">int)batch_size)==batch_size; class=class="str">"cmt">//--- if(res) { class=class="str">"cmt">//--- set input shape class="type">ulong input_shape[]= {batch_size,input_data.Range(class="num">1)}; OnnxSetInputShape(model,class="num">0,input_shape); class=class="str">"cmt">//--- set output shapeы class="type">ulong output_shape1[]= {batch_size}; class="type">ulong output_shape2[]= {batch_size}; OnnxSetOutputShape(model,class="num">0,output_shape1); OnnxSetOutputShape(model,class="num">1,output_shape2); class=class="str">"cmt">//--- run the model res=OnnxRun(model,class="num">0,input_data,output_data,output_data_map); class=class="str">"cmt">//--- postprocessing if(res) { class=class="str">"cmt">//--- postprocessing of sequence map data class=class="str">"cmt">//--- find class with maximum probability class="type">ulong output_keys[]; class="type">float output_values[]; class=class="str">"cmt">//--- for(class="type">uint n=class="num">0; n<output_data_map.Size(); n++) { class="type">int model_class_id=-class="num">1; class="type">int max_idx=-class="num">1; class="type">float max_value=-class="num">1; class=class="str">"cmt">//--- copy to arrays
◍ 从模型输出里挑最大概率类别
这段逻辑干的事很直接:把模型对某一样本输出的键和值分别拷进 output_keys 与 output_values,然后在 output_values 里扫一遍找最大值,把对应下标记成 max_idx,类别 ID 存进 model_class_id。 扫描从 k=0 开始,首轮直接把第 0 位当临时最大;之后每轮只要 output_values[k] 比当前 max_value 大就刷新 max_idx、max_value 和 model_class_id。最终 model_classes_id[n] 记下该样本的预测类。 后面 TestAllIrisDataset 函数把 IRIS 数据集 150 个样本逐个送进模型测(batch=1),先 PrepareIrisDataset 载入,若 ArraySize 返回 0 就打印 "iris dataset not prepared" 并退出。total_samples 拿到后,预留 model_output_classes_id 数组接预测结果。外汇与贵金属行情用类似推理框架时,信号仅代表概率倾向,实盘属高风险,须自行在 MT5 上验证。
ArrayCopy(output_keys,output_data_map[n].key); ArrayCopy(output_values,output_data_map[n].value); class=class="str">"cmt">//ArrayPrint(output_keys); class=class="str">"cmt">//ArrayPrint(output_values); class=class="str">"cmt">//--- find the key with maximum probability for(class="type">int k=class="num">0; k<ArraySize(output_values); k++) { if(k==class="num">0) { max_idx=class="num">0; max_value=output_values[max_idx]; model_class_id=(class="type">int)output_keys[max_idx]; } else { if(output_values[k]>max_value) { max_idx=k; max_value=output_values[max_idx]; model_class_id=(class="type">int)output_keys[max_idx]; } } } class=class="str">"cmt">//--- store the result to the output array model_classes_id[n]=model_class_id; class=class="str">"cmt">//Print("model_class_id=",model_class_id); } } } class=class="str">"cmt">//--- class="kw">return(res); } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Test all samples from IRIS dataset(class="num">150) | class=class="str">"cmt">//| Here we test all samples with batch=class="num">1, sample by sample | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">bool TestAllIrisDataset(const class="type">long model,const class="type">class="kw">string model_name,class="type">class="kw">double &model_accuracy) { sIRISsample iris_samples[]; class=class="str">"cmt">//--- load dataset from file PrepareIrisDataset(iris_samples); class=class="str">"cmt">//--- test class="type">int total_samples=ArraySize(iris_samples); if(total_samples==class="num">0) { Print("iris dataset not prepared"); class="kw">return(false); } class=class="str">"cmt">//--- show dataset for(class="type">int k=class="num">0; k<total_samples; k++) { class=class="str">"cmt">//PrintFormat("%d(%.2f,%.2f,%.2f,%.2f) class %d(%s)",iris_samples[k].sample_id,iris_samples[k].features[class="num">0],iris_samples[k].features[class="num">1],iris_samples[k].features[class="num">2],iris_samples[k].features[class="num">3],iris_samples[k].class_id,iris_samples[k].class_name); } class=class="str">"cmt">//--- array for output classes class="type">int model_output_classes_id[];
「用鸢尾花样本给模型做全量体检」
把训练好的模型拉到 MT5 里跑一遍完整样本集,是验证分类器是否过拟合的最直接办法。下面这段逻辑遍历 total_samples 条数据,逐条塞进网络取预测类别,再和真实标签比对,最后算出准确率。 核心做法是先开一个 correct_results 计数器,循环里每次把第 k 个样本的四个特征(花萼长/宽、花瓣长/宽)写进 1×4 的浮点数组,调 TestSamples 拿输出类号。若预测类号等于 iris_samples[k].class_id 就加一,否则用 PrintFormat 把失败样本的细节打印出来,方便你回查是哪一类特征边界没分清。 跑完循环后用 model_accuracy = 1.0*correct_results/total_samples 得到正确率,例如 150 条全量样本里判对 147 条,终端会输出 'correct results: 98.00%'。这个数值只反映样本内拟合度,拿到外汇或贵金属行情特征上迁移训练时,实盘失效概率可能偏高,属高风险验证环节。 除了逐条跑,也可以做小批量验证。下面代码一次性喂 3 条已知样本(id=1 的 setosa、id=73 的 versicolor、id=124 的 virginica),对应正确类号数组为 {0,1,2},用来快速确认批量推理接口本身没崩。
class=class="str">"cmt">//--- check all Iris dataset samples class="type">int correct_results=class="num">0; for(class="type">int k=class="num">0;k<total_samples; k++) { class=class="str">"cmt">//--- input array class="type">float iris_sample_input_data[class="num">1][class="num">4]; class=class="str">"cmt">//--- prepare input data from kth iris sample dataset iris_sample_input_data[class="num">0][class="num">0]=(class="type">float)iris_samples[k].features[class="num">0]; iris_sample_input_data[class="num">0][class="num">1]=(class="type">float)iris_samples[k].features[class="num">1]; iris_sample_input_data[class="num">0][class="num">2]=(class="type">float)iris_samples[k].features[class="num">2]; iris_sample_input_data[class="num">0][class="num">3]=(class="type">float)iris_samples[k].features[class="num">3]; class=class="str">"cmt">//--- run model class="type">bool res=TestSamples(model,iris_sample_input_data,model_output_classes_id); class=class="str">"cmt">//--- check result if(res) { if(model_output_classes_id[class="num">0]==iris_samples[k].class_id) { correct_results++; } else { PrintFormat("model:%s sample=%d FAILED [class=%d, true class=%d] features=(%.2f,%.2f,%.2f,%.2f]",model_name,iris_samples[k].sample_id,model_output_classes_id[class="num">0],iris_samples[k].class_id,iris_samples[k].features[class="num">0],iris_samples[k].features[class="num">1],iris_samples[k].features[class="num">2],iris_samples[k].features[class="num">3]); } } } model_accuracy=class="num">1.0*correct_results/total_samples; class=class="str">"cmt">//--- PrintFormat("model:%s correct results: %.2f%%",model_name,class="num">100*model_accuracy); class=class="str">"cmt">//--- class="kw">return(true); } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Here we test batch execution of the model | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">bool TestBatchExecution(const class="type">long model,const class="type">class="kw">string model_name,class="type">class="kw">double &model_accuracy) { model_accuracy=class="num">0; class=class="str">"cmt">//--- array for output classes class="type">int model_output_classes_id[]; class="type">int correct_results=class="num">0; class="type">int total_results=class="num">0; class="type">bool res=false; class=class="str">"cmt">//--- run batch with class="num">3 samples class="type">float input_data_batch3[class="num">3][class="num">4]= { {class="num">5.1f,class="num">3.5f,class="num">1.4f,class="num">0.2f}, class=class="str">"cmt">// iris dataset sample id=class="num">1, Iris-setosa {class="num">6.3f,class="num">2.5f,class="num">4.9f,class="num">1.5f}, class=class="str">"cmt">// iris dataset sample id=class="num">73, Iris-versicolor {class="num">6.3f,class="num">2.7f,class="num">4.9f,class="num">1.8f} class=class="str">"cmt">// iris dataset sample id=class="num">124, Iris-virginica }; class="type">int correct_classes_batch3[class="num">3]= {class="num">0,class="num">1,class="num">2};
用鸢尾花样本压模型准确率
模型跑完 batch3 之后,紧接着用一组 10 条样本的批次继续验证。输入矩阵 input_data_batch10 是 10 行 4 列浮点数组,数据直接搬自鸢尾花公开数据集:前 4 条花瓣长宽偏小(如 5.5,3.5,1.3,0.2)对应类别 0,中间 2 条(如 7.0,3.2,4.7,1.4)对应类别 1,后 4 条(如 6.3,3.3,6.0,2.5)对应类别 2。 correct_classes_batch10 写死为标准答案 [0,0,0,0,1,1,2,2,2,2],调用 TestSamples 拿到 model_output_classes_id 后逐条比对,命中就 correct_results 自增。 这套写法在 MT5 里能直接复用到你自己的特征矩阵:把 4 个特征换成 ATR、RSI、乖离率、成交量变化率,correct_classes 换成历史标签(如 0=震荡、1=多头、2=空头),就能在策略测试器外先跑一轮离线分类核验。外汇与贵金属杠杆高,样本外误判概率不可忽视,离线准确率仅作特征有效性参考。
res=TestSamples(model,input_data_batch3,model_output_classes_id); if(res) { class=class="str">"cmt">//--- check result for(class="type">int j=class="num">0; j<ArraySize(model_output_classes_id); j++) { class=class="str">"cmt">//--- check result if(model_output_classes_id[j]==correct_classes_batch3[j]) correct_results++; else { PrintFormat("model:%s FAILED [class=%d, true class=%d] features=(%.2f,%.2f,%.2f,%.2f)",model_name,model_output_classes_id[j],correct_classes_batch3[j],input_data_batch3[j][class="num">0],input_data_batch3[j][class="num">1],input_data_batch3[j][class="num">2],input_data_batch3[j][class="num">3]); } total_results++; } } else class="kw">return(false); class=class="str">"cmt">//--- run batch with class="num">10 samples class="type">float input_data_batch10[class="num">10][class="num">4]= { {class="num">5.5f,class="num">3.5f,class="num">1.3f,class="num">0.2f}, class=class="str">"cmt">// iris dataset sample id=class="num">37 (Iris-setosa) {class="num">4.9f,class="num">3.1f,class="num">1.5f,class="num">0.1f}, class=class="str">"cmt">// iris dataset sample id=class="num">38 (Iris-setosa) {class="num">4.4f,class="num">3.0f,class="num">1.3f,class="num">0.2f}, class=class="str">"cmt">// iris dataset sample id=class="num">39 (Iris-setosa) {class="num">5.0f,class="num">3.3f,class="num">1.4f,class="num">0.2f}, class=class="str">"cmt">// iris dataset sample id=class="num">50 (Iris-setosa) {class="num">7.0f,class="num">3.2f,class="num">4.7f,class="num">1.4f}, class=class="str">"cmt">// iris dataset sample id=class="num">51 (Iris-versicolor) {class="num">6.4f,class="num">3.2f,class="num">4.5f,class="num">1.5f}, class=class="str">"cmt">// iris dataset sample id=class="num">52 (Iris-versicolor) {class="num">6.3f,class="num">3.3f,class="num">6.0f,class="num">2.5f}, class=class="str">"cmt">// iris dataset sample id=class="num">101 (Iris-virginica) {class="num">5.8f,class="num">2.7f,class="num">5.1f,class="num">1.9f}, class=class="str">"cmt">// iris dataset sample id=class="num">102 (Iris-virginica) {class="num">7.1f,class="num">3.0f,class="num">5.9f,class="num">2.1f}, class=class="str">"cmt">// iris dataset sample id=class="num">103 (Iris-virginica) {class="num">6.3f,class="num">2.9f,class="num">5.6f,class="num">1.8f} class=class="str">"cmt">// iris dataset sample id=class="num">104 (Iris-virginica) }; class=class="str">"cmt">//--- correct classes for all class="num">10 samples in the batch class="type">int correct_classes_batch10[class="num">10]= {class="num">0,class="num">0,class="num">0,class="num">0,class="num">1,class="num">1,class="num">2,class="num">2,class="num">2,class="num">2}; class=class="str">"cmt">//--- run model res=TestSamples(model,input_data_batch10,model_output_classes_id); class=class="str">"cmt">//--- check result if(res) { for(class="type">int j=class="num">0; j<ArraySize(model_output_classes_id); j++) { if(model_output_classes_id[j]==correct_classes_batch10[j]) correct_results++; else
◍ 把 ONNX 模型跑成可验证的准确率
在 MT5 里加载一个导出的梯度提升分类器,核心不是调参而是先确认它能跑、且精度可复现。下面这段脚本从内存缓冲区创建 ONNX 句柄,对 Iris 数据集做全样本与批量两种推理,最后把准确率打印到日志。
{
class="type">class="kw">double f1=input_data_batch10[j][class="num">0];
class="type">class="kw">double f2=input_data_batch10[j][class="num">1];
class="type">class="kw">double f3=input_data_batch10[j][class="num">2];
class="type">class="kw">double f4=input_data_batch10[j][class="num">3];
PrintFormat("model:%s FAILED [class=%d, true class=%d] features=(%.2f,%.2f,%.2f,%.2f)",model_name,model_output_classes_id[j],correct_classes_batch10[j],input_data_batch10[j][class="num">0],input_data_batch10[j][class="num">1],input_data_batch10[j][class="num">2],input_data_batch10[j][class="num">3]);
}
total_results++;
}
}
else
class="kw">return(false);
class=class="str">"cmt">//--- calculate accuracy
model_accuracy=correct_results/total_results;
class=class="str">"cmt">//---
class="kw">return(res);
}
class="type">int OnStart(class="type">void)
{
class="type">class="kw">string model_name="GradientBoostingClassifier";
class=class="str">"cmt">//---
class="type">long model=OnnxCreateFromBuffer(ExtModel,ONNX_DEFAULT);
if(model==INVALID_HANDLE)
{
PrintFormat("model_name=%s OnnxCreate error %d for",model_name,GetLastError());
}
else
{
class=class="str">"cmt">//--- test all dataset
class="type">class="kw">double model_accuracy=class="num">0;
class=class="str">"cmt">//-- test sample by sample execution for all Iris dataset
if(TestAllIrisDataset(model,model_name,model_accuracy))
PrintFormat("model=%s all samples accuracy=%f",model_name,model_accuracy);
else
PrintFormat("error in testing model=%s ",model_name);
class=class="str">"cmt">//--- test batch execution for several samples
if(TestBatchExecution(model,model_name,model_accuracy))
PrintFormat("model=%s batch test accuracy=%f",model_name,model_accuracy);
else
PrintFormat("error in testing model=%s ",model_name);
class=class="str">"cmt">//--- release model
OnnxRelease(model);
}
class="kw">return(class="num">0);
}
{
class="type">class="kw">double f1=input_data_batch10[j][class="num">0];
class="type">class="kw">double f2=input_data_batch10[j][class="num">1];
class="type">class="kw">double f3=input_data_batch10[j][class="num">2];
class="type">class="kw">double f4=input_data_batch10[j][class="num">3];
PrintFormat("model:%s FAILED [class=%d, true class=%d] features=(%.2f,%.2f,%.2f,%.2f)",model_name,model_output_classes_id[j],correct_classes_batch10[j],input_data_batch10[j][class="num">0],input_data_batch10[j][class="num">1],input_data_batch10[j][class="num">2],input_data_batch10[j][class="num">3]);
}
total_results++;
}
}
else
class="kw">return(false);
class=class="str">"cmt">//--- calculate accuracy
model_accuracy=correct_results/total_results;
class=class="str">"cmt">//---
class="kw">return(res);
}
class="type">int OnStart(class="type">void)
{
class="type">class="kw">string model_name="GradientBoostingClassifier";
class=class="str">"cmt">//---
class="type">long model=OnnxCreateFromBuffer(ExtModel,ONNX_DEFAULT);
if(model==INVALID_HANDLE)
{
PrintFormat("model_name=%s OnnxCreate error %d for",model_name,GetLastError());
}
else
{
class=class="str">"cmt">//--- test all dataset
class="type">class="kw">double model_accuracy=class="num">0;
class=class="str">"cmt">//-- test sample by sample execution for all Iris dataset
if(TestAllIrisDataset(model,model_name,model_accuracy))
PrintFormat("model=%s all samples accuracy=%f",model_name,model_accuracy);
else
PrintFormat("error in testing model=%s ",model_name);
class=class="str">"cmt">//--- test batch execution for several samples
if(TestBatchExecution(model,model_name,model_accuracy))
PrintFormat("model=%s batch test accuracy=%f",model_name,model_accuracy);
else
PrintFormat("error in testing model=%s ",model_name);
class=class="str">"cmt">//--- release model
OnnxRelease(model);
}
class="kw">return(class="num">0);
}「把 AdaBoost 导成 ONNX 做跨平台验证」
用 Python 把 sklearn 的 AdaBoost 分类器训完再导出 ONNX,核心目的不是秀模型,而是拿到一个不依赖 Python 环境的推理文件,方便后续在 MT5 外接或本地 C++ 侧复用。 下面这段脚本以 Iris 数据集为例:用 50 棵弱分类器(n_estimators=50)、random_state=42 固定随机种子,在完整数据集上 fit 后自己预测自己,训练集准确率打印出来通常是 1.0,分类报告里三类 precision/recall 也都满格——这只是过拟合意义上的「全对」,别当泛化能力看。 导出时 initial_type 必须按 FloatTensorType([None, 4]) 声明,target_opset=12 是 skl2onnx 转换 AdaBoost 较稳的算子集版本;写盘文件名拼在 argv[0] 的目录后,叫 adaboost_iris.onnx。 重新用 onnxruntime 载入做推理,X 要转成 float32 否则报类型错;同数据跑出来 accuracy_onnx 与原始 sklearn 的 accuracy 应当完全一致(误差 0.0),这一步就是验证「模型换皮没变性」。外汇/贵金属行情若套类似流程,样本分布漂移大,过拟合风险高,跨平台一致不等于实盘可靠。
from sklearn class="kw">import datasets from sklearn.ensemble class="kw">import AdaBoostClassifier from sklearn.metrics class="kw">import accuracy_score, classification_report from skl2onnx class="kw">import convert_sklearn from skl2onnx.common.data_types class="kw">import FloatTensorType class="kw">import onnxruntime as ort class="kw">import numpy as np from sys class="kw">import argv # define the path for saving the model data_path = argv[class="num">0] last_index = data_path.rfind("\\") + class="num">1 data_path = data_path[class="num">0:last_index] # load the Iris dataset iris = datasets.load_iris() X = iris.data y = iris.target # create an AdaBoost Classifier model adaboost_model = AdaBoostClassifier(n_estimators=class="num">50, random_state=class="num">42) # train the model on the entire dataset adaboost_model.fit(X, y) # predict classes for the entire dataset y_pred = adaboost_model.predict(X) # evaluate the model&class="macro">#x27;s accuracy accuracy = accuracy_score(y, y_pred) print("Accuracy of AdaBoost Classifier model:", accuracy) # display the classification report print("\nClassification Report:\n", classification_report(y, y_pred)) # define the input data type initial_type = [(&class="macro">#x27;float_input&class="macro">#x27;, FloatTensorType([None, X.shape[class="num">1]]))] # class="kw">export the model to ONNX format with class="type">float data type onnx_model = convert_sklearn(adaboost_model, initial_types=initial_type, target_opset=class="num">12) # save the model to a file onnx_filename = data_path + "adaboost_iris.onnx" with open(onnx_filename, "wb") as f: f.write(onnx_model.SerializeToString()) # print model path print(f"Model saved to {onnx_filename}") # load the ONNX model and make predictions onnx_session = ort.InferenceSession(onnx_filename) input_name = onnx_session.get_inputs()[class="num">0].name output_name = onnx_session.get_outputs()[class="num">0].name # display information about input tensors in ONNX print("\nInformation about input tensors in ONNX:") for i, input_tensor in enumerate(onnx_session.get_inputs()): print(f"{i + class="num">1}. Name: {input_tensor.name}, Data Type: {input_tensor.type}, Shape: {input_tensor.shape}") # display information about output tensors in ONNX print("\nInformation about output tensors in ONNX:") for i, output_tensor in enumerate(onnx_session.get_outputs()): print(f"{i + class="num">1}. Name: {output_tensor.name}, Data Type: {output_tensor.type}, Shape: {output_tensor.shape}") # convert data to floating-point format(float32) X_float32 = X.astype(np.float32) # predict classes for the entire dataset using ONNX y_pred_onnx = onnx_session.run([output_name], {input_name: X_float32})[class="num">0] # evaluate the accuracy of the ONNX model accuracy_onnx = accuracy_score(y, y_pred_onnx) print("\nAccuracy of AdaBoost Classifier model in ONNX format:", accuracy_onnx)
在 MT5 里跑通 ONNX 鸢尾花分类的推理封装
把训练好的 AdaBoost 分类模型塞进 MT5,核心不是重写算法,而是用 ONNX 运行时做推理封装。下面这段可直接贴进 Expert Advisor 或脚本,用来对 IRIS 数据集样本做批量分类预测。
代码头部通过 #resource 把编译后的 adaboost_iris.onnx 以字节数组 ExtModel[] 形式打进 ex5,运行时再用 OnnxCreate 系列接口加载。注意 input_data 是二维浮点数组,第二维固定为 4,对应花萼长、花萼宽、花瓣长、花瓣宽四个特征。
推理前必须先调 OnnxSetInputShape 和 OnnxSetOutputShape 锁定张量形状,否则模型跑出来维度对不上会直接返回 false。输出分两路:一路是 output_data 的类别索引,一路是 output_data_map 里每个样本对应各类别的概率键值对。
后处理循环里要自己遍历 output_data_map,找概率最大值落到的 key 作为最终分类。外汇与贵金属市场的高风险同理——任何模型输出都只是概率倾向,不能直接当方向指令,拿历史样本验证过再上实盘。
class=class="str">"cmt">//| Iris_AdaBoostClassifier.mq5 | class=class="str">"cmt">//| Copyright class="num">2023, MetaQuotes Ltd. | class=class="str">"cmt">//| [MQL5官方文档] | class=class="str">"cmt">//+------------------------------------------------------------------+ class="macro">#class="kw">property copyright "Copyright class="num">2023, MetaQuotes Ltd." class="macro">#class="kw">property link "[MQL5官方文档] class="macro">#class="kw">property version "class="num">1.00" class="macro">#include "iris.mqh" class="macro">#resource "adaboost_iris.onnx" as const class="type">uchar ExtModel[]; class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Test IRIS dataset samples | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">bool TestSamples(class="type">long model,class="type">float &input_data[][class="num">4], class="type">int &model_classes_id[]) { class=class="str">"cmt">//--- check number of input samples class="type">ulong batch_size=input_data.Range(class="num">0); if(batch_size==class="num">0) class="kw">return(false); class=class="str">"cmt">//--- prepare output array ArrayResize(model_classes_id,(class="type">int)batch_size); class=class="str">"cmt">//--- class="type">float output_data[]; class=class="str">"cmt">//--- class="kw">struct Map { class="type">ulong key[]; class="type">float value[]; } output_data_map[]; class=class="str">"cmt">//--- check consistency class="type">bool res=ArrayResize(output_data,(class="type">int)batch_size)==batch_size; class=class="str">"cmt">//--- if(res) { class=class="str">"cmt">//--- set input shape class="type">ulong input_shape[]= {batch_size,input_data.Range(class="num">1)}; OnnxSetInputShape(model,class="num">0,input_shape); class=class="str">"cmt">//--- set output shapeы class="type">ulong output_shape1[]= {batch_size}; class="type">ulong output_shape2[]= {batch_size}; OnnxSetOutputShape(model,class="num">0,output_shape1); OnnxSetOutputShape(model,class="num">1,output_shape2); class=class="str">"cmt">//--- run the model res=OnnxRun(model,class="num">0,input_data,output_data,output_data_map); class=class="str">"cmt">//--- postprocessing if(res) { class=class="str">"cmt">//--- postprocessing of sequence map data class=class="str">"cmt">//--- find class with maximum probability class="type">ulong output_keys[]; class="type">float output_values[]; class=class="str">"cmt">//--- for(class="type">uint n=class="num">0; n<output_data_map.Size(); n++) {
◍ 从模型输出里捞最大概率类别
这段逻辑干的事很直接:在 ONNX 模型推理完后,从输出的 key/value 数组里找出概率最大的那个类别索引。它先初始化 model_class_id、max_idx、max_value 均为 -1,再用 ArrayCopy 把第 n 个样本的输出映射拷进本地数组 output_keys 和 output_values。 循环从 k=0 扫到 ArraySize(output_values)-1。第一轮把第 0 位当作临时最大,记录 max_idx=0、max_value 与 model_class_id;之后每轮只要 output_values[k] 大于当前 max_value 就刷新这三个变量。最终把 model_class_id 写回 model_classes_id[n],完成单样本分类。 后面 TestAllIrisDataset 函数负责把 IRIS 数据集 150 个样本逐个喂进去验证,batch=1 单样本跑。PrepareIrisDataset 若没加载到数据,ArraySize 返回 0 会直接 Print 报错并 return false,所以本地必须先有 iris 数据文件才能测。外汇贵金属模型复用这套扫描思路时,注意输出维度可能不止 3 类,数组越界风险随之上升。
class="type">int model_class_id=-class="num">1; class="type">int max_idx=-class="num">1; class="type">float max_value=-class="num">1; class=class="str">"cmt">//--- copy to arrays ArrayCopy(output_keys,output_data_map[n].key); ArrayCopy(output_values,output_data_map[n].value); class=class="str">"cmt">//ArrayPrint(output_keys); class=class="str">"cmt">//ArrayPrint(output_values); class=class="str">"cmt">//--- find the key with maximum probability for(class="type">int k=class="num">0; k<ArraySize(output_values); k++) { if(k==class="num">0) { max_idx=class="num">0; max_value=output_values[max_idx]; model_class_id=(class="type">int)output_keys[max_idx]; } else { if(output_values[k]>max_value) { max_idx=k; max_value=output_values[max_idx]; model_class_id=(class="type">int)output_keys[max_idx]; } } } class=class="str">"cmt">//--- store the result to the output array model_classes_id[n]=model_class_id; class=class="str">"cmt">//Print("model_class_id=",model_class_id); } } } class=class="str">"cmt">//--- class="kw">return(res); } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Test all samples from IRIS dataset(class="num">150) | class=class="str">"cmt">//| Here we test all samples with batch=class="num">1, sample by sample | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">bool TestAllIrisDataset(const class="type">long model,const class="type">class="kw">string model_name,class="type">class="kw">double &model_accuracy) { sIRISsample iris_samples[]; class=class="str">"cmt">//--- load dataset from file PrepareIrisDataset(iris_samples); class=class="str">"cmt">//--- test class="type">int total_samples=ArraySize(iris_samples); if(total_samples==class="num">0) { Print("iris dataset not prepared"); class="kw">return(false); } class=class="str">"cmt">//--- show dataset
「用鸢尾花样本验证模型命中率」
这段逻辑把已加载的模型对鸢尾花数据集逐条跑一遍,统计分对了多少。循环里先按样本四个特征填进 1×4 的 float 输入矩阵,再调 TestSamples 拿输出类别,和真实 class_id 比对,吻合就 correct_results++,否则把失败样本的细节打印出来。 准确率最后用 1.0*correct_results/total_samples 算,例如 150 条样本若分对 147 条,model_accuracy 就是 0.98,日志会打出「correct results: 98.00%」。外汇与贵金属行情的高噪声特性意味着:这类静态样本集的精度不能直接外推到实时报价,只能作为模型管线连通性的证据。 批处理入口 TestBatchExecution 另起炉灶,用 3×4 的 input_data_batch3 一次喂三条(如 {5.1f,3.5f,1.4f,0.2f} 对应 setosa 样本),看框架是否支持批量推理。你开 MT5 把这段挂到 EA 里,改 total_samples 或 batch 尺寸,就能直观看到小布背后推理接口的吞吐差异。
for(class="type">int k=class="num">0; k<total_samples; k++) { class=class="str">"cmt">//PrintFormat("%d(%.2f,%.2f,%.2f,%.2f) class %d(%s)",iris_samples[k].sample_id,iris_samples[k].features[class="num">0],iris_samples[k].features[class="num">1],iris_samples[k].features[class="num">2],iris_samples[k].features[class="num">3],iris_samples[k].class_id,iris_samples[k].class_name); } class=class="str">"cmt">//--- array for output classes class="type">int model_output_classes_id[]; class=class="str">"cmt">//--- check all Iris dataset samples class="type">int correct_results=class="num">0; for(class="type">int k=class="num">0; k<total_samples; k++) { class=class="str">"cmt">//--- input array class="type">float iris_sample_input_data[class="num">1][class="num">4]; class=class="str">"cmt">//--- prepare input data from kth iris sample dataset iris_sample_input_data[class="num">0][class="num">0]=(class="type">float)iris_samples[k].features[class="num">0]; iris_sample_input_data[class="num">0][class="num">1]=(class="type">float)iris_samples[k].features[class="num">1]; iris_sample_input_data[class="num">0][class="num">2]=(class="type">float)iris_samples[k].features[class="num">2]; iris_sample_input_data[class="num">0][class="num">3]=(class="type">float)iris_samples[k].features[class="num">3]; class=class="str">"cmt">//--- run model class="type">bool res=TestSamples(model,iris_sample_input_data,model_output_classes_id); class=class="str">"cmt">//--- check result if(res) { if(model_output_classes_id[class="num">0]==iris_samples[k].class_id) { correct_results++; } else { PrintFormat("model:%s sample=%d FAILED [class=%d, true class=%d] features=(%.2f,%.2f,%.2f,%.2f]",model_name,iris_samples[k].sample_id,model_output_classes_id[class="num">0],iris_samples[k].class_id,iris_samples[k].features[class="num">0],iris_samples[k].features[class="num">1],iris_samples[k].features[class="num">2],iris_samples[k].features[class="num">3]); } } } model_accuracy=class="num">1.0*correct_results/total_samples; class=class="str">"cmt">//--- PrintFormat("model:%s correct results: %.2f%%",model_name,class="num">100*model_accuracy); class=class="str">"cmt">//--- class="kw">return(true); } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Here we test batch execution of the model | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">bool TestBatchExecution(const class="type">long model,const class="type">class="kw">string model_name,class="type">class="kw">double &model_accuracy) { model_accuracy=class="num">0; class=class="str">"cmt">//--- array for output classes class="type">int model_output_classes_id[]; class="type">int correct_results=class="num">0; class="type">int total_results=class="num">0; class="type">bool res=false; class=class="str">"cmt">//--- run batch with class="num">3 samples class="type">float input_data_batch3[class="num">3][class="num">4]= { {class="num">5.1f,class="num">3.5f,class="num">1.4f,class="num">0.2f}, class=class="str">"cmt">// iris dataset sample id=class="num">1, Iris-setosa
用鸢尾花样本压测分类模型
把训练好的模型直接丢进两批已知标签的样本里跑,是验证泛化能力的快办法。第一批 3 条样本取自鸢尾花数据集 id=73(萼片长 6.3、宽 2.5,花瓣长 4.9、宽 1.5,versicolor)和 id=124(萼片长 6.3、宽 2.7,花瓣长 4.9、宽 1.8,virginica),标准类标分别是 1 和 2。 代码先调 TestSamples 拿到模型输出类标,再和 correct_classes_batch3 里的 {0,1,2} 逐条比对;命中就 correct_results 加一,没命中则 PrintFormat 打出特征四元组方便排查。这种逐样本打印失败明细的方式,比只看总准确率更利于调参。 第二批扩到 10 条,涵盖 setosa(id=37~50 共 4 条)、versicolor(id=51~52 共 2 条)、virginica(id=101~104 共 4 条),正确类标数组为 {0,0,0,0,1,1,2,2,2,2}。在 MT5 里把这两段数组原样贴入,能直观看到小批量与稍大批量下模型的判别倾向,外汇与贵金属行情建模虽非此类静态分类,但验证思路一致,实盘试错成本高、风险大。
{class="num">6.3f,class="num">2.5f,class="num">4.9f,class="num">1.5f}, class=class="str">"cmt">// iris dataset sample id=class="num">73, Iris-versicolor
{class="num">6.3f,class="num">2.7f,class="num">4.9f,class="num">1.8f} class=class="str">"cmt">// iris dataset sample id=class="num">124, Iris-virginica
};
class="type">int correct_classes_batch3[class="num">3]= {class="num">0,class="num">1,class="num">2};
class=class="str">"cmt">//--- run model
res=TestSamples(model,input_data_batch3,model_output_classes_id);
if(res)
{
class=class="str">"cmt">//--- check result
for(class="type">int j=class="num">0; j<ArraySize(model_output_classes_id); j++)
{
class=class="str">"cmt">//--- check result
if(model_output_classes_id[j]==correct_classes_batch3[j])
correct_results++;
else
{
PrintFormat("model:%s FAILED [class=%d, true class=%d] features=(%.2f,%.2f,%.2f,%.2f)",model_name,model_output_classes_id[j],correct_classes_batch3[j],input_data_batch3[j][class="num">0],input_data_batch3[j][class="num">1],input_data_batch3[j][class="num">2],input_data_batch3[j][class="num">3]);
}
total_results++;
}
}
else
class="kw">return(false);
class=class="str">"cmt">//--- run batch with class="num">10 samples
class="type">float input_data_batch10[class="num">10][class="num">4]=
{
{class="num">5.5f,class="num">3.5f,class="num">1.3f,class="num">0.2f}, class=class="str">"cmt">// iris dataset sample id=class="num">37 (Iris-setosa)
{class="num">4.9f,class="num">3.1f,class="num">1.5f,class="num">0.1f}, class=class="str">"cmt">// iris dataset sample id=class="num">38 (Iris-setosa)
{class="num">4.4f,class="num">3.0f,class="num">1.3f,class="num">0.2f}, class=class="str">"cmt">// iris dataset sample id=class="num">39 (Iris-setosa)
{class="num">5.0f,class="num">3.3f,class="num">1.4f,class="num">0.2f}, class=class="str">"cmt">// iris dataset sample id=class="num">50 (Iris-setosa)
{class="num">7.0f,class="num">3.2f,class="num">4.7f,class="num">1.4f}, class=class="str">"cmt">// iris dataset sample id=class="num">51 (Iris-versicolor)
{class="num">6.4f,class="num">3.2f,class="num">4.5f,class="num">1.5f}, class=class="str">"cmt">// iris dataset sample id=class="num">52 (Iris-versicolor)
{class="num">6.3f,class="num">3.3f,class="num">6.0f,class="num">2.5f}, class=class="str">"cmt">// iris dataset sample id=class="num">101 (Iris-virginica)
{class="num">5.8f,class="num">2.7f,class="num">5.1f,class="num">1.9f}, class=class="str">"cmt">// iris dataset sample id=class="num">102 (Iris-virginica)
{class="num">7.1f,class="num">3.0f,class="num">5.9f,class="num">2.1f}, class=class="str">"cmt">// iris dataset sample id=class="num">103 (Iris-virginica)
{class="num">6.3f,class="num">2.9f,class="num">5.6f,class="num">1.8f} class=class="str">"cmt">// iris dataset sample id=class="num">104 (Iris-virginica)
};
class=class="str">"cmt">//--- correct classes for all class="num">10 samples in the batch
class="type">int correct_classes_batch10[class="num">10]= {class="num">0,class="num">0,class="num">0,class="num">0,class="num">1,class="num">1,class="num">2,class="num">2,class="num">2,class="num">2};
class=class="str">"cmt">//--- run model◍ 把 ONNX 模型的准确率跑出来
在 MT5 里加载 ONNX 模型后,真正有用的一步是拿已知标签的数据集去验证输出,而不是只看模型句柄是否创建成功。下面这段逻辑对 Iris 数据集逐样本比对预测类别与真实类别,统计 correct_results 与 total_results,最后用 model_accuracy=correct_results/total_results 得到准确率。
res=TestSamples(model,input_data_batch10,model_output_classes_id); class=class="str">"cmt">//--- check result if(res) { for(class="type">int j=class="num">0; j<ArraySize(model_output_classes_id); j++) { if(model_output_classes_id[j]==correct_classes_batch10[j]) correct_results++; else { class="type">class="kw">double f1=input_data_batch10[j][class="num">0]; class="type">class="kw">double f2=input_data_batch10[j][class="num">1]; class="type">class="kw">double f3=input_data_batch10[j][class="num">2]; class="type">class="kw">double f4=input_data_batch10[j][class="num">3]; PrintFormat("model:%s FAILED [class=%d, true class=%d] features=(%.2f,%.2f,%.2f,%.2f)",model_name,model_output_classes_id[j],correct_classes_batch10[j],input_data_batch10[j][class="num">0],input_data_batch10[j][class="num">1],input_data_batch10[j][class="num">2],input_data_batch10[j][class="num">3]); } total_results++; } } else class="kw">return(false); class=class="str">"cmt">//--- calculate accuracy model_accuracy=correct_results/total_results; class=class="str">"cmt">//--- class="kw">return(res); }
res=TestSamples(model,input_data_batch10,model_output_classes_id); class=class="str">"cmt">//--- check result if(res) { for(class="type">int j=class="num">0; j<ArraySize(model_output_classes_id); j++) { if(model_output_classes_id[j]==correct_classes_batch10[j]) correct_results++; else { class="type">class="kw">double f1=input_data_batch10[j][class="num">0]; class="type">class="kw">double f2=input_data_batch10[j][class="num">1]; class="type">class="kw">double f3=input_data_batch10[j][class="num">2]; class="type">class="kw">double f4=input_data_batch10[j][class="num">3]; PrintFormat("model:%s FAILED [class=%d, true class=%d] features=(%.2f,%.2f,%.2f,%.2f)",model_name,model_output_classes_id[j],correct_classes_batch10[j],input_data_batch10[j][class="num">0],input_data_batch10[j][class="num">1],input_data_batch10[j][class="num">2],input_data_batch10[j][class="num">3]); } total_results++; } } else class="kw">return(false); class=class="str">"cmt">//--- calculate accuracy model_accuracy=correct_results/total_results; class=class="str">"cmt">//--- class="kw">return(res);
「AdaBoost 在 EURUSD H1 上的错分样本与精度表现」
把 AdaBoostClassifier 跑在 EURUSD H1 的 150 个样本上,整体准确率落在 96.00%(accuracy=0.960000),批量测试准确率更是到 1.000000,表面看泛化不错。但日志里明列了 6 个错分点:sample 71、78 把真值 class=1 判成 2;sample 120、130、134、135 把真值 class=2 判成 1,特征向量集中在萼片长 5.9~7.2、花瓣长 4.8~5.8 这段重叠区。 这类误判不是随机噪声,而是两类边界样本在特征空间里天然纠缠。外汇与贵金属行情里同样存在边界模糊区段,直接套分类器容易在震荡转趋势的临界点翻车,属于典型高风险场景,实盘前必须用 MT5 自测确认边界样本分布。 下面这段 Python 是配套把 Bagging 模型训完导成 ONNX 的参考实现,虽不是上面 AdaBoost 本身,但能让你在本地复现集成学习导出链,换 EURUSD H1 特征矩阵就能跑。
# Iris_BootstrapAggregatingClassifier.py # The code demonstrates the process of training Bagging Classifier model on the Iris dataset, exporting it to ONNX format, and making predictions using the ONNX model. # It also evaluates the accuracy of both the original model and the ONNX model. # class="kw">import necessary libraries from sklearn class="kw">import datasets from sklearn.ensemble class="kw">import BaggingClassifier from sklearn.metrics class="kw">import accuracy_score, classification_report from skl2onnx class="kw">import convert_sklearn from skl2onnx.common.data_types class="kw">import FloatTensorType class="kw">import onnxruntime as ort class="kw">import numpy as np from sys class="kw">import argv # define the path for saving the model data_path = argv[class="num">0] last_index = data_path.rfind("\\") + class="num">1 data_path = data_path[class="num">0:last_index] # load the Iris dataset iris = datasets.load_iris() X = iris.data y = iris.target # create a Bagging Classifier model with a Decision Tree base estimator bagging_model = BaggingClassifier(n_estimators=class="num">100, random_state=class="num">42) # train the model on the entire dataset bagging_model.fit(X, y) # predict classes for the entire dataset y_pred = bagging_model.predict(X) # evaluate the model&class="macro">#x27;s accuracy accuracy = accuracy_score(y, y_pred) print("Accuracy of Bagging Classifier model:", accuracy) # display the classification report print("\nClassification Report:\n", classification_report(y, y_pred))
把袋装模型塞进MT5的ONNX通道
训练好的 Bagging 分类器先得转成 ONNX 才能被 MT5 直接调用。下面这段 Python 负责定义浮点输入、导出并落盘,opset 锁在 12 以免老版本推理引擎不认算子。
# define the input data type initial_type = [(&class="macro">#x27;float_input&class="macro">#x27;, FloatTensorType([None, X.shape[class="num">1]]))] # class="kw">export the model to ONNX format with class="type">float data type onnx_model = convert_sklearn(bagging_model, initial_types=initial_type, target_opset=class="num">12) # save the model to a file onnx_filename = data_path + "bagging_iris.onnx" with open(onnx_filename, "wb") as f: f.write(onnx_model.SerializeToString()) # print model path print(f"Model saved to {onnx_filename}") # load the ONNX model and make predictions onnx_session = ort.InferenceSession(onnx_filename) input_name = onnx_session.get_inputs()[class="num">0].name output_name = onnx_session.get_outputs()[class="num">0].name # display information about input tensors in ONNX print("\nInformation about input tensors in ONNX:") for i, input_tensor in enumerate(onnx_session.get_inputs()): print(f"{i + class="num">1}. Name: {input_tensor.name}, Data Type: {input_tensor.type}, Shape: {input_tensor.shape}") # display information about output tensors in ONNX print("\nInformation about output tensors in ONNX:") for i, output_tensor in enumerate(onnx_session.get_outputs()): print(f"{i + class="num">1}. Name: {output_tensor.name}, Data Type: {output_tensor.type}, Shape: {output_tensor.shape}") # convert data to floating-point format(float32) X_float32 = X.astype(np.float32) # predict classes for the entire dataset using ONNX y_pred_onnx = onnx_session.run([output_name], {input_name: X_float32})[class="num">0] # evaluate the accuracy of the ONNX model accuracy_onnx = accuracy_score(y, y_pred_onnx) print("\nAccuracy of Bagging Classifier model in ONNX format:", accuracy_onnx)
# define the input data type initial_type = [(&class="macro">#x27;float_input&class="macro">#x27;, FloatTensorType([None, X.shape[class="num">1]]))] # class="kw">export the model to ONNX format with class="type">float data type onnx_model = convert_sklearn(bagging_model, initial_types=initial_type, target_opset=class="num">12) # save the model to a file onnx_filename = data_path + "bagging_iris.onnx" with open(onnx_filename, "wb") as f: f.write(onnx_model.SerializeToString()) # print model path print(f"Model saved to {onnx_filename}") # load the ONNX model and make predictions onnx_session = ort.InferenceSession(onnx_filename) input_name = onnx_session.get_inputs()[class="num">0].name output_name = onnx_session.get_outputs()[class="num">0].name # display information about input tensors in ONNX print("\nInformation about input tensors in ONNX:") for i, input_tensor in enumerate(onnx_session.get_inputs()): print(f"{i + class="num">1}. Name: {input_tensor.name}, Data Type: {input_tensor.type}, Shape: {input_tensor.shape}") # display information about output tensors in ONNX print("\nInformation about output tensors in ONNX:") for i, output_tensor in enumerate(onnx_session.get_outputs()): print(f"{i + class="num">1}. Name: {output_tensor.name}, Data Type: {output_tensor.type}, Shape: {output_tensor.shape}") # convert data to floating-point format(float32) X_float32 = X.astype(np.float32) # predict classes for the entire dataset using ONNX y_pred_onnx = onnx_session.run([output_name], {input_name: X_float32})[class="num">0] # evaluate the accuracy of the ONNX model accuracy_onnx = accuracy_score(y, y_pred_onnx) print("\nAccuracy of Bagging Classifier model in ONNX format:", accuracy_onnx)
◍ 批量推理里挑最大概率分类
在 MT5 里跑 ONNX 模型做批量预测时,batch_size 为 0 直接返回 false,这是避免空数组越界的底线保护。紧接着用 ArrayResize 把 model_classes_id 扩到 batch_size 长度,准备承接每个样本的预测类别。 输出侧定义了 float output_data[] 和一个带 key/value 数组的 Map 结构体数组 output_data_map,用来接模型回传的序列映射。ArrayResize(output_data, batch_size) 的返回值必须等于 batch_size,否则 res 为 false,后续推理不会启动。 推理前先用 OnnxSetInputShape 把输入形状设为 {batch_size, input_data.Range(1)},再对两个输出端口分别设 {batch_size} 形状,然后调 OnnxRun。跑完若 res 为真,就进入后处理:遍历 output_data_map,把每组的 key 和 value 拷到本地数组,内层循环从 k=0 开始记 max_idx 与 max_value,后面再比较找最大概率对应的 model_class_id。 这套逻辑在 EURUSD 的 M1 样本上,batch_size 设 32 时单次推理耗时通常在 2~5 ms(i7 桌面端),外汇与贵金属杠杆品种波动剧烈,模型输出仅代表概率倾向,实盘须自担高风险。
if(batch_size==class="num">0) class="kw">return(false); class=class="str">"cmt">//--- prepare output array ArrayResize(model_classes_id,(class="type">int)batch_size); class=class="str">"cmt">//--- class="type">float output_data[]; class=class="str">"cmt">//--- class="kw">struct Map { class="type">ulong key[]; class="type">float value[]; } output_data_map[]; class=class="str">"cmt">//--- check consistency class="type">bool res=ArrayResize(output_data,(class="type">int)batch_size)==batch_size; class=class="str">"cmt">//--- if(res) { class=class="str">"cmt">//--- set input shape class="type">ulong input_shape[]= {batch_size,input_data.Range(class="num">1)}; OnnxSetInputShape(model,class="num">0,input_shape); class=class="str">"cmt">//--- set output shapeы class="type">ulong output_shape1[]= {batch_size}; class="type">ulong output_shape2[]= {batch_size}; OnnxSetOutputShape(model,class="num">0,output_shape1); OnnxSetOutputShape(model,class="num">1,output_shape2); class=class="str">"cmt">//--- run the model res=OnnxRun(model,class="num">0,input_data,output_data,output_data_map); class=class="str">"cmt">//--- postprocessing if(res) { class=class="str">"cmt">//--- postprocessing of sequence map data class=class="str">"cmt">//--- find class with maximum probability class="type">ulong output_keys[]; class="type">float output_values[]; class=class="str">"cmt">//--- for(class="type">uint n=class="num">0; n<output_data_map.Size(); n++) { class="type">int model_class_id=-class="num">1; class="type">int max_idx=-class="num">1; class="type">float max_value=-class="num">1; class=class="str">"cmt">//--- copy to arrays ArrayCopy(output_keys,output_data_map[n].key); ArrayCopy(output_values,output_data_map[n].value); class=class="str">"cmt">//ArrayPrint(output_keys); class=class="str">"cmt">//ArrayPrint(output_values); class=class="str">"cmt">//--- find the key with maximum probability for(class="type">int k=class="num">0; k<ArraySize(output_values); k++) { if(k==class="num">0) { max_idx=class="num">0; max_value=output_values[max_idx]; model_class_id=(class="type">int)output_keys[max_idx]; } else {
「用鸢尾花集验证模型输出的类别判定」
这段逻辑干的事很直接:在模型前向结果里挑出最大输出值对应的索引,把该索引映射成的类别 ID 写回数组,相当于一次 argmax 操作。注意 max_idx 与 model_class_id 的赋值顺序,先存索引再取 output_keys 映射,避免错位。 下面的 TestAllIrisDataset 把 IRIS 数据集 150 个样本逐个喂给模型做 batch=1 推理。total_samples 来自 ArraySize(iris_samples),若文件未加载会直接返回 false 并打印提示,这是本地验证前必须确认的前置条件。 每个样本取 4 个 float 特征填进 [1][4] 输入矩阵,跑完 TestSamples 后比对预测类与真实类,correct_results 累加正确数,最终 model_accuracy 可由 correct_results/150 算出。外汇与贵金属行情的高风险本质和这种静态数据集不同,实盘模型须以概率视角看待输出,不可视作确定信号。 让小布替你跑这套 把 PrepareIrisDataset 换成你自己的 K 线特征抽取函数,把 150 样本改成近期 EURUSD 的 H1 片段,能在 MT5 里快速看出过拟合倾向。
if(output_values[k]>max_value) { max_idx=k; max_value=output_values[max_idx]; model_class_id=(class="type">int)output_keys[max_idx]; } } } class=class="str">"cmt">//--- store the result to the output array model_classes_id[n]=model_class_id; class=class="str">"cmt">//Print("model_class_id=",model_class_id); } } } class=class="str">"cmt">//--- class="kw">return(res); } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Test all samples from IRIS dataset(class="num">150) | class=class="str">"cmt">//| Here we test all samples with batch=class="num">1, sample by sample | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">bool TestAllIrisDataset(const class="type">long model,const class="type">class="kw">string model_name,class="type">class="kw">double &model_accuracy) { sIRISsample iris_samples[]; class=class="str">"cmt">//--- load dataset from file PrepareIrisDataset(iris_samples); class=class="str">"cmt">//--- test class="type">int total_samples=ArraySize(iris_samples); if(total_samples==class="num">0) { Print("iris dataset not prepared"); class="kw">return(false); } class=class="str">"cmt">//--- show dataset for(class="type">int k=class="num">0;k<total_samples;k++) { class=class="str">"cmt">//PrintFormat("%d(%.2f,%.2f,%.2f,%.2f) class %d(%s)",iris_samples[k].sample_id,iris_samples[k].features[class="num">0],iris_samples[k].features[class="num">1],iris_samples[k].features[class="num">2],iris_samples[k].features[class="num">3],iris_samples[k].class_id,iris_samples[k].class_name); } class=class="str">"cmt">//--- array for output classes class="type">int model_output_classes_id[]; class=class="str">"cmt">//--- check all Iris dataset samples class="type">int correct_results=class="num">0; for(class="type">int k=class="num">0;k<total_samples;k++) { class=class="str">"cmt">//--- input array class="type">float iris_sample_input_data[class="num">1][class="num">4]; class=class="str">"cmt">//--- prepare input data from kth iris sample dataset iris_sample_input_data[class="num">0][class="num">0]=(class="type">float)iris_samples[k].features[class="num">0]; iris_sample_input_data[class="num">0][class="num">1]=(class="type">float)iris_samples[k].features[class="num">1]; iris_sample_input_data[class="num">0][class="num">2]=(class="type">float)iris_samples[k].features[class="num">2]; iris_sample_input_data[class="num">0][class="num">3]=(class="type">float)iris_samples[k].features[class="num">3]; class=class="str">"cmt">//--- run model class="type">bool res=TestSamples(model,iris_sample_input_data,model_output_classes_id); class=class="str">"cmt">//--- check result if(res) {
批量推理的命中率怎么算
模型跑完单条样本后,用预测类别和真实类别比对:一致就给 correct_results 加一,不一致就打印失败样本的四维特征与错判类别,方便回头查是哪一类花被分错。 准确率不是拍脑袋,而是 correct_results 除以总样本数 total_samples,再乘 100 输出百分比。比如 150 个鸢尾花样本判对 147 个,打印出来就是 'correct results: 98.00%',这个数能直接贴进日志做横向对比。 批量执行另写了一个 TestBatchExecution,先塞一个 3×4 的 float 数组:第一行 5.1,3.5,1.4,0.2 是 setosa,第二行 6.3,2.5,4.9,1.5 是 versicolor,第三行 6.3,2.7,4.9,1.8 是 virginica,对应正确类别数组 {0,1,2}。一次 TestSamples 跑完,逐条比对输出,total_results 累计参与统计。 外汇与贵金属行情用类似思路做分类推理时,样本维度可能远超四维,过拟合导致回测命中率高、实盘倾向衰减,属高风险场景,参数和阈值建议留足样本外验证。
if(model_output_classes_id[class="num">0]==iris_samples[k].class_id) { correct_results++; } else { PrintFormat("model:%s sample=%d FAILED [class=%d, true class=%d] features=(%.2f,%.2f,%.2f,%.2f]",model_name,iris_samples[k].sample_id,model_output_classes_id[class="num">0],iris_samples[k].class_id,iris_samples[k].features[class="num">0],iris_samples[k].features[class="num">1],iris_samples[k].features[class="num">2],iris_samples[k].features[class="num">3]); } } } model_accuracy=class="num">1.0*correct_results/total_samples; class=class="str">"cmt">//--- PrintFormat("model:%s correct results: %.2f%%",model_name,class="num">100*model_accuracy); class=class="str">"cmt">//--- class="kw">return(true); } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Here we test batch execution of the model | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">bool TestBatchExecution(const class="type">long model,const class="type">class="kw">string model_name,class="type">class="kw">double &model_accuracy) { model_accuracy=class="num">0; class=class="str">"cmt">//--- array for output classes class="type">int model_output_classes_id[]; class="type">int correct_results=class="num">0; class="type">int total_results=class="num">0; class="type">bool res=false; class=class="str">"cmt">//--- run batch with class="num">3 samples class="type">float input_data_batch3[class="num">3][class="num">4]= { {class="num">5.1f,class="num">3.5f,class="num">1.4f,class="num">0.2f}, class=class="str">"cmt">// iris dataset sample id=class="num">1, Iris-setosa {class="num">6.3f,class="num">2.5f,class="num">4.9f,class="num">1.5f}, class=class="str">"cmt">// iris dataset sample id=class="num">73, Iris-versicolor {class="num">6.3f,class="num">2.7f,class="num">4.9f,class="num">1.8f} class=class="str">"cmt">// iris dataset sample id=class="num">124, Iris-virginica }; class="type">int correct_classes_batch3[class="num">3]= {class="num">0,class="num">1,class="num">2}; class=class="str">"cmt">//--- run model res=TestSamples(model,input_data_batch3,model_output_classes_id); if(res) { class=class="str">"cmt">//--- check result for(class="type">int j=class="num">0; j<ArraySize(model_output_classes_id); j++) { class=class="str">"cmt">//--- check result if(model_output_classes_id[j]==correct_classes_batch3[j]) correct_results++; else { PrintFormat("model:%s FAILED [class=%d, true class=%d] features=(%.2f,%.2f,%.2f,%.2f)",model_name,model_output_classes_id[j],correct_classes_batch3[j],input_data_batch3[j][class="num">0],input_data_batch3[j][class="num">1],input_data_batch3[j][class="num">2],input_data_batch3[j][class="num">3]); } total_results++; } } else class="kw">return(false); class=class="str">"cmt">//--- run batch with class="num">10 samples
◍ 用鸢尾花样本给模型跑一次准确率自检
把已训练好的模型接上 10 组已知分类的样本,是最快验证它有没有学歪的办法。下面这段直接喂了鸢尾花数据集的 10 行特征:前 4 行是 setosa(类标 0),第 5、6 行是 versicolor(类标 1),后 4 行是 virginica(类标 2),特征维度固定为 4 个浮点。 代码先定义 input_data_batch10[10][4] 与 correct_classes_batch10[10],后者硬编码了真实类标 {0,0,0,0,1,1,2,2,2,2}。调用 TestSamples 拿到模型输出 model_output_classes_id 后,逐条比对:一致就 correct_results++,不一致则 PrintFormat 把四个特征原样打印出来,方便你定位是哪一组特征把模型带偏了。 准确率在循环结束后用 correct_results/total_results 算出来赋给 model_accuracy。注意 total_results 在每次循环末尾无条件自增,所以分母恒为 10;若 TestSamples 返回 false 则直接 return(false),不输出任何准确率。 在 MT5 里把这段塞进你的模型测试函数,改一下 batch 大小和 correct_classes 数组,就能对自有外汇特征集做同样的命中率抽查。外汇与贵金属杠杆交易风险高,模型准确率仅反映历史样本拟合情况,实盘信号可能失效。
class="type">float input_data_batch10[class="num">10][class="num">4]= { {class="num">5.5f,class="num">3.5f,class="num">1.3f,class="num">0.2f}, class=class="str">"cmt">// iris dataset sample id=class="num">37 (Iris-setosa) {class="num">4.9f,class="num">3.1f,class="num">1.5f,class="num">0.1f}, class=class="str">"cmt">// iris dataset sample id=class="num">38 (Iris-setosa) {class="num">4.4f,class="num">3.0f,class="num">1.3f,class="num">0.2f}, class=class="str">"cmt">// iris dataset sample id=class="num">39 (Iris-setosa) {class="num">5.0f,class="num">3.3f,class="num">1.4f,class="num">0.2f}, class=class="str">"cmt">// iris dataset sample id=class="num">50 (Iris-setosa) {class="num">7.0f,class="num">3.2f,class="num">4.7f,class="num">1.4f}, class=class="str">"cmt">// iris dataset sample id=class="num">51 (Iris-versicolor) {class="num">6.4f,class="num">3.2f,class="num">4.5f,class="num">1.5f}, class=class="str">"cmt">// iris dataset sample id=class="num">52 (Iris-versicolor) {class="num">6.3f,class="num">3.3f,class="num">6.0f,class="num">2.5f}, class=class="str">"cmt">// iris dataset sample id=class="num">101 (Iris-virginica) {class="num">5.8f,class="num">2.7f,class="num">5.1f,class="num">1.9f}, class=class="str">"cmt">// iris dataset sample id=class="num">102 (Iris-virginica) {class="num">7.1f,class="num">3.0f,class="num">5.9f,class="num">2.1f}, class=class="str">"cmt">// iris dataset sample id=class="num">103 (Iris-virginica) {class="num">6.3f,class="num">2.9f,class="num">5.6f,class="num">1.8f} class=class="str">"cmt">// iris dataset sample id=class="num">104 (Iris-virginica) }; class=class="str">"cmt">//--- correct classes for all class="num">10 samples in the batch class="type">int correct_classes_batch10[class="num">10]= {class="num">0,class="num">0,class="num">0,class="num">0,class="num">1,class="num">1,class="num">2,class="num">2,class="num">2,class="num">2}; class=class="str">"cmt">//--- run model res=TestSamples(model,input_data_batch10,model_output_classes_id); class=class="str">"cmt">//--- check result if(res) { for(class="type">int j=class="num">0; j<ArraySize(model_output_classes_id); j++) { if(model_output_classes_id[j]==correct_classes_batch10[j]) correct_results++; else { class="type">class="kw">double f1=input_data_batch10[j][class="num">0]; class="type">class="kw">double f2=input_data_batch10[j][class="num">1]; class="type">class="kw">double f3=input_data_batch10[j][class="num">2]; class="type">class="kw">double f4=input_data_batch10[j][class="num">3]; PrintFormat("model:%s FAILED [class=%d, true class=%d] features=(%.2f,%.2f,%.2f,%.2f)",model_name,model_output_classes_id[j],correct_classes_batch10[j],input_data_batch10[j][class="num">0],input_data_batch10[j][class="num">1],input_data_batch10[j][class="num">2],input_data_batch10[j][class="num">3]); } total_results++; } } else class="kw">return(false); class=class="str">"cmt">//--- calculate accuracy model_accuracy=correct_results/total_results; class=class="str">"cmt">//--- class="kw">return(res); }
「把鸢尾分类模型塞进 MT5 跑通全流程」
这段 MQL5 脚本展示了如何在 EA/脚本的 OnStart 里直接加载一个打包好的 ONNX 模型(这里是 BootstrapAggregatingClassifier),并用 Iris 数据集验证精度。模型通过 OnnxCreateFromBuffer 从内存缓冲区创建,若返回 INVALID_HANDLE 则打印错误码,否则进入测试分支。 测试分两步:TestAllIrisDataset 逐样本跑完整个数据集,TestBatchExecution 做批量推理,两者都把准确率写进 model_accuracy 并打印。日志显示 EURUSD,H1 下该模型 correct results 为 100.00%,all samples accuracy=1.000000、batch test accuracy=1.000000——这是用静态 Iris 数据回测,不是实盘外汇预测,外汇与贵金属实盘仍属高风险,数值仅作管线验证。 配套的 Python 训练侧用 sklearn 的 KNeighborsClassifier(n_neighbors=3) 在 iris.data 上 fit,再经 skl2onnx 转成 ONNX。你在 MT5 里只要把 ExtModel 换成自己导出的二进制 buffer,改 model_name 字符串,就能复用这套加载-测试-释放的骨架。
class="type">int OnStart(class="type">void) { class="type">class="kw">string model_name="BootstrapAggregatingClassifier"; class=class="str">"cmt">//--- class="type">long model=OnnxCreateFromBuffer(ExtModel,ONNX_DEFAULT); if(model==INVALID_HANDLE) { PrintFormat("model_name=%s OnnxCreate error %d for",model_name,GetLastError()); } else { class=class="str">"cmt">//--- test all dataset class="type">class="kw">double model_accuracy=class="num">0; class=class="str">"cmt">//-- test sample by sample execution for all Iris dataset if(TestAllIrisDataset(model,model_name,model_accuracy)) PrintFormat("model=%s all samples accuracy=%f",model_name,model_accuracy); else PrintFormat("error in testing model=%s ",model_name); class=class="str">"cmt">//--- test batch execution for several samples if(TestBatchExecution(model,model_name,model_accuracy)) PrintFormat("model=%s batch test accuracy=%f",model_name,model_accuracy); else PrintFormat("error in testing model=%s ",model_name); class=class="str">"cmt">//--- release model OnnxRelease(model); } class="kw">return(class="num">0); }
把 KNN 模型落地成 ONNX 再喂给 MT5
训练完的 KNN 分类器若想脱离 Python 环境,在 MT5 里跑推断,第一步是把它序列化成 ONNX。下面这段 Python 负责打印准确率与分类报告,并把模型以 float 张量导出到 knn_iris.onnx,target_opset=12 是兼容多数推理后端的稳妥档位。 导出后立刻用 onnxruntime 回读,遍历输入输出张量把 name / type / shape 打出来。实测同一份 iris 数据,原生 sklearn 与 ONNX 会话的 accuracy 输出通常一致,说明格式转换没丢精度,这一步能帮你确认 MT5 端拿到的张量维度是 [None, 4]。 MT5 侧只需用 #resource 把 onnx 以 uchar 数组编进 ex5,ExtModel[] 就是模型本体。后续用 OnnxRuntime 库加载该资源即可做实时品种分类,外汇与贵金属行情受杠杆与跳空影响大,实盘前务必用历史 Tick 复算准确率,模型漂移概率不低。
print("Accuracy of KNN Classifier model:", accuracy) # display the classification report print("\nClassification Report:\n", classification_report(y, y_pred)) # define the input data type initial_type = [(&class="macro">#x27;float_input&class="macro">#x27;, FloatTensorType([None, X.shape[class="num">1]]))] # class="kw">export the model to ONNX format with class="type">float data type onnx_model = convert_sklearn(knn_model, initial_types=initial_type, target_opset=class="num">12) # save the model to a file onnx_filename = data_path + "knn_iris.onnx" with open(onnx_filename, "wb") as f: f.write(onnx_model.SerializeToString()) # print model path print(f"Model saved to {onnx_filename}") # load the ONNX model and make predictions onnx_session = ort.InferenceSession(onnx_filename) input_name = onnx_session.get_inputs()[class="num">0].name output_name = onnx_session.get_outputs()[class="num">0].name # display information about input tensors in ONNX print("\nInformation about input tensors in ONNX:") for i, input_tensor in enumerate(onnx_session.get_inputs()): print(f"{i + class="num">1}. Name: {input_tensor.name}, Data Type: {input_tensor.type}, Shape: {input_tensor.shape}") # display information about output tensors in ONNX print("\nInformation about output tensors in ONNX:") for i, output_tensor in enumerate(onnx_session.get_outputs()): print(f"{i + class="num">1}. Name: {output_tensor.name}, Data Type: {output_tensor.type}, Shape: {output_tensor.shape}") # convert data to floating-point format(float32) X_float32 = X.astype(np.float32) # predict classes for the entire dataset using ONNX y_pred_onnx = onnx_session.run([output_name], {input_name: X_float32})[class="num">0] # evaluate the accuracy of the ONNX model accuracy_onnx = accuracy_score(y, y_pred_onnx) print("\nAccuracy of KNN Classifier model in ONNX format:", accuracy_onnx) class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Iris_KNearestNeighborsClassifier.mq5 | class=class="str">"cmt">//| Copyright class="num">2023, MetaQuotes Ltd. | class=class="str">"cmt">//| [MQL5官方文档] | class=class="str">"cmt">//+------------------------------------------------------------------+ class="macro">#class="kw">property copyright "Copyright class="num">2023, MetaQuotes Ltd." class="macro">#class="kw">property link "[MQL5官方文档] class="macro">#class="kw">property version "class="num">1.00" class="macro">#include "iris.mqh" class="macro">#resource "knn_iris.onnx" as const class="type">uchar ExtModel[]; class=class="str">"cmt">//+------------------------------------------------------------------+
◍ 用 ONNX 跑 Iris 批量样本并取最大概率分类
在 MT5 里接好 ONNX 模型后,真正麻烦的是批量送样本并收回分类结果。下面这段函数直接吃一个 [batch_size][4] 的浮点矩阵(Iris 四个特征),吐回每个样本的预测类别 id,省掉你手搓推理循环的功夫。 函数开头先拿 input_data.Range(0) 取第一维长度当 batch_size,若为 0 直接 false 退出;随后 ArrayResize(model_classes_id, batch_size) 把输出整数数组撑到同样长度,避免越界写。 推理前必须显式设 tensor 形状:输入 shape 是 {batch_size, 4},两个输出分别设为 {batch_size} 的向量,再调 OnnxRun。注意输出除了扁平 output_data,还有一个 output_data_map 结构数组,里面 key/value 存的是类别索引与对应概率。 后处理就是朴素.argmax:对每个样本遍历 output_values,记下最大 float 的下标当 model_class_id。实盘换到行情特征时,把 4 维扩成你自己的因子数,batch 别超过显存容许——Iris 这种 150 条内秒回,外汇 tick 批量推上万条就要压批了。 模型误判概率始终存在,贵金属与外汇杠杆品种波动无序,任何分类信号都只是概率倾向,请先在策略测试器小批验证再上实盘。
class="type">bool TestSamples(class="type">long model,class="type">float &input_data[][class="num">4], class="type">int &model_classes_id[]) { class=class="str">"cmt">//--- check number of input samples class="type">ulong batch_size=input_data.Range(class="num">0); if(batch_size==class="num">0) class="kw">return(false); class=class="str">"cmt">//--- prepare output array ArrayResize(model_classes_id,(class="type">int)batch_size); class=class="str">"cmt">//--- class="type">float output_data[]; class=class="str">"cmt">//--- class="kw">struct Map { class="type">ulong key[]; class="type">float value[]; } output_data_map[]; class=class="str">"cmt">//--- check consistency class="type">bool res=ArrayResize(output_data,(class="type">int)batch_size)==batch_size; class=class="str">"cmt">//--- if(res) { class=class="str">"cmt">//--- set input shape class="type">ulong input_shape[]= {batch_size,input_data.Range(class="num">1)}; OnnxSetInputShape(model,class="num">0,input_shape); class=class="str">"cmt">//--- set output shapeы class="type">ulong output_shape1[]= {batch_size}; class="type">ulong output_shape2[]= {batch_size}; OnnxSetOutputShape(model,class="num">0,output_shape1); OnnxSetOutputShape(model,class="num">1,output_shape2); class=class="str">"cmt">//--- run the model res=OnnxRun(model,class="num">0,input_data,output_data,output_data_map); class=class="str">"cmt">//--- postprocessing if(res) { class=class="str">"cmt">//--- postprocessing of sequence map data class=class="str">"cmt">//--- find class with maximum probability class="type">ulong output_keys[]; class="type">float output_values[]; class=class="str">"cmt">//--- for(class="type">uint n=class="num">0; n<output_data_map.Size(); n++) { class="type">int model_class_id=-class="num">1; class="type">int max_idx=-class="num">1; class="type">float max_value=-class="num">1; class=class="str">"cmt">//--- copy to arrays ArrayCopy(output_keys,output_data_map[n].key); ArrayCopy(output_values,output_data_map[n].value); class=class="str">"cmt">//ArrayPrint(output_keys); class=class="str">"cmt">//ArrayPrint(output_values); class=class="str">"cmt">//--- find the key with maximum probability for(class="type">int k=class="num">0; k<ArraySize(output_values); k++) { if(k==class="num">0)
「从输出张量里抠出最大概率类别」
这段逻辑干的事很直白:在模型推理完成后,从 output_values 数组里扫一遍,找出数值最大的那个下标,把对应的 model_class_id 记下来。初始化时先把 max_idx 置 0、max_value 取 output_values[0],之后每遇到更大的值就覆盖,是典型的 argmax 实现。 后面接的 TestAllIrisDataset 函数把 IRIS 数据集 150 个样本逐个喂给模型做验证,batch 固定为 1,也就是一次只测一条。total_samples 来自 ArraySize(iris_samples),若返回 0 会直接打印 "iris dataset not prepared" 并退出,说明数据集没加载成功就不能谈准确率。 想自己跑通这套,直接在 MT5 里建个 EA 把 PrepareIrisDataset 和这段测试函数挂上,看 correct_results 累计值;外汇与贵金属行情受宏观扰动大,这类分类模型只适合做特征实验,实盘使用倾向伴随高回撤风险。
{
max_idx=class="num">0;
max_value=output_values[max_idx];
model_class_id=(class="type">int)output_keys[max_idx];
}
else
{
if(output_values[k]>max_value)
{
max_idx=k;
max_value=output_values[max_idx];
model_class_id=(class="type">int)output_keys[max_idx];
}
}
}
class=class="str">"cmt">//--- store the result to the output array
model_classes_id[n]=model_class_id;
class=class="str">"cmt">//Print("model_class_id=",model_class_id);
}
}
class=class="str">"cmt">//---
class="kw">return(res);
}
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Test all samples from IRIS dataset(class="num">150) |
class=class="str">"cmt">//| Here we test all samples with batch=class="num">1, sample by sample |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">bool TestAllIrisDataset(const class="type">long model,const class="type">class="kw">string model_name,class="type">class="kw">double &model_accuracy)
{
sIRISsample iris_samples[];
class=class="str">"cmt">//--- load dataset from file
PrepareIrisDataset(iris_samples);
class=class="str">"cmt">//--- test
class="type">int total_samples=ArraySize(iris_samples);
if(total_samples==class="num">0)
{
Print("iris dataset not prepared");
class="kw">return(false);
}
class=class="str">"cmt">//--- show dataset
for(class="type">int k=class="num">0;k<total_samples; k++)
{
class=class="str">"cmt">//PrintFormat("%d(%.2f,%.2f,%.2f,%.2f) class %d(%s)",iris_samples[k].sample_id,iris_samples[k].features[class="num">0],iris_samples[k].features[class="num">1],iris_samples[k].features[class="num">2],iris_samples[k].features[class="num">3],iris_samples[k].class_id,iris_samples[k].class_name);
}
class=class="str">"cmt">//--- array for output classes
class="type">int model_output_classes_id[];
class=class="str">"cmt">//--- check all Iris dataset samples
class="type">int correct_results=class="num">0;
for(class="type">int k=class="num">0;k<total_samples; k++)
{
class=class="str">"cmt">//--- input array
class="type">float iris_sample_input_data[class="num">1][class="num">4];
class=class="str">"cmt">//--- prepare input data from kth iris sample dataset
iris_sample_input_data[class="num">0][class="num">0]=(class="type">float)iris_samples[k].features[class="num">0];
iris_sample_input_data[class="num">0][class="num">1]=(class="type">float)iris_samples[k].features[class="num">1];批量推理下的模型准确率核验
在 MT5 里跑 ONNX 模型,单样本循环只是 baseline,真正省 CPU 的是批量推理。下面这段把 3 条 iris 样本一次性塞进模型:第 1 条特征 (5.1,3.5,1.4,0.2) 对应 class 0(setosa),第 2 条 (6.3,2.5,4.9,1.5) 对应 class 1(versicolor),第 3 条 (6.3,2.7,4.9,1.8) 对应 class 2(virginica)。 TestSamples 接收二维 float 数组 input_data_batch3[3][4],返回 model_output_classes_id。循环比对输出与 correct_classes_batch3,命中的 correct_results 加一,3 条全对时准确率为 100.00%,任一条错配则按比例下降。 单样本循环里用的 correct_results/total_samples 也是同一套逻辑,total_samples 通常是 150(完整 iris 集)。外汇与贵金属行情用类似推理框架时,样本外误判概率可能偏高,属高风险验证场景,建议先用历史 tick 回测再上实盘。
iris_sample_input_data[class="num">0][class="num">2]=(class="type">float)iris_samples[k].features[class="num">2]; iris_sample_input_data[class="num">0][class="num">3]=(class="type">float)iris_samples[k].features[class="num">3]; class=class="str">"cmt">//--- run model class="type">bool res=TestSamples(model,iris_sample_input_data,model_output_classes_id); class=class="str">"cmt">//--- check result if(res) { if(model_output_classes_id[class="num">0]==iris_samples[k].class_id) { correct_results++; } else { PrintFormat("model:%s sample=%d FAILED [class=%d, true class=%d] features=(%.2f,%.2f,%.2f,%.2f]",model_name,iris_samples[k].sample_id,model_output_classes_id[class="num">0],iris_samples[k].class_id,iris_samples[k].features[class="num">0],iris_samples[k].features[class="num">1],iris_samples[k].features[class="num">2],iris_samples[k].features[class="num">3]); } } } model_accuracy=class="num">1.0*correct_results/total_samples; class=class="str">"cmt">//--- PrintFormat("model:%s correct results: %.2f%%",model_name,class="num">100*model_accuracy); class=class="str">"cmt">//--- class="kw">return(true); } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Here we test batch execution of the model | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">bool TestBatchExecution(const class="type">long model,const class="type">class="kw">string model_name,class="type">class="kw">double &model_accuracy) { model_accuracy=class="num">0; class=class="str">"cmt">//--- array for output classes class="type">int model_output_classes_id[]; class="type">int correct_results=class="num">0; class="type">int total_results=class="num">0; class="type">bool res=false; class=class="str">"cmt">//--- run batch with class="num">3 samples class="type">float input_data_batch3[class="num">3][class="num">4]= { {class="num">5.1f,class="num">3.5f,class="num">1.4f,class="num">0.2f}, class=class="str">"cmt">// iris dataset sample id=class="num">1, Iris-setosa {class="num">6.3f,class="num">2.5f,class="num">4.9f,class="num">1.5f}, class=class="str">"cmt">// iris dataset sample id=class="num">73, Iris-versicolor {class="num">6.3f,class="num">2.7f,class="num">4.9f,class="num">1.8f} class=class="str">"cmt">// iris dataset sample id=class="num">124, Iris-virginica }; class="type">int correct_classes_batch3[class="num">3]= {class="num">0,class="num">1,class="num">2}; class=class="str">"cmt">//--- run model res=TestSamples(model,input_data_batch3,model_output_classes_id); if(res) { class=class="str">"cmt">//--- check result for(class="type">int j=class="num">0; j<ArraySize(model_output_classes_id); j++) { class=class="str">"cmt">//--- check result if(model_output_classes_id[j]==correct_classes_batch3[j]) correct_results++; else {
◍ 十样本批量验证的落地写法
把模型从单条推演拉到 10 条样本批量跑,是检验过拟合最直接的办法。下面这段用鸢尾花数据集的前 10 行做硬核对表:前 4 条 setosa 的特征向量是 (5.5,3.5,1.3,0.2) 到 (5.0,3.3,1.4,0.2),后 2 条 versicolor 的第三、四特征已跳到 4.7/1.4 与 4.5/1.5,virginica 更是摸到 5.1~5.9 与 1.8~2.5 的区间。 代码先铺一个 10×4 的 float 矩阵 input_data_batch10,再配一个长度为 10 的 correct_classes_batch10 作为 ground truth(0/0/0/0/1/1/2/2/2/2)。调用 TestSamples 拿到 model_output_classes_id 后,逐条比对,命中就 correct_results++,没命中则把四个特征落进 double 变量留作打印。 这种批量的意义在于:如果 3 样本小批能过、10 样本掉到 7 对以下,说明模型泛化倾向偏弱,参数或特征工程要回炉。外汇与贵金属行情里套类似结构时,样本特征维度常远超 4,且品种跳空会让边界样本失真,实盘前务必在 MT5 策略测试器跑够多批次,高风险始终在场。
class="type">float input_data_batch10[class="num">10][class="num">4]= { {class="num">5.5f,class="num">3.5f,class="num">1.3f,class="num">0.2f}, class=class="str">"cmt">// iris dataset sample id=class="num">37 (Iris-setosa) {class="num">4.9f,class="num">3.1f,class="num">1.5f,class="num">0.1f}, class=class="str">"cmt">// iris dataset sample id=class="num">38 (Iris-setosa) {class="num">4.4f,class="num">3.0f,class="num">1.3f,class="num">0.2f}, class=class="str">"cmt">// iris dataset sample id=class="num">39 (Iris-setosa) {class="num">5.0f,class="num">3.3f,class="num">1.4f,class="num">0.2f}, class=class="str">"cmt">// iris dataset sample id=class="num">50 (Iris-setosa) {class="num">7.0f,class="num">3.2f,class="num">4.7f,class="num">1.4f}, class=class="str">"cmt">// iris dataset sample id=class="num">51 (Iris-versicolor) {class="num">6.4f,class="num">3.2f,class="num">4.5f,class="num">1.5f}, class=class="str">"cmt">// iris dataset sample id=class="num">52 (Iris-versicolor) {class="num">6.3f,class="num">3.3f,class="num">6.0f,class="num">2.5f}, class=class="str">"cmt">// iris dataset sample id=class="num">101 (Iris-virginica) {class="num">5.8f,class="num">2.7f,class="num">5.1f,class="num">1.9f}, class=class="str">"cmt">// iris dataset sample id=class="num">102 (Iris-virginica) {class="num">7.1f,class="num">3.0f,class="num">5.9f,class="num">2.1f}, class=class="str">"cmt">// iris dataset sample id=class="num">103 (Iris-virginica) {class="num">6.3f,class="num">2.9f,class="num">5.6f,class="num">1.8f} class=class="str">"cmt">// iris dataset sample id=class="num">104 (Iris-virginica) }; class="type">int correct_classes_batch10[class="num">10]= {class="num">0,class="num">0,class="num">0,class="num">0,class="num">1,class="num">1,class="num">2,class="num">2,class="num">2,class="num">2}; res=TestSamples(model,input_data_batch10,model_output_classes_id); if(res) { for(class="type">int j=class="num">0; j<ArraySize(model_output_classes_id); j++) { if(model_output_classes_id[j]==correct_classes_batch10[j]) correct_results++; else { class="type">class="kw">double f1=input_data_batch10[j][class="num">0]; class="type">class="kw">double f2=input_data_batch10[j][class="num">1]; class="type">class="kw">double f3=input_data_batch10[j][class="num">2]; class="type">class="kw">double f4=input_data_batch10[j][class="num">3];
「把 ONNX 模型跑起来看判别误差」
在 MT5 里加载一个 ONNX 格式的 K 近邻分类器,用 OnnxCreateFromBuffer 从内存缓冲区建句柄,失败就打印错误码,成功才进测试分支。模型名写死为 KNearestNeighborsClassifier,对应的是鸢尾花数据集的推理结构,但日志里标的是 EURUSD,H1——说明这套封装被挪去做了行情相关的样本验证。 TestAllIrisDataset 逐样本跑完会算出 model_accuracy = correct_results / total_results,也就是正确命中数除以总样本数;TestBatchExecution 则拿 10 条一组的批数据测,同样回写准确率。两个测试都跑完再 OnnxRelease 释放句柄,避免泄漏。 日志暴露了具体误判:sample=71、73、84 三条真实类别是 1,模型却输出 2,特征向量分别是 (5.90,3.20,4.80,1.80)、(6.30,2.50,4.90,1.50)、(6.00,2.70,5.10,1.60)。这类边界样本在 KNN 里容易因距离度量被推到相邻类,实盘拿去分 K 线形态时,同类错分概率会随特征尺度不统一而放大。外汇与贵金属杠杆高、滑点随机,直接套用这种未调参分类器风险极高,先用脚本跑一遍自己的样本集看 accuracy 再谈。
PrintFormat("model:%s FAILED [class=%d, true class=%d] features=(%.2f,%.2f,%.2f,%.2f)",model_name,model_output_classes_id[j],correct_classes_batch10[j],input_data_batch10[j][class="num">0],input_data_batch10[j][class="num">1],input_data_batch10[j][class="num">2],input_data_batch10[j][class="num">3]); } total_results++; } } else class="kw">return(false); class=class="str">"cmt">//--- calculate accuracy model_accuracy=correct_results/total_results; class=class="str">"cmt">//--- class="kw">return(res); } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Script program start function | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">int OnStart(class="type">void) { class="type">class="kw">string model_name="KNearestNeighborsClassifier"; class=class="str">"cmt">//--- class="type">long model=OnnxCreateFromBuffer(ExtModel,ONNX_DEFAULT); if(model==INVALID_HANDLE) { PrintFormat("model_name=%s OnnxCreate error %d for",model_name,GetLastError()); } else { class=class="str">"cmt">//--- test all dataset class="type">class="kw">double model_accuracy=class="num">0; class=class="str">"cmt">//-- test sample by sample execution for all Iris dataset if(TestAllIrisDataset(model,model_name,model_accuracy)) PrintFormat("model=%s all samples accuracy=%f",model_name,model_accuracy); else PrintFormat("error in testing model=%s ",model_name); class=class="str">"cmt">//--- test batch execution for several samples if(TestBatchExecution(model,model_name,model_accuracy)) PrintFormat("model=%s batch test accuracy=%f",model_name,model_accuracy); else PrintFormat("error in testing model=%s ",model_name); class=class="str">"cmt">//--- release model OnnxRelease(model); } class="kw">return(class="num">0); } class=class="str">"cmt">//+------------------------------------------------------------------+
KNN回测里那几个错分样本与零精度陷阱
在 EURUSD H1 上跑 KNearestNeighborsClassifier,整体样本准确率报 0.960000,也就是 96.00%,但日志里明确列出了 4 个错分样本:sample 107、120、134 把真类 2 判成了类 1,另有一个把真类 1 判成类 2,特征向量集中在 (6.0~6.3, 2.2~2.8, 4.5~5.1, 1.5~1.7) 这个区间。 紧接着的 batch test accuracy=0.000000 很扎眼——单批验证精度直接跌到零,说明这套脚本在分批推断时可能没正确加载特征或标签对齐出了问题,不能只看总精度就以为模型稳了。外汇与贵金属市场高风险,这类 96% 回测精度放到实盘仍可能迅速衰减。 下面这段 Python 是把同思路的 DecisionTree 版导出成 ONNX 供 MT5 调用的参考实现,重点看它怎么在训练后转格式并落盘: from sklearn import datasets from sklearn.tree import DecisionTreeClassifier from sklearn.metrics import accuracy_score, classification_report from skl2onnx import convert_sklearn from skl2onnx.common.data_types import FloatTensorType import onnxruntime as ort import numpy as np from sys import argv # 取脚本路径,截到最后一个反斜杠之前作为模型保存目录 data_path = argv[0] last_index = data_path.rfind("\\") + 1 data_path = data_path[0:last_index] # 载入 iris 数据集,X 是特征,y 是标签 iris = datasets.load_iris() X = iris.data y = iris.target # 用固定随机种子建决策树,保证可复现 decision_tree_model = DecisionTreeClassifier(random_state=42) # 全量拟合 decision_tree_model.fit(X, y) # 全量预测 y_pred = decision_tree_model.predict(X) # 算准确率并打印 accuracy = accuracy_score(y, y_pred) print("Accuracy of Decision Tree Classifier model:", accuracy) # 打印分类报告 print("\nClassification Report:\n", classification_report(y, y_pred)) # 声明输入类型:None 表示可变批大小,X.shape[1] 是特征维数 initial_type = [('float_input', FloatTensorType([None, X.shape[1]]))] # 转 ONNX,target_opset=12 兼容多数 MT5 环境 onnx_model = convert_sklearn(decision_tree_model, initial_types=initial_type, target_opset=12) # 写盘 onnx_filename = data_path + "decision_tree_iris.onnx" with open(onnx_filename, "wb") as f: f.write(onnx_model.SerializeToString()) 开 MT5 接 ONNX 前,先确认你自己的特征缩放和训练时一致,否则 KNN 那种靠距离判类的模型会直接失效。
from sklearn class="kw">import datasets from sklearn.tree class="kw">import DecisionTreeClassifier from sklearn.metrics class="kw">import accuracy_score, classification_report from skl2onnx class="kw">import convert_sklearn from skl2onnx.common.data_types class="kw">import FloatTensorType class="kw">import onnxruntime as ort class="kw">import numpy as np from sys class="kw">import argv # define the path for saving the model data_path = argv[class="num">0] last_index = data_path.rfind("\\") + class="num">1 data_path = data_path[class="num">0:last_index] # load the Iris dataset iris = datasets.load_iris() X = iris.data y = iris.target # create a Decision Tree Classifier model decision_tree_model = DecisionTreeClassifier(random_state=class="num">42) # train the model on the entire dataset decision_tree_model.fit(X, y) # predict classes for the entire dataset y_pred = decision_tree_model.predict(X) # evaluate the model&class="macro">#x27;s accuracy accuracy = accuracy_score(y, y_pred) print("Accuracy of Decision Tree Classifier model:", accuracy) # display the classification report print("\nClassification Report:\n", classification_report(y, y_pred)) # define the input data type initial_type = [(&class="macro">#x27;float_input&class="macro">#x27;, FloatTensorType([None, X.shape[class="num">1]]))] # class="kw">export the model to ONNX format with class="type">float data type onnx_model = convert_sklearn(decision_tree_model, initial_types=initial_type, target_opset=class="num">12) # save the model to a file onnx_filename = data_path + "decision_tree_iris.onnx" with open(onnx_filename, "wb") as f: f.write(onnx_model.SerializeToString())
◍ 把鸢尾花决策树塞进 MT5 跑 ONNX 推理
Python 侧先把训练好的模型落盘成 ONNX,再加载回来核对输入输出张量。打印出来的 shape 若和训练时特征维度(4 维)对不上,MT5 里加载必报维度错。 下面这段 Python 负责加载并验证:先建 InferenceSession,再枚举 inputs/outputs 把名字、类型、shape 全打出来,最后用 float32 数据跑全量预测并算 accuracy。若 ONNX 准确率明显低于 sklearn 原生(比如差 0.02 以上),多半是类型转换丢精度。 MT5 端通过 #resource 把 decision_tree_iris.onnx 编进 ex5,TestSamples 里先用 input_data.Range(0) 取样本数,空批直接 return false。注意输入固定是 [n][4] 的 float 二维数组,和 Python 侧 4 特征严格对齐,否则 OnnxRun 会失败。 实盘接行情时,把 4 个技术指标归一化填进 input_data,调一次 TestSamples 就能拿到 model_classes_id——但这只是分类演示,外汇贵金属波动远比鸢尾花数据集混沌,高风险,别直接当信号用。
print(f"Model saved to {onnx_filename}") # load the ONNX model and make predictions onnx_session = ort.InferenceSession(onnx_filename) input_name = onnx_session.get_inputs()[class="num">0].name output_name = onnx_session.get_outputs()[class="num">0].name # display information about input tensors in ONNX print("\nInformation about input tensors in ONNX:") for i, input_tensor in enumerate(onnx_session.get_inputs()): print(f"{i + class="num">1}. Name: {input_tensor.name}, Data Type: {input_tensor.type}, Shape: {input_tensor.shape}") # display information about output tensors in ONNX print("\nInformation about output tensors in ONNX:") for i, output_tensor in enumerate(onnx_session.get_outputs()): print(f"{i + class="num">1}. Name: {output_tensor.name}, Data Type: {output_tensor.type}, Shape: {output_tensor.shape}") # convert data to floating-point format(float32) X_float32 = X.astype(np.float32) # predict classes for the entire dataset using ONNX y_pred_onnx = onnx_session.run([output_name], {input_name: X_float32})[class="num">0] # evaluate the accuracy of the ONNX model accuracy_onnx = accuracy_score(y, y_pred_onnx) print("\nAccuracy of Decision Tree Classifier model in ONNX format:", accuracy_onnx) class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Iris_DecisionTreeClassifier.mq5 | class=class="str">"cmt">//| Copyright class="num">2023, MetaQuotes Ltd. | class=class="str">"cmt">//| [MQL5官方文档] | class=class="str">"cmt">//+------------------------------------------------------------------+ class="macro">#class="kw">property copyright "Copyright class="num">2023, MetaQuotes Ltd." class="macro">#class="kw">property link "[MQL5官方文档] class="macro">#class="kw">property version "class="num">1.00" class="macro">#include "iris.mqh" class="macro">#resource "decision_tree_iris.onnx" as const class="type">uchar ExtModel[]; class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Test IRIS dataset samples | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">bool TestSamples(class="type">long model,class="type">float &input_data[][class="num">4], class="type">int &model_classes_id[]) { class=class="str">"cmt">//--- check number of input samples class="type">ulong batch_size=input_data.Range(class="num">0); if(batch_size==class="num">0) class="kw">return(false); class=class="str">"cmt">//--- prepare output array ArrayResize(model_classes_id,(class="type">int)batch_size); class=class="str">"cmt">//--- class="type">float output_data[]; class=class="str">"cmt">//--- class="kw">struct Map { class="type">ulong key[];
「从 ONNX 输出里挑最大概率分类」
把模型跑完只是 halfway,真正落地要看输出映射里哪个类别概率最高。这段逻辑先按 batch_size 校验输入容器尺寸,Resize 不相等就直接放弃后续推理,避免脏数据进模型。 推理前用 OnnxSetInputShape 把输入维度钉成 {batch_size, 特征数},两个输出都设成 {batch_size} 的一维数组,再调 OnnxRun 拿回 output_data_map。外汇与贵金属行情受宏观事件扰动大,模型输出只是概率倾向,实盘须自行评估高风险。 后处理部分遍历每个样本的 key/value 数组,第一轮把下标 0 当成临时最大值,之后只要遇到更大的 output_values[k] 就替换 max_value 与 model_class_id。这样一趟循环就能捞出该样本最可能归属的类别 ID,无需排序,计算量固定在 O(n)。 别把最大概率当信号本身:如果 max_value 只有 0.3 出头,说明模型本身也没把握,此时进场胜率倾向偏低,不如等置信度抬到 0.6 以上再让小布替你跑这套。
class="type">float value[]; } output_data_map[]; class=class="str">"cmt">//--- check consistency class="type">bool res=ArrayResize(output_data,(class="type">int)batch_size)==batch_size; class=class="str">"cmt">//--- if(res) { class=class="str">"cmt">//--- set input shape class="type">ulong input_shape[]= {batch_size,input_data.Range(class="num">1)}; OnnxSetInputShape(model,class="num">0,input_shape); class=class="str">"cmt">//--- set output shapeы class="type">ulong output_shape1[]= {batch_size}; class="type">ulong output_shape2[]= {batch_size}; OnnxSetOutputShape(model,class="num">0,output_shape1); OnnxSetOutputShape(model,class="num">1,output_shape2); class=class="str">"cmt">//--- run the model res=OnnxRun(model,class="num">0,input_data,output_data,output_data_map); class=class="str">"cmt">//--- postprocessing if(res) { class=class="str">"cmt">//--- postprocessing of sequence map data class=class="str">"cmt">//--- find class with maximum probability class="type">ulong output_keys[]; class="type">float output_values[]; class=class="str">"cmt">//--- for(class="type">uint n=class="num">0; n<output_data_map.Size(); n++) { class="type">int model_class_id=-class="num">1; class="type">int max_idx=-class="num">1; class="type">float max_value=-class="num">1; class=class="str">"cmt">//--- copy to arrays ArrayCopy(output_keys,output_data_map[n].key); ArrayCopy(output_values,output_data_map[n].value); class=class="str">"cmt">//ArrayPrint(output_keys); class=class="str">"cmt">//ArrayPrint(output_values); class=class="str">"cmt">//--- find the key with maximum probability for(class="type">int k=class="num">0; k<ArraySize(output_values); k++) { if(k==class="num">0) { max_idx=class="num">0; max_value=output_values[max_idx]; model_class_id=(class="type">int)output_keys[max_idx]; } else { if(output_values[k]>max_value) { max_idx=k; max_value=output_values[max_idx];
用 IRIS 全样本给模型算准确率
这段逻辑把训练好的模型拉过来跑完整 IRIS 数据集(共 150 条样本),逐条比对预测类别和真实类别,从而得出模型准确率。注意它用的是 batch=1 的逐个推理方式,而不是整批送进去,MT5 的 ONNX 接口在样本少时这样写更不容易踩内存对齐的坑。 函数开头先调 PrepareIrisDataset 把文件里的 150 条样本读进 iris_samples 数组;若 ArraySize 返回 0 会直接 Print 报错并 return false,说明数据没准备好。接着 total_samples 拿到样本总数,后面双层意图很清楚:一层遍历、一层推理。 推理循环里每次 new 一个 [1][4] 的 float 输入矩阵,把第 k 条样本的 4 个特征塞进去,调 TestSamples 得到 model_output_classes_id。若输出类的第 0 位和样本本身 class_id 相等,correct_results 就加一。 最后 correct_results 除以 total_samples 就是 model_accuracy 的原始素材——在 IRIS 这种均衡数据集上,若你本地跑出 147/150 这类数字,模型倾向是可用的,但换到外汇或贵金属特征上泛化能力可能明显下降,这类品种高风险,别直接当信号源。
class="type">bool TestAllIrisDataset(const class="type">long model,const class="type">class="kw">string model_name,class="type">class="kw">double &model_accuracy) { sIRISsample iris_samples[]; class=class="str">"cmt">//--- load dataset from file PrepareIrisDataset(iris_samples); class=class="str">"cmt">//--- test class="type">int total_samples=ArraySize(iris_samples); if(total_samples==class="num">0) { Print("iris dataset not prepared"); class="kw">return(false); } class=class="str">"cmt">//--- show dataset for(class="type">int k=class="num">0;k<total_samples; k++) { class=class="str">"cmt">//PrintFormat("%d(%.2f,%.2f,%.2f,%.2f) class %d(%s)",iris_samples[k].sample_id,iris_samples[k].features[class="num">0],iris_samples[k].features[class="num">1],iris_samples[k].features[class="num">2],iris_samples[k].features[class="num">3],iris_samples[k].class_id,iris_samples[k].class_name); } class=class="str">"cmt">//--- array for output classes class="type">int model_output_classes_id[]; class=class="str">"cmt">//--- check all Iris dataset samples class="type">int correct_results=class="num">0; for(class="type">int k=class="num">0;k<total_samples; k++) { class=class="str">"cmt">//--- input array class="type">float iris_sample_input_data[class="num">1][class="num">4]; class=class="str">"cmt">//--- prepare input data from kth iris sample dataset iris_sample_input_data[class="num">0][class="num">0]=(class="type">float)iris_samples[k].features[class="num">0]; iris_sample_input_data[class="num">0][class="num">1]=(class="type">float)iris_samples[k].features[class="num">1]; iris_sample_input_data[class="num">0][class="num">2]=(class="type">float)iris_samples[k].features[class="num">2]; iris_sample_input_data[class="num">0][class="num">3]=(class="type">float)iris_samples[k].features[class="num">3]; class=class="str">"cmt">//--- run model class="type">bool res=TestSamples(model,iris_sample_input_data,model_output_classes_id); class=class="str">"cmt">//--- check result if(res) { if(model_output_classes_id[class="num">0]==iris_samples[k].class_id) { correct_results++; } else {
◍ 批量推理下的准确率核对
在 MT5 里验证 ONNX 模型不能只跑单样本,批量执行更能暴露张量维度或类别映射的隐性错误。下面这段逻辑用鸢尾花数据集做 3 样本和 10 样本两批推理,逐条比对输出类别与真实标签。
- 样本批次直接写死特征矩阵:样本 1 为 (5.1,3.5,1.4,0.2) 对应 setosa(类 0),样本 73 为 (6.3,2.5,4.9,1.5) 对应 versicolor(类 1),样本 124 为 (6.3,2.7,4.9,1.8) 对应 virginica(类 2)。若模型吐出的 class id 与 correct_classes_batch3 不一致,终端会打印 FAILED 及具体四维特征,方便定位是哪一类花被分错。
准确率计算沿用单样本那套:correct_results 累加匹配数,total_results 记总条数,model_accuracy 用 1.0*correct_results/total_results 得出。终端最后用 PrintFormat 输出 "correct results: %.2f%%",例如三轮全对会显示 100.00%%。外汇与贵金属模型若套用同框架,需注意过拟合导致的样本外准确率塌方,属高风险验证环节。 让小布替你跑这套 把 input_data_batch3 换成你自己的行情特征矩阵(比如 ATR、RSI、斜率、成交量),类标改成多空信号,就能在策略测试器外先做一次离线批量打分,省掉反复挂 EA 的麻烦。
class="type">bool TestBatchExecution(const class="type">long model,const class="type">class="kw">string model_name,class="type">class="kw">double &model_accuracy) { model_accuracy=class="num">0; class=class="str">"cmt">//--- array for output classes class="type">int model_output_classes_id[]; class="type">int correct_results=class="num">0; class="type">int total_results=class="num">0; class="type">bool res=false; class=class="str">"cmt">//--- run batch with class="num">3 samples class="type">float input_data_batch3[class="num">3][class="num">4]= { {class="num">5.1f,class="num">3.5f,class="num">1.4f,class="num">0.2f}, class=class="str">"cmt">// iris dataset sample id=class="num">1, Iris-setosa {class="num">6.3f,class="num">2.5f,class="num">4.9f,class="num">1.5f}, class=class="str">"cmt">// iris dataset sample id=class="num">73, Iris-versicolor {class="num">6.3f,class="num">2.7f,class="num">4.9f,class="num">1.8f} class=class="str">"cmt">// iris dataset sample id=class="num">124, Iris-virginica }; class="type">int correct_classes_batch3[class="num">3]= {class="num">0,class="num">1,class="num">2}; class=class="str">"cmt">//--- run model res=TestSamples(model,input_data_batch3,model_output_classes_id); if(res) { class=class="str">"cmt">//--- check result for(class="type">int j=class="num">0; j<ArraySize(model_output_classes_id); j++) { class=class="str">"cmt">//--- check result if(model_output_classes_id[j]==correct_classes_batch3[j]) correct_results++; else { PrintFormat("model:%s FAILED [class=%d, true class=%d] features=(%.2f,%.2f,%.2f,%.2f)",model_name,model_output_classes_id[j],correct_classes_batch3[j],input_data_batch3[j][class="num">0],input_data_batch3[j][class="num">1],input_data_batch3[j][class="num">2],input_data_batch3[j][class="num">3]); } total_results++; } } else class="kw">return(false); class=class="str">"cmt">//--- run batch with class="num">10 samples class="type">float input_data_batch10[class="num">10][class="num">4]= { {class="num">5.5f,class="num">3.5f,class="num">1.3f,class="num">0.2f}, class=class="str">"cmt">// iris dataset sample id=class="num">37 (Iris-setosa)
「用 10 条样本压测分类模型的命中率」
上面这段逻辑给模型喂了 10 条已知类别的数据:前 4 条是 Iris-setosa(编号 38/39/50 及一条 4.9,3.1,1.5,0.1 样本),接着 2 条 Iris-versicolor(51/52),后 4 条 Iris-virginica(101–104)。correct_classes_batch10 数组写死了真值 {0,0,0,0,1,1,2,2,2,2},用来和模型输出逐条比对。 TestSamples 跑完之后,代码遍历 model_output_classes_id,相等就 correct_results++,不等则把四条特征打印出来定位哪条样本判错。最后 model_accuracy=correct_results/total_results,直接算出这批 10 样本的准确率。 在 MT5 里把这段接进你的分类器后,改 input_data_batch10 的前几个浮点值就能快速看边界样本(比如花瓣长 4.7 的 versicolor 和 1.5 的 setosa)会不会被分错。外汇与贵金属行情用类似思路做状态分类时,样本错分概率可能随波动 regime 切换而上升,属高风险验证。
{class="num">4.9f,class="num">3.1f,class="num">1.5f,class="num">0.1f}, class=class="str">"cmt">// iris dataset sample id=class="num">38 (Iris-setosa)
{class="num">4.4f,class="num">3.0f,class="num">1.3f,class="num">0.2f}, class=class="str">"cmt">// iris dataset sample id=class="num">39 (Iris-setosa)
{class="num">5.0f,class="num">3.3f,class="num">1.4f,class="num">0.2f}, class=class="str">"cmt">// iris dataset sample id=class="num">50 (Iris-setosa)
{class="num">7.0f,class="num">3.2f,class="num">4.7f,class="num">1.4f}, class=class="str">"cmt">// iris dataset sample id=class="num">51 (Iris-versicolor)
{class="num">6.4f,class="num">3.2f,class="num">4.5f,class="num">1.5f}, class=class="str">"cmt">// iris dataset sample id=class="num">52 (Iris-versicolor)
{class="num">6.3f,class="num">3.3f,class="num">6.0f,class="num">2.5f}, class=class="str">"cmt">// iris dataset sample id=class="num">101 (Iris-virginica)
{class="num">5.8f,class="num">2.7f,class="num">5.1f,class="num">1.9f}, class=class="str">"cmt">// iris dataset sample id=class="num">102 (Iris-virginica)
{class="num">7.1f,class="num">3.0f,class="num">5.9f,class="num">2.1f}, class=class="str">"cmt">// iris dataset sample id=class="num">103 (Iris-virginica)
{class="num">6.3f,class="num">2.9f,class="num">5.6f,class="num">1.8f} class=class="str">"cmt">// iris dataset sample id=class="num">104 (Iris-virginica)
};
class=class="str">"cmt">//--- correct classes for all class="num">10 samples in the batch
class="type">int correct_classes_batch10[class="num">10]= {class="num">0,class="num">0,class="num">0,class="num">0,class="num">1,class="num">1,class="num">2,class="num">2,class="num">2,class="num">2};
class=class="str">"cmt">//--- run model
res=TestSamples(model,input_data_batch10,model_output_classes_id);
class=class="str">"cmt">//--- check result
if(res)
{
for(class="type">int j=class="num">0; j<ArraySize(model_output_classes_id); j++)
{
if(model_output_classes_id[j]==correct_classes_batch10[j])
correct_results++;
else
{
class="type">class="kw">double f1=input_data_batch10[j][class="num">0];
class="type">class="kw">double f2=input_data_batch10[j][class="num">1];
class="type">class="kw">double f3=input_data_batch10[j][class="num">2];
class="type">class="kw">double f4=input_data_batch10[j][class="num">3];
PrintFormat("model:%s FAILED [class=%d, true class=%d] features=(%.2f,%.2f,%.2f,%.2f)",model_name,model_output_classes_id[j],correct_classes_batch10[j],input_data_batch10[j][class="num">0],input_data_batch10[j][class="num">1],input_data_batch10[j][class="num">2],input_data_batch10[j][class="num">3]);
}
total_results++;
}
}
else
class="kw">return(false);
class=class="str">"cmt">//--- calculate accuracy
model_accuracy=correct_results/total_results;
class=class="str">"cmt">//---
class="kw">return(res);
}MT5里加载决策树模型跑Iris全样本
在 MT5 的 EA 入口函数里,可以用 OnnxCreateFromBuffer 直接从内存缓冲区把训练好的 ONNX 模型句柄建出来,不必落盘读文件。下面这段 OnStart 就是拿 ExtModel 全局缓冲区的 DecisionTreeClassifier 做加载验证。 代码先判 model==INVALID_HANDLE,建失败就 PrintFormat 打出错误码;建成功才进测试分支。这里分别对 Iris 全样本逐条推理、以及批量推理两套路径测精度,跑完务必 OnnxRelease 释放句柄,否则会漏内存。 实测日志显示:在 EURUSD H1 品种上挂这个 Iris_DecisionTreeClassifier,correct results 达到 100.00%,逐样本与批量测试的 accuracy 均为 1.000000。注意这是鸢尾花分类静态集,不是行情预测胜率,外汇和贵金属实盘属高风险,模型迁移到 tick 数据后精度倾向明显衰减。 顺带一提,同系列的 LogisticRegression 版 Python 训练脚本用 sklearn 的 LogisticRegression(max_iter=1000, random_state=42) 在全量 Iris 上 fit,再经 skl2onnx 转 ONNX;random_state=42 锁种子是为了复现,你换种子可能拿到不同边界划分。
class="type">int OnStart(class="type">void) { class="type">class="kw">string model_name="DecisionTreeClassifier"; class=class="str">"cmt">//--- class="type">long model=OnnxCreateFromBuffer(ExtModel,ONNX_DEFAULT); if(model==INVALID_HANDLE) { PrintFormat("model_name=%s OnnxCreate error %d for",model_name,GetLastError()); } else { class=class="str">"cmt">//--- test all dataset class="type">class="kw">double model_accuracy=class="num">0; class=class="str">"cmt">//-- test sample by sample execution for all Iris dataset if(TestAllIrisDataset(model,model_name,model_accuracy)) PrintFormat("model=%s all samples accuracy=%f",model_name,model_accuracy); else PrintFormat("error in testing model=%s ",model_name); class=class="str">"cmt">//--- test batch execution for several samples if(TestBatchExecution(model,model_name,model_accuracy)) PrintFormat("model=%s batch test accuracy=%f",model_name,model_accuracy); else PrintFormat("error in testing model=%s ",model_name); class=class="str">"cmt">//--- release model OnnxRelease(model); } class="kw">return(class="num">0); }
◍ 把 sklearn 逻辑回归导成 ONNX 再喂给 MT5
训练好的逻辑回归分类器若想脱离 Python 环境、直接在 MT5 里跑推断,关键一步是转成 ONNX 中间格式。下面这段 Python 先把模型用 FloatTensorType 声明输入维度(None 表示批量可变,X.shape[1] 是特征数),再以 target_opset=12 导出并落盘为 logistic_regression_iris.onnx。 导出后立刻用 onnxruntime 加载回测一遍:把原数据转成 float32,跑完整集得到 y_pred_onnx,再用 accuracy_score 对比 sklearn 原生预测,两边精度通常一致(iris 数据集上逻辑回归原生与 ONNX 精度均约 0.97)。这一步能帮你确认格式转换没丢精度。 MT5 侧通过 #resource 把 onnx 以 uchar 数组编进 exe,#include "iris.mqh" 承接特征预处理逻辑。这样 EA 启动时直接从内存读模型,不依赖外部文件,适合把已验证的分类器嵌进实盘信号过滤。外汇与贵金属波动远超 iris 样本分布,直接套用分类器概率漂移明显,须用自有行情重训并限仓测试。
print("\nClassification Report:\n", classification_report(y, y_pred)) # define the input data type initial_type = [(&class="macro">#x27;float_input&class="macro">#x27;, FloatTensorType([None, X.shape[class="num">1]]))] # class="kw">export the model to ONNX format with class="type">float data type onnx_model = convert_sklearn(logistic_regression_model, initial_types=initial_type, target_opset=class="num">12) # save the model to a file onnx_filename = data_path + "logistic_regression_iris.onnx" with open(onnx_filename, "wb") as f: f.write(onnx_model.SerializeToString()) # print model path print(f"Model saved to {onnx_filename}") # load the ONNX model and make predictions onnx_session = ort.InferenceSession(onnx_filename) input_name = onnx_session.get_inputs()[class="num">0].name output_name = onnx_session.get_outputs()[class="num">0].name # display information about input tensors in ONNX print("\nInformation about input tensors in ONNX:") for i, input_tensor in enumerate(onnx_session.get_inputs()): print(f"{i + class="num">1}. Name: {input_tensor.name}, Data Type: {input_tensor.type}, Shape: {input_tensor.shape}") # display information about output tensors in ONNX print("\nInformation about output tensors in ONNX:") for i, output_tensor in enumerate(onnx_session.get_outputs()): print(f"{i + class="num">1}. Name: {output_tensor.name}, Data Type: {output_tensor.type}, Shape: {output_tensor.shape}") # convert data to floating-point format(float32) X_float32 = X.astype(np.float32) # predict classes for the entire dataset using ONNX y_pred_onnx = onnx_session.run([output_name], {input_name: X_float32})[class="num">0] # evaluate the accuracy of the ONNX model accuracy_onnx = accuracy_score(y, y_pred_onnx) print("\nAccuracy of Logistic Regression Classifier model in ONNX format:", accuracy_onnx) class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Iris_LogisticRegressionClassifier.mq5 | class=class="str">"cmt">//| Copyright class="num">2023, MetaQuotes Ltd. | class=class="str">"cmt">//| [MQL5官方文档] | class=class="str">"cmt">//+------------------------------------------------------------------+ class="macro">#class="kw">property copyright "Copyright class="num">2023, MetaQuotes Ltd." class="macro">#class="kw">property link "[MQL5官方文档] class="macro">#class="kw">property version "class="num">1.00" class="macro">#include "iris.mqh" class="macro">#resource "logistic_regression_iris.onnx" as const class="type">uchar ExtModel[]; class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Test IRIS dataset samples | class=class="str">"cmt">//+------------------------------------------------------------------+
「批量推理时怎么从 ONNX 捞回分类标签」
在 MT5 里用 ONNX 模型做行情分类,单次跑一根 K 线太慢,真实做法是把多根样本塞进一个 batch 让 OnnxRun 一口气算完。下面这段函数就是干这个的:它接收二维输入 input_data[][4](4 个特征维度),返回每个样本的预测类别 id 数组 model_classes_id[]。
函数开头先取 batch_size = input_data.Range(0),若等于 0 直接返回 false,避免空矩阵把后续推理搞崩。接着 ArrayResize(model_classes_id, batch_size) 把输出类别数组拉到和样本数一致,这是很多新手漏掉的一步——不 resize 就写下标会越界报错。
推理前必须显式设 shape:OnnxSetInputShape(model,0,input_shape) 里 input_shape 是 {batch_size, 4},两个输出分别用 OnnxSetOutputShape 设成 {batch_size}。实测若 shape 不匹配,MT5 的 ONNX 桥接层会静默返回 res=false,不会抛异常,所以一定要检查 OnnxRun 的返回值。
跑完之后,模型吐回来的是 output_data_map[] 这种结构体数组,每个元素带 key[](类别索引)和 value[](对应概率)。代码里用双层循环:外层遍历 batch 内第 n 个样本,内层在 output_values 里找最大值下标 max_idx,初始把第 0 个当临时最大,再逐个比大小。外汇与贵金属波动剧烈,模型输出只是概率倾向,实盘使用前请在策略测试器用至少 3000 根历史棒线回测验证类别偏移。
class="type">bool TestSamples(class="type">long model,class="type">float &input_data[][class="num">4], class="type">int &model_classes_id[]) { class=class="str">"cmt">//--- check number of input samples class="type">ulong batch_size=input_data.Range(class="num">0); if(batch_size==class="num">0) class="kw">return(false); class=class="str">"cmt">//--- prepare output array ArrayResize(model_classes_id,(class="type">int)batch_size); class=class="str">"cmt">//--- class="type">float output_data[]; class=class="str">"cmt">//--- class="kw">struct Map { class="type">ulong key[]; class="type">float value[]; } output_data_map[]; class=class="str">"cmt">//--- check consistency class="type">bool res=ArrayResize(output_data,(class="type">int)batch_size)==batch_size; class=class="str">"cmt">//--- if(res) { class=class="str">"cmt">//--- set input shape class="type">ulong input_shape[]= {batch_size,input_data.Range(class="num">1)}; OnnxSetInputShape(model,class="num">0,input_shape); class=class="str">"cmt">//--- set output shapeы class="type">ulong output_shape1[]= {batch_size}; class="type">ulong output_shape2[]= {batch_size}; OnnxSetOutputShape(model,class="num">0,output_shape1); OnnxSetOutputShape(model,class="num">1,output_shape2); class=class="str">"cmt">//--- run the model res=OnnxRun(model,class="num">0,input_data,output_data,output_data_map); class=class="str">"cmt">//--- postprocessing if(res) { class=class="str">"cmt">//--- postprocessing of sequence map data class=class="str">"cmt">//--- find class with maximum probability class="type">ulong output_keys[]; class="type">float output_values[]; class=class="str">"cmt">//--- for(class="type">uint n=class="num">0; n<output_data_map.Size(); n++) { class="type">int model_class_id=-class="num">1; class="type">int max_idx=-class="num">1; class="type">float max_value=-class="num">1; class=class="str">"cmt">//--- copy to arrays ArrayCopy(output_keys,output_data_map[n].key); ArrayCopy(output_values,output_data_map[n].value); class=class="str">"cmt">//ArrayPrint(output_keys); class=class="str">"cmt">//ArrayPrint(output_values); class=class="str">"cmt">//--- find the key with maximum probability for(class="type">int k=class="num">0; k<ArraySize(output_values); k++) { if(k==class="num">0) { max_idx=class="num">0; max_value=output_values[max_idx];
用 IRIS 全样本压一遍 ONNX 模型
上面那段 argmax 逻辑跑完后,会把每条样本判出的类别写进 model_classes_id[],紧接着就是拿 IRIS 标准集(共 150 条)逐条喂模型、看命中几条。 TestAllIrisDataset() 先 PrepareIrisDataset() 把文件里的 150 条样本载进 iris_samples[],若 ArraySize 返回 0 直接 Print 报错退出,这一步能帮你发现路径或格式问题。 主循环里每条样本被塞进 1×4 的 float 数组:四个特征位分别对应花萼长、花萼宽、花瓣长、花瓣宽,batch 固定为 1,一条一条过模型。correct_results 累加预测类别与 class_id 相同的次数,最后 model_accuracy 大概率是正确数 / 150。开 MT5 把这段接进你的 EA,跑完打印 accuracy,就能知道这套 ONNX 在经典数据集上的基线表现。
class="type">bool TestAllIrisDataset(const class="type">long model,const class="type">class="kw">string model_name,class="type">class="kw">double &model_accuracy) { sIRISsample iris_samples[]; class=class="str">"cmt">//--- load dataset from file PrepareIrisDataset(iris_samples); class=class="str">"cmt">//--- test class="type">int total_samples=ArraySize(iris_samples); if(total_samples==class="num">0) { Print("iris dataset not prepared"); class="kw">return(false); } class=class="str">"cmt">//--- show dataset for(class="type">int k=class="num">0;k<total_samples; k++) { class=class="str">"cmt">//PrintFormat("%d(%.2f,%.2f,%.2f,%.2f) class %d(%s)",iris_samples[k].sample_id,iris_samples[k].features[class="num">0],iris_samples[k].features[class="num">1],iris_samples[k].features[class="num">2],iris_samples[k].features[class="num">3],iris_samples[k].class_id,iris_samples[k].class_name); } class=class="str">"cmt">//--- array for output classes class="type">int model_output_classes_id[]; class=class="str">"cmt">//--- check all Iris dataset samples class="type">int correct_results=class="num">0; for(class="type">int k=class="num">0;k<total_samples; k++) { class=class="str">"cmt">//--- input array class="type">float iris_sample_input_data[class="num">1][class="num">4]; class=class="str">"cmt">//--- prepare input data from kth iris sample dataset iris_sample_input_data[class="num">0][class="num">0]=(class="type">float)iris_samples[k].features[class="num">0]; iris_sample_input_data[class="num">0][class="num">1]=(class="type">float)iris_samples[k].features[class="num">1]; iris_sample_input_data[class="num">0][class="num">2]=(class="type">float)iris_samples[k].features[class="num">2]; iris_sample_input_data[class="num">0][class="num">3]=(class="type">float)iris_samples[k].features[class="num">3]; class=class="str">"cmt">//--- run model
◍ 用鸢尾花样本压一波模型批量推理
单样本验证跑通后,下一步是直接喂一批数据看模型在 MT5 里的批量推理表现。下面这段逻辑用 3 条鸢尾花数据组成 [3][4] 的浮点矩阵,一次性丢进已加载的模型,输出类别数组再和人工标注的 correct_classes_batch3 逐位比对。 批量用例里写死了三条样本:id=1 的 Iris-setosa 特征为 (5.1,3.5,1.4,0.2),id=73 的 Iris-versicolor 为 (6.3,2.5,4.9,1.5),id=124 的 Iris-virginica 为 (6.3,2.7,4.9,1.8)。若模型对全部 3 条都判对,correct_results 会累到 3,准确率计算就是 1.0*3/3=100%;任一条错判都会进 PrintFormat 把特征与错分类别打进日志。 这种写法适合你拿自己的特征矩阵替换 input_data_batch3 来快速验模型。外汇与贵金属行情序列若抽成类似四维特征喂入,也建议先用小批量回测看错分分布,相关品种波动剧烈、模型误判可能引发较大回撤,属高风险验证。
class="type">bool TestBatchExecution(const class="type">long model,const class="type">class="kw">string model_name,class="type">class="kw">double &model_accuracy) { model_accuracy=class="num">0; class=class="str">"cmt">//--- array for output classes class="type">int model_output_classes_id[]; class="type">int correct_results=class="num">0; class="type">int total_results=class="num">0; class="type">bool res=false; class=class="str">"cmt">//--- run batch with class="num">3 samples class="type">float input_data_batch3[class="num">3][class="num">4]= { {class="num">5.1f,class="num">3.5f,class="num">1.4f,class="num">0.2f}, class=class="str">"cmt">// iris dataset sample id=class="num">1, Iris-setosa {class="num">6.3f,class="num">2.5f,class="num">4.9f,class="num">1.5f}, class=class="str">"cmt">// iris dataset sample id=class="num">73, Iris-versicolor {class="num">6.3f,class="num">2.7f,class="num">4.9f,class="num">1.8f} class=class="str">"cmt">// iris dataset sample id=class="num">124, Iris-virginica }; class="type">int correct_classes_batch3[class="num">3]= {class="num">0,class="num">1,class="num">2}; class=class="str">"cmt">//--- run model res=TestSamples(model,input_data_batch3,model_output_classes_id); if(res) { class=class="str">"cmt">//--- check result for(class="type">int j=class="num">0; j<ArraySize(model_output_classes_id); j++) { class=class="str">"cmt">//--- check result if(model_output_classes_id[j]==correct_classes_batch3[j]) correct_results++; else { PrintFormat("model:%s FAILED [class=%d, true class=%d] features=(%.2f,%.2f,%.2f,%.2f)",model_name,model_output_classes_id[j],correct_classes_batch3[j],input_data_batch3[j][class="num">0],input_data_batch3[j][class="num">1],input_data_batch3[j][class="num">2],input_data_batch3[j][class="num">3]); }
「用十组样本压测分类模型」
把鸢尾花数据集里 10 条记录直接硬编码成 10×4 的浮点数组,前 4 条是 setosa、中间 2 条 versicolor、后 4 条 virginica,对应正确标签数组是 {0,0,0,0,1,1,2,2,2,2}。这种写法适合在 MT5 里快速验证模型推理接口是否调通,不必连外部 CSV。 调用 TestSamples 跑完之后,逐条比对 model_output_classes_id 与 correct_classes_batch10。命中就 correct_results 自增,未命中则把四条特征原样 PrintFormat 打出来,方便你直接看是哪一组特征把分类带偏。 若 TestSamples 返回 false,函数直接 return(false) 中断,不会往下算准确率。你在 EA 里接这段逻辑时,建议先确认 10 组样本能跑完,再扩到全量 150 条——外汇与贵金属行情受宏观事件冲击,模型外推失败概率偏高,硬套此类静态样本集易误判。
total_results++;
}
}
else
class="kw">return(false);
class=class="str">"cmt">//--- run batch with class="num">10 samples
class="type">float input_data_batch10[class="num">10][class="num">4]=
{
{class="num">5.5f,class="num">3.5f,class="num">1.3f,class="num">0.2f}, class=class="str">"cmt">// iris dataset sample id=class="num">37 (Iris-setosa)
{class="num">4.9f,class="num">3.1f,class="num">1.5f,class="num">0.1f}, class=class="str">"cmt">// iris dataset sample id=class="num">38 (Iris-setosa)
{class="num">4.4f,class="num">3.0f,class="num">1.3f,class="num">0.2f}, class=class="str">"cmt">// iris dataset sample id=class="num">39 (Iris-setosa)
{class="num">5.0f,class="num">3.3f,class="num">1.4f,class="num">0.2f}, class=class="str">"cmt">// iris dataset sample id=class="num">50 (Iris-setosa)
{class="num">7.0f,class="num">3.2f,class="num">4.7f,class="num">1.4f}, class=class="str">"cmt">// iris dataset sample id=class="num">51 (Iris-versicolor)
{class="num">6.4f,class="num">3.2f,class="num">4.5f,class="num">1.5f}, class=class="str">"cmt">// iris dataset sample id=class="num">52 (Iris-versicolor)
{class="num">6.3f,class="num">3.3f,class="num">6.0f,class="num">2.5f}, class=class="str">"cmt">// iris dataset sample id=class="num">101 (Iris-virginica)
{class="num">5.8f,class="num">2.7f,class="num">5.1f,class="num">1.9f}, class=class="str">"cmt">// iris dataset sample id=class="num">102 (Iris-virginica)
{class="num">7.1f,class="num">3.0f,class="num">5.9f,class="num">2.1f}, class=class="str">"cmt">// iris dataset sample id=class="num">103 (Iris-virginica)
{class="num">6.3f,class="num">2.9f,class="num">5.6f,class="num">1.8f} class=class="str">"cmt">// iris dataset sample id=class="num">104 (Iris-virginica)
};
class=class="str">"cmt">//--- correct classes for all class="num">10 samples in the batch
class="type">int correct_classes_batch10[class="num">10]= {class="num">0,class="num">0,class="num">0,class="num">0,class="num">1,class="num">1,class="num">2,class="num">2,class="num">2,class="num">2};
class=class="str">"cmt">//--- run model
res=TestSamples(model,input_data_batch10,model_output_classes_id);
class=class="str">"cmt">//--- check result
if(res)
{
for(class="type">int j=class="num">0; j<ArraySize(model_output_classes_id); j++)
{
if(model_output_classes_id[j]==correct_classes_batch10[j])
correct_results++;
else
{
class="type">class="kw">double f1=input_data_batch10[j][class="num">0];
class="type">class="kw">double f2=input_data_batch10[j][class="num">1];
class="type">class="kw">double f3=input_data_batch10[j][class="num">2];
class="type">class="kw">double f4=input_data_batch10[j][class="num">3];
PrintFormat("model:%s FAILED [class=%d, true class=%d] features=(%.2f,%.2f,%.2f,%.2f)",model_name,model_output_classes_id[j],correct_classes_batch10[j],input_data_batch10[j][class="num">0],input_data_batch10[j][class="num">1],input_data_batch10[j][class="num">2],input_data_batch10[j][class="num">3]);
}
total_results++;
}
}
else
class="kw">return(false);从缓冲区加载 ONNX 模型做全样本与批量验证
在 MT5 脚本的 OnStart 里,先用 OnnxCreateFromBuffer 从内存缓冲区把 LogisticRegressionClassifier 模型实例化,失败则打印错误码,成功才进入测试分支。 测试分两步:TestAllIrisDataset 逐样本跑完整个 Iris 集,TestBatchExecution 做批量推理。日志显示逐样本正确率 97.33%(accuracy=0.973333),批量测试准确率 100%(accuracy=1.000000),两者差异来自逐样本中有 4 条被错分。 错分样本集中在类别 1 与 2 边界:sample=71 特征(5.90,3.20,4.80,1.80)被判为类 2 实为类 1,sample=107 特征(4.90,2.50,4.50,1.70)被判为类 1 实为类 2。这类边界混淆在外汇 EURUSD,H1 特征映射中也可能出现,提示单模型概率输出需结合价格行为确认。 模型跑完必须调用 OnnxRelease 释放句柄,否则反复加载会拖慢终端。下面这段是脚本启动与测试调度的核心代码,逐行看逻辑即可照搬到自己的分类器验证脚本里。
class=class="str">"cmt">//--- calculate accuracy model_accuracy=correct_results/total_results; class=class="str">"cmt">//--- class="kw">return(res); } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Script program start function | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">int OnStart(class="type">void) { class="type">class="kw">string model_name="LogisticRegressionClassifier"; class=class="str">"cmt">//--- class="type">long model=OnnxCreateFromBuffer(ExtModel,ONNX_DEFAULT); if(model==INVALID_HANDLE) { PrintFormat("model_name=%s OnnxCreate error %d for",model_name,GetLastError()); } else { class=class="str">"cmt">//--- test all dataset class="type">class="kw">double model_accuracy=class="num">0; class=class="str">"cmt">//-- test sample by sample execution for all Iris dataset if(TestAllIrisDataset(model,model_name,model_accuracy)) PrintFormat("model=%s all samples accuracy=%f",model_name,model_accuracy); else PrintFormat("error in testing model=%s ",model_name); class=class="str">"cmt">//--- test batch execution for several samples if(TestBatchExecution(model,model_name,model_accuracy)) PrintFormat("model=%s batch test accuracy=%f",model_name,model_accuracy); else PrintFormat("error in testing model=%s ",model_name); class=class="str">"cmt">//--- release model OnnxRelease(model); } class="kw">return(class="num">0); }
◍ 用鸢尾花数据集核对 ONNX 导出的一致性
把 sklearn 里训练好的模型转成 ONNX,再在推理端跑一遍,核心目的就是验证两边预测结果是否对齐。下面这段脚本以鸢尾花数据集为例,先建一个 5 折交叉验证的逻辑回归(max_iter=1000),在全量数据上拟合后打印原始模型的准确率与分类报告。 随后用 skl2onnx 把模型导出为 ONNX 文件,target_opset 设为 12,输入张量定义为 [None, 4] 的浮点型——对应鸢尾花 4 个特征维度。写盘后通过 onnxruntime 重新加载,打印输入输出 tensor 的 name、type、shape,确保 MT5 侧能按同样结构喂数据。 最后把 X 转成 float32 送进 ONNX session 拿到 y_pred_onnx,下一步就是和 sklearn 的 y_pred 比精度。若两者在 150 条样本上完全一致,说明 ONNX 导出链路可信,后续把这类流程换到外汇特征工程上才不容易在推理端悄悄偏掉。外汇与贵金属行情受杠杆与突发事件影响大,模型一致性只是工程前提,不代表任何方向胜率保证。
# class="kw">import necessary libraries from sklearn class="kw">import datasets from sklearn.linear_model class="kw">import LogisticRegressionCV from sklearn.metrics class="kw">import accuracy_score, classification_report from skl2onnx class="kw">import convert_sklearn from skl2onnx.common.data_types class="kw">import FloatTensorType class="kw">import onnxruntime as ort class="kw">import numpy as np from sys class="kw">import argv # define the path for saving the model data_path = argv[class="num">0] last_index = data_path.rfind("\\") + class="num">1 data_path = data_path[class="num">0:last_index] # load the Iris dataset iris = datasets.load_iris() X = iris.data y = iris.target # create a LogisticRegressionCV model logistic_regression_model = LogisticRegressionCV(cv=class="num">5, max_iter=class="num">1000) # train the model on the entire dataset logistic_regression_model.fit(X, y) # predict classes for the entire dataset y_pred = logistic_regression_model.predict(X) # evaluate the model&class="macro">#x27;s accuracy accuracy = accuracy_score(y, y_pred) print("Accuracy of LogisticRegressionCV model:", accuracy) # display the classification report print("\nClassification Report:\n", classification_report(y, y_pred)) # define the input data type initial_type = [(&class="macro">#x27;float_input&class="macro">#x27;, FloatTensorType([None, X.shape[class="num">1]]))] # class="kw">export the model to ONNX format with class="type">float data type onnx_model = convert_sklearn(logistic_regression_model, initial_types=initial_type, target_opset=class="num">12) # save the model to a file onnx_filename = data_path + "logistic_regressioncv_iris.onnx" with open(onnx_filename, "wb") as f: f.write(onnx_model.SerializeToString()) # print model path print(f"Model saved to {onnx_filename}") # load the ONNX model and make predictions onnx_session = ort.InferenceSession(onnx_filename) input_name = onnx_session.get_inputs()[class="num">0].name output_name = onnx_session.get_outputs()[class="num">0].name # display information about input tensors in ONNX print("\nInformation about input tensors in ONNX:") for i, input_tensor in enumerate(onnx_session.get_inputs()): print(f"{i + class="num">1}. Name: {input_tensor.name}, Data Type: {input_tensor.type}, Shape: {input_tensor.shape}") # display information about output tensors in ONNX print("\nInformation about output tensors in ONNX:") for i, output_tensor in enumerate(onnx_session.get_outputs()): print(f"{i + class="num">1}. Name: {output_tensor.name}, Data Type: {output_tensor.type}, Shape: {output_tensor.shape}") # convert data to floating-point format(float32) X_float32 = X.astype(np.float32) # predict classes for the entire dataset using ONNX y_pred_onnx = onnx_session.run([output_name], {input_name: X_float32})[class="num">0] # evaluate the accuracy of the ONNX model
「把鸢尾花分类模型塞进 MT5 跑推理」
前面在 Python 端用 ONNX 格式导出逻辑回归交叉验证模型后,得到 0.98 上下的分类准确率(accuracy_onnx),这一步只是离线验证。真正要在 MT5 里复用,得把 .onnx 文件作为编译期资源嵌进去。
代码里用 #resource "logistic_regressioncv_iris.onnx" as const uchar ExtModel[] 把模型二进制直接打进 ex5,运行时再交给 Onnx 系列函数加载。TestSamples 函数先拿 input_data 的 Range(0) 取样本数,空批直接返回 false,避免空跑。
输入维度被锁死成 4 列(花萼长宽、花瓣长宽),output_data 按 batch_size 扩容,随后用 OnnxSetInputShape 把形状设成 {batch_size,4}。两个输出分支分别用 output_shape1/2 设为 {batch_size},对应类别 id 与概率序列。
OnnxRun 跑完以后,后处理段会从 output_data_map 里捞 key 和 value,找每样本最大概率对应的 class id 写回 model_classes_id。这套结构可以原样套到行情特征矩阵上,但外汇与贵金属波动远高于鸢尾花静态数据,实盘推理结果仅作概率参考,杠杆品种风险偏高。
class="macro">#class="kw">property copyright "Copyright class="num">2023, MetaQuotes Ltd." class="macro">#class="kw">property link "[MQL5官方文档] class="macro">#class="kw">property version "class="num">1.00" class="macro">#include "iris.mqh" class="macro">#resource "logistic_regressioncv_iris.onnx" as const class="type">uchar ExtModel[]; class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Test IRIS dataset samples | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">bool TestSamples(class="type">long model,class="type">float &input_data[][class="num">4], class="type">int &model_classes_id[]) { class=class="str">"cmt">//--- check number of input samples class="type">ulong batch_size=input_data.Range(class="num">0); if(batch_size==class="num">0) class="kw">return(false); class=class="str">"cmt">//--- prepare output array ArrayResize(model_classes_id,(class="type">int)batch_size); class=class="str">"cmt">//--- class="type">float output_data[]; class=class="str">"cmt">//--- class="kw">struct Map { class="type">ulong key[]; class="type">float value[]; } output_data_map[]; class=class="str">"cmt">//--- check consistency class="type">bool res=ArrayResize(output_data,(class="type">int)batch_size)==batch_size; class=class="str">"cmt">//--- if(res) { class=class="str">"cmt">//--- set input shape class="type">ulong input_shape[]= {batch_size,input_data.Range(class="num">1)}; OnnxSetInputShape(model,class="num">0,input_shape); class=class="str">"cmt">//--- set output shapeы class="type">ulong output_shape1[]= {batch_size}; class="type">ulong output_shape2[]= {batch_size}; OnnxSetOutputShape(model,class="num">0,output_shape1); OnnxSetOutputShape(model,class="num">1,output_shape2); class=class="str">"cmt">//--- run the model res=OnnxRun(model,class="num">0,input_data,output_data,output_data_map); class=class="str">"cmt">//--- postprocessing if(res) { class=class="str">"cmt">//--- postprocessing of sequence map data class=class="str">"cmt">//--- find class with maximum probability class="type">ulong output_keys[]; class="type">float output_values[]; class=class="str">"cmt">//---
从模型输出里捞最大概率分类
这段逻辑干的事很直白:遍历 output_data_map 里的每一组模型输出,在 key/value 两个数组里找出概率值最大的那个分类编号。外层 for 循环用 uint n 从 0 跑到 output_data_map.Size(),每一次都把对应项的 key 和 value 拷进本地数组 output_keys、output_values。 内层 for 从 k=0 扫到 ArraySize(output_values)-1,首轮直接把第 0 位当临时最大值;之后只要碰到 output_values[k] 比当前 max_value 大,就刷新 max_idx、max_value 和 model_class_id。最终 model_class_id 强转成 int 存进 model_classes_id[n],这就是该样本的预测类别。 后面 TestAllIrisDataset 函数顺手用 IRIS 数据集(共 150 个样本)做全量验证,batch=1 逐个样本喂进去。你在 MT5 里把 PrepareIrisDataset 接好、跑通这 150 条,就能直接看到模型分类准确率 model_accuracy 的实际数值,外汇或贵金属品种上套用同类推理也别忘:样本外泛化能力可能打折,杠杆品种高风险。
for(class="type">uint n=class="num">0; n<output_data_map.Size(); n++) { class="type">int model_class_id=-class="num">1; class="type">int max_idx=-class="num">1; class="type">float max_value=-class="num">1; class=class="str">"cmt">//--- copy to arrays ArrayCopy(output_keys,output_data_map[n].key); ArrayCopy(output_values,output_data_map[n].value); class=class="str">"cmt">//ArrayPrint(output_keys); class=class="str">"cmt">//ArrayPrint(output_values); class=class="str">"cmt">//--- find the key with maximum probability for(class="type">int k=class="num">0; k<ArraySize(output_values); k++) { if(k==class="num">0) { max_idx=class="num">0; max_value=output_values[max_idx]; model_class_id=(class="type">int)output_keys[max_idx]; } else { if(output_values[k]>max_value) { max_idx=k; max_value=output_values[max_idx]; model_class_id=(class="type">int)output_keys[max_idx]; } } } class=class="str">"cmt">//--- store the result to the output array model_classes_id[n]=model_class_id; class=class="str">"cmt">//Print("model_class_id=",model_class_id); } } } class=class="str">"cmt">//--- class="kw">return(res); } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Test all samples from IRIS dataset(class="num">150) | class=class="str">"cmt">//| Here we test all samples with batch=class="num">1, sample by sample | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">bool TestAllIrisDataset(const class="type">long model,const class="type">class="kw">string model_name,class="type">class="kw">double &model_accuracy) { sIRISsample iris_samples[]; class=class="str">"cmt">//--- load dataset from file PrepareIrisDataset(iris_samples); class=class="str">"cmt">//--- test class="type">int total_samples=ArraySize(iris_samples); if(total_samples==class="num">0) { Print("iris dataset not prepared");
◍ 跑通鸢尾花样本看模型命中率
这段逻辑把已加载的模型直接丢进鸢尾花(Iris)数据集做全样本推理,核心目的不是交易预测,而是验证 ONNX 模型在 MT5 内的前向传播链路是否通畅。循环里逐条取 4 个特征塞进二维浮点数组,再调 TestSamples 拿输出类,和样本真实 class_id 比对计数。 correct_results 累加后,model_accuracy 用 1.0*correct_results/total_samples 算出小数精度,打印时乘 100 得到百分比。若 150 条全样本跑完,correct_results=147,终端会输出 'correct results: 98.00%',这种回显能让你立刻判断模型文件有没有被错误量化或维度错位。 下面的 TestBatchExecution 换成批量喂入,input_data_batch3[3][4] 一次装 3 条样本,total_results 记录总推理条数。外汇与贵金属行情的高噪声特征和鸢尾花静态数据集完全不同,直接套用这类静态分类准确率容易高估实盘泛化能力,接入 tick 序列前务必做 walk-forward 验证。
class="kw">return(false); } class=class="str">"cmt">//--- show dataset for(class="type">int k=class="num">0;k<total_samples; k++) { class=class="str">"cmt">//PrintFormat("%d(%.2f,%.2f,%.2f,%.2f) class %d(%s)",iris_samples[k].sample_id,iris_samples[k].features[class="num">0],iris_samples[k].features[class="num">1],iris_samples[k].features[class="num">2],iris_samples[k].features[class="num">3],iris_samples[k].class_id,iris_samples[k].class_name); } class=class="str">"cmt">//--- array for output classes class="type">int model_output_classes_id[]; class=class="str">"cmt">//--- check all Iris dataset samples class="type">int correct_results=class="num">0; for(class="type">int k=class="num">0;k<total_samples; k++) { class=class="str">"cmt">//--- input array class="type">float iris_sample_input_data[class="num">1][class="num">4]; class=class="str">"cmt">//--- prepare input data from kth iris sample dataset iris_sample_input_data[class="num">0][class="num">0]=(class="type">float)iris_samples[k].features[class="num">0]; iris_sample_input_data[class="num">0][class="num">1]=(class="type">float)iris_samples[k].features[class="num">1]; iris_sample_input_data[class="num">0][class="num">2]=(class="type">float)iris_samples[k].features[class="num">2]; iris_sample_input_data[class="num">0][class="num">3]=(class="type">float)iris_samples[k].features[class="num">3]; class=class="str">"cmt">//--- run model class="type">bool res=TestSamples(model,iris_sample_input_data,model_output_classes_id); class=class="str">"cmt">//--- check result if(res) { if(model_output_classes_id[class="num">0]==iris_samples[k].class_id) { correct_results++; } else { PrintFormat("model:%s sample=%d FAILED [class=%d, true class=%d] features=(%.2f,%.2f,%.2f,%.2f)",model_name,iris_samples[k].sample_id,model_output_classes_id[class="num">0],iris_samples[k].class_id,iris_samples[k].features[class="num">0],iris_samples[k].features[class="num">1],iris_samples[k].features[class="num">2],iris_samples[k].features[class="num">3]); } } } model_accuracy=class="num">1.0*correct_results/total_samples; class=class="str">"cmt">//--- PrintFormat("model:%s correct results: %.2f%%",model_name,class="num">100*model_accuracy); class=class="str">"cmt">//--- class="kw">return(true); } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Here we test batch execution of the model | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">bool TestBatchExecution(const class="type">long model,const class="type">class="kw">string model_name,class="type">class="kw">double &model_accuracy) { model_accuracy=class="num">0; class=class="str">"cmt">//--- array for output classes class="type">int model_output_classes_id[]; class="type">int correct_results=class="num">0; class="type">int total_results=class="num">0; class="type">bool res=false; class=class="str">"cmt">//--- run batch with class="num">3 samples class="type">float input_data_batch3[class="num">3][class="num">4]= {
「拿鸢尾花样本给模型做批量验证」
模型训完不能只看训练集误差,得用留出样本跑前向推理看分类对不对。下面这段先塞了 3 条已知类别的数据:setosa(id=1,花萼长 5.1、宽 3.5、花瓣长 1.4、宽 0.2)、versicolor(id=73,6.3/2.5/4.9/1.5)、virginica(id=124,6.3/2.7/4.9/1.8),正确标签数组是 {0,1,2}。 调用 TestSamples 拿到 model_output_classes_id 后,逐条比对:命中就 correct_results++,没命中就 PrintFormat 把模型名、预测类、真类、四个特征全部打印出来,方便你直接在 MT5 Experts 日志里抓错分样本。 紧接着又上了 10 条样本的 batch:前 4 条全是 setosa(id 37/38/39/50),中间 2 条 versicolor(51/52),后 4 条 virginica(101–104)。特征数值跨度从花瓣长 1.3 到 6.0,足够检验边界样本。把这两段代码贴进你的 EA 测试函数,跑通后 correct_results 等于 13 即说明三分类在当前权重下全对,外汇贵金属模型迁移时同样建议先用静态 batch 验分类再上 tick 数据,杠杆品种误分成本远高于鸢尾花。
{class="num">5.1f,class="num">3.5f,class="num">1.4f,class="num">0.2f}, class=class="str">"cmt">// iris dataset sample id=class="num">1, Iris-setosa
{class="num">6.3f,class="num">2.5f,class="num">4.9f,class="num">1.5f}, class=class="str">"cmt">// iris dataset sample id=class="num">73, Iris-versicolor
{class="num">6.3f,class="num">2.7f,class="num">4.9f,class="num">1.8f} class=class="str">"cmt">// iris dataset sample id=class="num">124, Iris-virginica
};
class="type">int correct_classes_batch3[class="num">3]= {class="num">0,class="num">1,class="num">2};
class=class="str">"cmt">//--- run model
res=TestSamples(model,input_data_batch3,model_output_classes_id);
if(res)
{
class=class="str">"cmt">//--- check result
for(class="type">int j=class="num">0; j<ArraySize(model_output_classes_id); j++)
{
class=class="str">"cmt">//--- check result
if(model_output_classes_id[j]==correct_classes_batch3[j])
correct_results++;
else
{
PrintFormat("model:%s FAILED [class=%d, true class=%d] features=(%.2f,%.2f,%.2f,%.2f)",model_name,model_output_classes_id[j],correct_classes_batch3[j],input_data_batch3[j][class="num">0],input_data_batch3[j][class="num">1],input_data_batch3[j][class="num">2],input_data_batch3[j][class="num">3]);
}
total_results++;
}
}
else
class="kw">return(false);
class=class="str">"cmt">//--- run batch with class="num">10 samples
class="type">float input_data_batch10[class="num">10][class="num">4]=
{
{class="num">5.5f,class="num">3.5f,class="num">1.3f,class="num">0.2f}, class=class="str">"cmt">// iris dataset sample id=class="num">37 (Iris-setosa)
{class="num">4.9f,class="num">3.1f,class="num">1.5f,class="num">0.1f}, class=class="str">"cmt">// iris dataset sample id=class="num">38 (Iris-setosa)
{class="num">4.4f,class="num">3.0f,class="num">1.3f,class="num">0.2f}, class=class="str">"cmt">// iris dataset sample id=class="num">39 (Iris-setosa)
{class="num">5.0f,class="num">3.3f,class="num">1.4f,class="num">0.2f}, class=class="str">"cmt">// iris dataset sample id=class="num">50 (Iris-setosa)
{class="num">7.0f,class="num">3.2f,class="num">4.7f,class="num">1.4f}, class=class="str">"cmt">// iris dataset sample id=class="num">51 (Iris-versicolor)
{class="num">6.4f,class="num">3.2f,class="num">4.5f,class="num">1.5f}, class=class="str">"cmt">// iris dataset sample id=class="num">52 (Iris-versicolor)
{class="num">6.3f,class="num">3.3f,class="num">6.0f,class="num">2.5f}, class=class="str">"cmt">// iris dataset sample id=class="num">101 (Iris-virginica)
{class="num">5.8f,class="num">2.7f,class="num">5.1f,class="num">1.9f}, class=class="str">"cmt">// iris dataset sample id=class="num">102 (Iris-virginica)
{class="num">7.1f,class="num">3.0f,class="num">5.9f,class="num">2.1f}, class=class="str">"cmt">// iris dataset sample id=class="num">103 (Iris-virginica)
{class="num">6.3f,class="num">2.9f,class="num">5.6f,class="num">1.8f} class=class="str">"cmt">// iris dataset sample id=class="num">104 (Iris-virginica)
};
class=class="str">"cmt">//--- correct classes for all class="num">10 samples in the batch用鸢尾花数据校验 ONNX 分类模型准确率
在 MT5 里跑通 ONNX 模型后,最实在的一步是拿已知标签的小批量样本去验分类准不准。下面这段逻辑用 10 条样本(correct_classes_batch10 中 0/1/2 类分别占 4、2、4 条)比对模型输出,逐条累计 correct_results,最后用 correct_results/total_results 算出 model_accuracy。 别把正态当圣经:样例里特征只有 4 维且类别均衡,实盘外汇或贵金属的 K 线特征往往维度更高、类间极度不平衡,直接套同样阈值可能虚高。 TestSamples 返回 true 才进比对循环;任一条输出与正确标签不符,就 PrintFormat 打出该样本四维特征值和误判类号,方便你回 MT5 日志里定位是哪段行情特征让模型偏了。 OnStart 中先 OnnxCreateFromBuffer 载模型,句柄无效就报 GetLastError;有效则先后跑全量 Iris 与 10 条批量测试,终端会打印类似 'model=LogisticRegressionCVClassifier batch test accuracy=1.000000' 的数值——若你换自己的贵金属波动特征集,这个准确率大概率会掉,属正常。
class="type">int correct_classes_batch10[class="num">10]= {class="num">0,class="num">0,class="num">0,class="num">0,class="num">1,class="num">1,class="num">2,class="num">2,class="num">2,class="num">2}; class=class="str">"cmt">//--- run model res=TestSamples(model,input_data_batch10,model_output_classes_id); class=class="str">"cmt">//--- check result if(res) { for(class="type">int j=class="num">0; j<ArraySize(model_output_classes_id); j++) { if(model_output_classes_id[j]==correct_classes_batch10[j]) correct_results++; else { class="type">class="kw">double f1=input_data_batch10[j][class="num">0]; class="type">class="kw">double f2=input_data_batch10[j][class="num">1]; class="type">class="kw">double f3=input_data_batch10[j][class="num">2]; class="type">class="kw">double f4=input_data_batch10[j][class="num">3]; PrintFormat("model:%s FAILED [class=%d, true class=%d] features=(%.2f,%.2f,%.2f,%.2f)",model_name,model_output_classes_id[j],correct_classes_batch10[j],input_data_batch10[j][class="num">0],input_data_batch10[j][class="num">1],input_data_batch10[j][class="num">2],input_data_batch10[j][class="num">3]); } total_results++; } } else class="kw">return(false); class=class="str">"cmt">//--- calculate accuracy model_accuracy=correct_results/total_results; class=class="str">"cmt">//--- class="kw">return(res); } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Script program start function | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">int OnStart(class="type">void) { class="type">class="kw">string model_name="LogisticRegressionCVClassifier"; class=class="str">"cmt">//--- class="type">long model=OnnxCreateFromBuffer(ExtModel,ONNX_DEFAULT); if(model==INVALID_HANDLE) { PrintFormat("model_name=%s OnnxCreate error %d for",model_name,GetLastError()); } else { class=class="str">"cmt">//--- test all dataset class="type">class="kw">double model_accuracy=class="num">0; class=class="str">"cmt">//-- test sample by sample execution for all Iris dataset if(TestAllIrisDataset(model,model_name,model_accuracy)) PrintFormat("model=%s all samples accuracy=%f",model_name,model_accuracy); else PrintFormat("error in testing model=%s ",model_name); class=class="str">"cmt">//--- test batch execution for several samples if(TestBatchExecution(model,model_name,model_accuracy)) PrintFormat("model=%s batch test accuracy=%f",model_name,model_accuracy); else PrintFormat("error in testing model=%s ",model_name); class=class="str">"cmt">//--- release model
◍ 逻辑回归分类器在EURUSD_H1上的误判样本与精度
把 ONNX 模型跑完推断后记得用 OnnxRelease(model) 释放句柄,否则 MT5 终端跑多轮回测时显存/内存会悄悄堆积。上面这段尾部日志来自 EURUSD H1 周期下 LogisticRegressionCVClassifier 的批量验证输出。 具体看失败样本:sample=71 与 sample=84 都是预测 class=2 但真实为 class=1,特征向量分别是 (5.90,3.20,4.80,1.80) 和 (6.00,2.70,5.10,1.60);sample=134 反向误判,预测 class=1 真实 class=2,特征为 (6.30,2.80,5.10,1.50)。这三笔错分集中在花萼/花瓣尺寸交界区,说明线性边界对该区间敏感度偏高。 整体指标上,correct results 报 98.00%,all samples accuracy=0.980000,而 batch test accuracy=1.000000。后者是分批推断时恰好避开了那 3 个错分样本,并非模型质变。外汇与贵金属属高风险品种,这类 98% 回测精度仅反映历史样本拟合,实盘信号概率仍可能漂移。 若你想换一个轻量在线学习器做对照,可以用 Passive-Aggressive Classifier 重跑同一批 Iris 特征再转 ONNX。下面这段 Python 即生成 pa_classifier_iris.onnx 的可用脚本,max_iter=1000、random_state=42 固定后结果可复现。
# Iris_PassiveAgressiveClassifier.py # The code uses the Passive-Aggressive(PA) Classifier for the Iris dataset, converts the model to ONNX format, saves it, and evaluates its accuracy. # It also evaluates the accuracy of both the original model and the ONNX model. # class="kw">import necessary libraries from sklearn class="kw">import datasets from sklearn.linear_model class="kw">import PassiveAggressiveClassifier from sklearn.metrics class="kw">import accuracy_score, classification_report from skl2onnx class="kw">import convert_sklearn from skl2onnx.common.data_types class="kw">import FloatTensorType class="kw">import onnxruntime as ort class="kw">import numpy as np from sys class="kw">import argv # define the path for saving the model data_path = argv[class="num">0] last_index = data_path.rfind("\\") + class="num">1 data_path = data_path[class="num">0:last_index] # load the Iris dataset iris = datasets.load_iris() X = iris.data y = iris.target # create a Passive-Aggressive(PA) Classifier model pa_classifier_model = PassiveAggressiveClassifier(max_iter=class="num">1000, random_state=class="num">42) # train the model on the entire dataset pa_classifier_model.fit(X, y) # predict classes for the entire dataset y_pred = pa_classifier_model.predict(X) # evaluate the model&class="macro">#x27;s accuracy accuracy = accuracy_score(y, y_pred) print("Accuracy of Passive-Aggressive(PA) Classifier model:", accuracy) # display the classification report print("\nClassification Report:\n", classification_report(y, y_pred)) # define the input data type initial_type = [(&class="macro">#x27;float_input&class="macro">#x27;, FloatTensorType([None, X.shape[class="num">1]]))] # class="kw">export the model to ONNX format with class="type">float data type onnx_model = convert_sklearn(pa_classifier_model, initial_types=initial_type, target_opset=class="num">12) # save the model to a file onnx_filename = data_path + "pa_classifier_iris.onnx" with open(onnx_filename, "wb") as f: f.write(onnx_model.SerializeToString()) # print model path print(f"Model saved to {onnx_filename}") # load the ONNX model and make predictions onnx_session = ort.InferenceSession(onnx_filename)
「把 PA 分类器塞进 MT5 前先看张量和精度」
在 Python 侧加载 ONNX 会话后,先抓输入和输出的名字:input_name 取第 0 个输入的 name,output_name 取第 0 个输出的 name。这两个字符串是后面 run 调用时的钥匙,写错一个就会报 shape mismatch。 打印输入输出张量信息能直接看到数据类型和维度。比如 Iris 数据集这里输入 shape 通常是 [N,4]、输出是 [N],N 为样本数;跑一遍能确认 ONNX 导出的 Passive-Aggressive 模型结构和你训练时一致。 数据必须转成 float32 再喂给会话:X_float32 = X.astype(np.float32)。用 onnx_session.run([output_name], {input_name: X_float32})[0] 拿到全量预测,再用 accuracy_score(y, y_pred_onnx) 算准确率——同一份 Iris 数据下,ONNX 推理精度应与 sklearn 原模型一致(差异通常为 0.0)。 MT5 端用 #resource 把 pa_classifier_iris.onnx 编进 exe,TestSamples 里先查 batch_size,空样本直接返回 false;输出数组按 batch_size resize。输入 shape 硬编码为 {batch_size, 4},对应 Iris 四个特征,接 ONNX 模型前必须保证 extern uchar ExtModel[] 已正确嵌入资源。外汇与贵金属行情用类似推理框架时,样本外泛化能力可能明显低于回测,属高风险应用。
input_name = onnx_session.get_inputs()[class="num">0].name output_name = onnx_session.get_outputs()[class="num">0].name # display information about input tensors in ONNX print("\nInformation about input tensors in ONNX:") for i, input_tensor in enumerate(onnx_session.get_inputs()): print(f"{i + class="num">1}. Name: {input_tensor.name}, Data Type: {input_tensor.type}, Shape: {input_tensor.shape}") # display information about output tensors in ONNX print("\nInformation about output tensors in ONNX:") for i, output_tensor in enumerate(onnx_session.get_outputs()): print(f"{i + class="num">1}. Name: {output_tensor.name}, Data Type: {output_tensor.type}, Shape: {output_tensor.shape}") # convert data to floating-point format(float32) X_float32 = X.astype(np.float32) # predict classes for the entire dataset using ONNX y_pred_onnx = onnx_session.run([output_name], {input_name: X_float32})[class="num">0] # evaluate the accuracy of the ONNX model accuracy_onnx = accuracy_score(y, y_pred_onnx) print("\nAccuracy of Passive-Aggressive(PA) Classifier model in ONNX format:", accuracy_onnx)
从 ONNX 输出映射里捞最大概率分类
跑完 OnnxRun 之后,模型吐回来的是两张输出:output_data 和 output_data_map,后者是带 key(类别 id)和 value(概率)的序列映射。后处理的核心动作,就是遍历这个 map,把每条样本里概率最高的那个类别抠出来。 代码里先给两个输出都设了 batch_size 维度的 shape,再调 OnnxRun 拿到结果。紧接着用 output_data_map.Size() 控制外层循环,每个 n 对应一批里的一条样本;内部用 ArrayCopy 把 key 和 value 分别塞进 output_keys、output_values 两个数组,方便后面比大小。 找最大值的逻辑很直白:k 从 0 开始,第一轮直接把第 0 位当临时最大值,之后但凡遇到 output_values[k] 更大的就覆盖 max_value 和 max_idx,同时把 model_class_id 更新成对应的 output_keys 值。一轮扫完,model_class_id 就是这条样本模型给的最高概率类别。 最后把 model_class_id 写进 model_classes_id[n],外层循环走完,整个 batch 的预测类别就齐了。在 MT5 里接自己的 ONNX 分类模型时,这段后处理可以直接抄,只要确认你的 map 里 value 确实是 softmax 概率、key 是连续或离散的类别编号即可;外汇与贵金属行情下用模型信号须警惕过拟合与滑点风险,信号仅作概率参考。
OnnxSetInputShape(model,class="num">0,input_shape); class=class="str">"cmt">//--- set output shapeы class="type">ulong output_shape1[]= {batch_size}; class="type">ulong output_shape2[]= {batch_size}; OnnxSetOutputShape(model,class="num">0,output_shape1); OnnxSetOutputShape(model,class="num">1,output_shape2); class=class="str">"cmt">//--- run the model res=OnnxRun(model,class="num">0,input_data,output_data,output_data_map); class=class="str">"cmt">//--- postprocessing if(res) { class=class="str">"cmt">//--- postprocessing of sequence map data class=class="str">"cmt">//--- find class with maximum probability class="type">ulong output_keys[]; class="type">float output_values[]; class=class="str">"cmt">//--- for(class="type">uint n=class="num">0; n<output_data_map.Size(); n++) { class="type">int model_class_id=-class="num">1; class="type">int max_idx=-class="num">1; class="type">float max_value=-class="num">1; class=class="str">"cmt">//--- copy to arrays ArrayCopy(output_keys,output_data_map[n].key); ArrayCopy(output_values,output_data_map[n].value); class=class="str">"cmt">//ArrayPrint(output_keys); class=class="str">"cmt">//ArrayPrint(output_values); class=class="str">"cmt">//--- find the key with maximum probability for(class="type">int k=class="num">0; k<ArraySize(output_values); k++) { if(k==class="num">0) { max_idx=class="num">0; max_value=output_values[max_idx]; model_class_id=(class="type">int)output_keys[max_idx]; } else { if(output_values[k]>max_value) { max_idx=k; max_value=output_values[max_idx]; model_class_id=(class="type">int)output_keys[max_idx]; } } } class=class="str">"cmt">//--- store the result to the output array model_classes_id[n]=model_class_id; class=class="str">"cmt">//Print("model_class_id=",model_class_id);
◍ 逐样本跑完 IRIS 全集算准确率
把训练好的模型接上 IRIS 数据集做全量验证,是检验分类器泛化能力的直接办法。下面这段代码逐条读取 150 个样本,单条推理、比对真值,最后算出正确率。 函数开头先 PrepareIrisDataset 把样本塞进 iris_samples 数组,ArraySize 取到 total_samples。若返回 0 会直接 Print 报错并退出,避免空跑。 核心循环里每条样本被压成 1×4 的 float 矩阵 iris_sample_input_data,四个特征依次填入。TestSamples 跑完把预测类标写进 model_output_classes_id,与样本自身 class_id 一致才算 correct_results 加一,否则打印哪条样本翻车。 最终 model_accuracy 用 correct_results 除以总样本数,PrintFormat 打出如「model:xxx correct results: 96.00%」的日志。外汇与贵金属行情的高风险提醒:这类静态数据集验证和实时报价分布差异极大,模型准确率不能直接外推到实盘信号。
class="type">bool TestAllIrisDataset(const class="type">long model,const class="type">class="kw">string model_name,class="type">class="kw">double &model_accuracy) { sIRISsample iris_samples[]; class=class="str">"cmt">//--- load dataset from file PrepareIrisDataset(iris_samples); class=class="str">"cmt">//--- test class="type">int total_samples=ArraySize(iris_samples); if(total_samples==class="num">0) { Print("iris dataset not prepared"); class="kw">return(false); } class=class="str">"cmt">//--- show dataset for(class="type">int k=class="num">0;k<total_samples; k++) { class=class="str">"cmt">//PrintFormat("%d(%.2f,%.2f,%.2f,%.2f) class %d(%s)",iris_samples[k].sample_id,iris_samples[k].features[class="num">0],iris_samples[k].features[class="num">1],iris_samples[k].features[class="num">2],iris_samples[k].features[class="num">3],iris_samples[k].class_id,iris_samples[k].class_name); } class=class="str">"cmt">//--- array for output classes class="type">int model_output_classes_id[]; class=class="str">"cmt">//--- check all Iris dataset samples class="type">int correct_results=class="num">0; for(class="type">int k=class="num">0;k<total_samples; k++) { class=class="str">"cmt">//--- input array class="type">float iris_sample_input_data[class="num">1][class="num">4]; class=class="str">"cmt">//--- prepare input data from kth iris sample dataset iris_sample_input_data[class="num">0][class="num">0]=(class="type">float)iris_samples[k].features[class="num">0]; iris_sample_input_data[class="num">0][class="num">1]=(class="type">float)iris_samples[k].features[class="num">1]; iris_sample_input_data[class="num">0][class="num">2]=(class="type">float)iris_samples[k].features[class="num">2]; iris_sample_input_data[class="num">0][class="num">3]=(class="type">float)iris_samples[k].features[class="num">3]; class=class="str">"cmt">//--- run model class="type">bool res=TestSamples(model,iris_sample_input_data,model_output_classes_id); class=class="str">"cmt">//--- check result if(res) { if(model_output_classes_id[class="num">0]==iris_samples[k].class_id) { correct_results++; } else { PrintFormat("model:%s sample=%d FAILED [class=%d, true class=%d] features=(%.2f,%.2f,%.2f,%.2f)",model_name,iris_samples[k].sample_id,model_output_classes_id[class="num">0],iris_samples[k].class_id,iris_samples[k].features[class="num">0],iris_samples[k].features[class="num">1],iris_samples[k].features[class="num">2],iris_samples[k].features[class="num">3]); } } } model_accuracy=class="num">1.0*correct_results/total_samples; class=class="str">"cmt">//--- PrintFormat("model:%s correct results: %.2f%%",model_name,class="num">100*model_accuracy); class=class="str">"cmt">//---
「批量推理的准确率怎么跑出来」
在 MT5 里验证一个 ONNX 模型不能只看单次预测,批量跑几组已知标签的样本才能看出模型稳定性。下面这段函数用鸢尾花数据集做探针:先喂 3 条样本(ID 1/73/124,分别对应 setosa/versicolor/virginica),再喂 10 条样本,逐条比对输出类别和真实类别。 函数开头把 model_accuracy 清零,声明输出类别数组 model_output_classes_id、正确计数 correct_results 和总数 total_results。3 样本批次的输入矩阵是 float input_data_batch3[3][4],每条含 4 个特征,correct_classes_batch3 写死为 {0,1,2}。 调用 TestSamples 拿到预测类别后,用 for 循环遍历 ArraySize(model_output_classes_id),若预测值等于真值则 correct_results++,否则 PrintFormat 打出模型名、错分详情和四条特征。10 样本批次逻辑完全相同,只是输入扩到 10 行。 你在 EA 里照抄这套批测骨架,把 input_data 换成自己的行情特征(比如 H1 的 OHLC+波动率),就能在日志里直接数出 correct_results/total_results,得到该模型在你数据上的近似准确率。外汇与贵金属行情噪声大,离线准确率不代表实盘概率,务必用小资金验证。
class="type">bool TestBatchExecution(const class="type">long model,const class="type">class="kw">string model_name,class="type">class="kw">double &model_accuracy) { model_accuracy=class="num">0; class=class="str">"cmt">//--- array for output classes class="type">int model_output_classes_id[]; class="type">int correct_results=class="num">0; class="type">int total_results=class="num">0; class="type">bool res=false; class=class="str">"cmt">//--- run batch with class="num">3 samples class="type">float input_data_batch3[class="num">3][class="num">4]= { {class="num">5.1f,class="num">3.5f,class="num">1.4f,class="num">0.2f}, class=class="str">"cmt">// iris dataset sample id=class="num">1, Iris-setosa {class="num">6.3f,class="num">2.5f,class="num">4.9f,class="num">1.5f}, class=class="str">"cmt">// iris dataset sample id=class="num">73, Iris-versicolor {class="num">6.3f,class="num">2.7f,class="num">4.9f,class="num">1.8f} class=class="str">"cmt">// iris dataset sample id=class="num">124, Iris-virginica }; class="type">int correct_classes_batch3[class="num">3]= {class="num">0,class="num">1,class="num">2}; class=class="str">"cmt">//--- run model res=TestSamples(model,input_data_batch3,model_output_classes_id); if(res) { class=class="str">"cmt">//--- check result for(class="type">int j=class="num">0; j<ArraySize(model_output_classes_id); j++) { class=class="str">"cmt">//--- check result if(model_output_classes_id[j]==correct_classes_batch3[j]) correct_results++; else { PrintFormat("model:%s FAILED [class=%d, true class=%d] features=(%.2f,%.2f,%.2f,%.2f)",model_name,model_output_classes_id[j],correct_classes_batch3[j],input_data_batch3[j][class="num">0],input_data_batch3[j][class="num">1],input_data_batch3[j][class="num">2],input_data_batch3[j][class="num">3]); } total_results++; } } else class="kw">return(false); class=class="str">"cmt">//--- run batch with class="num">10 samples class="type">float input_data_batch10[class="num">10][class="num">4]= { {class="num">5.5f,class="num">3.5f,class="num">1.3f,class="num">0.2f}, class=class="str">"cmt">// iris dataset sample id=class="num">37 (Iris-setosa) {class="num">4.9f,class="num">3.1f,class="num">1.5f,class="num">0.1f}, class=class="str">"cmt">// iris dataset sample id=class="num">38 (Iris-setosa) {class="num">4.4f,class="num">3.0f,class="num">1.3f,class="num">0.2f}, class=class="str">"cmt">// iris dataset sample id=class="num">39 (Iris-setosa) {class="num">5.0f,class="num">3.3f,class="num">1.4f,class="num">0.2f}, class=class="str">"cmt">// iris dataset sample id=class="num">50 (Iris-setosa) {class="num">7.0f,class="num">3.2f,class="num">4.7f,class="num">1.4f}, class=class="str">"cmt">// iris dataset sample id=class="num">51 (Iris-versicolor)
用鸢尾花样本压测分类模型准确率
这段逻辑给了一批 10 条的批量样本做模型推理校验,其中后 5 条(id=100~104)是 Iris-virginica,前 4 条为 Iris-setosa、第 5 条为 Iris-versicolor,correct_classes_batch10 数组写死了真实标签 {0,0,0,0,1,1,2,2,2,2}。TestSamples 跑完之后,逐条比对输出类与真实类,命中就 correct_results++,否则把四条特征打印出来定位错分样本。 准确率直接由 correct_results/total_results 得出,total_results 恒等于 10,所以这就是批内分类正确率。外汇与贵金属行情序列若套用同类 ONNX 分类器,过拟合风险高,小样本准确率不能直接外推到实盘。 OnStart 里先用 OnnxCreateFromBuffer 从内存缓冲区加载 PassiveAgressiveClassifier 模型,句柄无效就打错误码退出;有效则进入全量数据集测试分支,把 model_accuracy 初始化为 0 再交给测试函数填充。开 MT5 把这段挂到脚本里,改 ExtModel 换你自己的.onnx,就能复算这批样本的 10 条准确率。
{class="num">6.4f,class="num">3.2f,class="num">4.5f,class="num">1.5f}, class=class="str">"cmt">// iris dataset sample id=class="num">52 (Iris-versicolor)
{class="num">6.3f,class="num">3.3f,class="num">6.0f,class="num">2.5f}, class=class="str">"cmt">// iris dataset sample id=class="num">101 (Iris-virginica)
{class="num">5.8f,class="num">2.7f,class="num">5.1f,class="num">1.9f}, class=class="str">"cmt">// iris dataset sample id=class="num">102 (Iris-virginica)
{class="num">7.1f,class="num">3.0f,class="num">5.9f,class="num">2.1f}, class=class="str">"cmt">// iris dataset sample id=class="num">103 (Iris-virginica)
{class="num">6.3f,class="num">2.9f,class="num">5.6f,class="num">1.8f} class=class="str">"cmt">// iris dataset sample id=class="num">104 (Iris-virginica)
};
class=class="str">"cmt">//--- correct classes for all class="num">10 samples in the batch
class="type">int correct_classes_batch10[class="num">10]= {class="num">0,class="num">0,class="num">0,class="num">0,class="num">1,class="num">1,class="num">2,class="num">2,class="num">2,class="num">2};
class=class="str">"cmt">//--- run model
res=TestSamples(model,input_data_batch10,model_output_classes_id);
class=class="str">"cmt">//--- check result
if(res)
{
for(class="type">int j=class="num">0; j<ArraySize(model_output_classes_id); j++)
{
if(model_output_classes_id[j]==correct_classes_batch10[j])
correct_results++;
else
{
class="type">class="kw">double f1=input_data_batch10[j][class="num">0];
class="type">class="kw">double f2=input_data_batch10[j][class="num">1];
class="type">class="kw">double f3=input_data_batch10[j][class="num">2];
class="type">class="kw">double f4=input_data_batch10[j][class="num">3];
PrintFormat("model:%s FAILED [class=%d, true class=%d] features=(%.2f,%.2f,%.2f,%.2f)",model_name,model_output_classes_id[j],correct_classes_batch10[j],input_data_batch10[j][class="num">0],input_data_batch10[j][class="num">1],input_data_batch10[j][class="num">2],input_data_batch10[j][class="num">3]);
}
total_results++;
}
}
else
class="kw">return(false);
class=class="str">"cmt">//--- calculate accuracy
model_accuracy=correct_results/total_results;
class=class="str">"cmt">//---
class="kw">return(res);
}
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Script program start function |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">int OnStart(class="type">void)
{
class="type">class="kw">string model_name="PassiveAgressiveClassifier";
class=class="str">"cmt">//---
class="type">long model=OnnxCreateFromBuffer(ExtModel,ONNX_DEFAULT);
if(model==INVALID_HANDLE)
{
PrintFormat("model_name=%s OnnxCreate error %d for",model_name,GetLastError());
}
else
{
class=class="str">"cmt">//--- test all dataset
class="type">class="kw">double model_accuracy=class="num">0;◍ 被动感知机在EURUSD-H1上的逐样本验证与释放
把训练好的 ONNX 模型接进 MT5 后,先跑全样本逐条推理,再跑一小批批量推理,两者精度会暴露不同层面的过拟合。下面这段执行逻辑就是典型的验证骨架:先 TestAllIrisDataset 逐条过,再 TestBatchExecution 批量过,最后必须 OnnxRelease 释放句柄,否则 EA 跑久了会漏内存。 //-- test sample by sample execution for all Iris dataset if(TestAllIrisDataset(model,model_name,model_accuracy)) PrintFormat("model=%s all samples accuracy=%f",model_name,model_accuracy); else PrintFormat("error in testing model=%s ",model_name); //--- test batch execution for several samples if(TestBatchExecution(model,model_name,model_accuracy)) PrintFormat("model=%s batch test accuracy=%f",model_name,model_accuracy); else PrintFormat("error in testing model=%s ",model_name); //--- release model OnnxRelease(model); 逐行拆解:第1行注释说明接下来做全样本逐条测试;第2行调用 TestAllIrisDataset,入参是模型指针、模型名和接收精度的变量;第3–4行若成功则用 PrintFormat 打出模型名与全样本精度(%f 浮点);第5–7行失败则仅报模型名错误;第8行注释切到批量测试;第9–12行逻辑同前,只是换成 TestBatchExecution 验证批推理;第13行注释提示释放;第14行 OnnxRelease(model) 收回模型资源。 实跑日志里 PassiveAgressiveClassifier 在 EURUSD,H1 上翻了 6 个错:样本 67/71/84/85 把真类 1 判成 2,样本 130/134 把真类 2 判成 1,特征集中在萼片宽 2.7–3.2、花瓣长 4.5–5.8 这段模糊带。全样本精度 96.00%(0.96),而批量测试精度到了 100.00%(1.00)——批量挑的样本可能刚好避开了边界case,别被 1.0 迷惑。 外汇与贵金属属高风险品种,这类 96% 回测精度只是历史样本表现,实盘换周期或品种可能明显衰减,上 MT5 验证前先想清楚过拟合边界。
class=class="str">"cmt">//-- test sample by sample execution for all Iris dataset if(TestAllIrisDataset(model,model_name,model_accuracy)) PrintFormat("model=%s all samples accuracy=%f",model_name,model_accuracy); else PrintFormat("error in testing model=%s ",model_name); class=class="str">"cmt">//--- test batch execution for several samples if(TestBatchExecution(model,model_name,model_accuracy)) PrintFormat("model=%s batch test accuracy=%f",model_name,model_accuracy); else PrintFormat("error in testing model=%s ",model_name); class=class="str">"cmt">//--- release model OnnxRelease(model);
「把感知机塞进 ONNX 再喂回 MT5 的链路」
想在 MT5 里跑 sklearn 训出来的分类模型,中间得先转成 ONNX。下面这段 Python 把 Iris 数据集上的感知机(max_iter=1000, random_state=42)整训练、导出、再用 onnxruntime 复核一遍精度,实跑下来原生与 ONNX 两次 accuracy 应当一致,可作为你后续接行情特征的基线参照。 转换时 initial_type 必须显式声明为 FloatTensorType([None, 4]),对应 Iris 的 4 维特征;target_opset=12 是多数 MT5 端 ONNX 推理组件能认的版本,乱填高版本可能加载失败。 外汇与贵金属行情做这类分类特征时波动剧烈、样本分布漂移快,模型历史精度不代表实盘倾向,任何信号都只是概率参考,杠杆品种高风险莫忽视。 导出文件命名 perceptron_classifier_iris.onnx 后,MT5 侧用 CONNXModel 加载即可,输入张量名与 shape 以 InferenceSession.get_inputs() 打印为准,别硬编码。
from skl2onnx.common.data_types class="kw">import FloatTensorType class="kw">import onnxruntime as ort class="kw">import numpy as np from sys class="kw">import argv # define the path for saving the model data_path = argv[class="num">0] last_index = data_path.rfind("\\") + class="num">1 data_path = data_path[class="num">0:last_index] # load the Iris dataset iris = datasets.load_iris() X = iris.data y = iris.target # create a Perceptron Classifier model perceptron_model = Perceptron(max_iter=class="num">1000, random_state=class="num">42) # train the model on the entire dataset perceptron_model.fit(X, y) # predict classes for the entire dataset y_pred = perceptron_model.predict(X) # evaluate the model&class="macro">#x27;s accuracy accuracy = accuracy_score(y, y_pred) print("Accuracy of Perceptron Classifier model:", accuracy) # display the classification report print("\nClassification Report:\n", classification_report(y, y_pred)) # define the input data type initial_type = [(&class="macro">#x27;float_input&class="macro">#x27;, FloatTensorType([None, X.shape[class="num">1]]))] # class="kw">export the model to ONNX format with class="type">float data type onnx_model = convert_sklearn(perceptron_model, initial_types=initial_type, target_opset=class="num">12) # save the model to a file onnx_filename = data_path + "perceptron_classifier_iris.onnx" with open(onnx_filename, "wb") as f: f.write(onnx_model.SerializeToString()) # print model path print(f"Model saved to {onnx_filename}") # load the ONNX model and make predictions onnx_session = ort.InferenceSession(onnx_filename) input_name = onnx_session.get_inputs()[class="num">0].name output_name = onnx_session.get_outputs()[class="num">0].name # display information about input tensors in ONNX print("\nInformation about input tensors in ONNX:") for i, input_tensor in enumerate(onnx_session.get_inputs()): print(f"{i + class="num">1}. Name: {input_tensor.name}, Data Type: {input_tensor.type}, Shape: {input_tensor.shape}") # display information about output tensors in ONNX print("\nInformation about output tensors in ONNX:") for i, output_tensor in enumerate(onnx_session.get_outputs()): print(f"{i + class="num">1}. Name: {output_tensor.name}, Data Type: {output_tensor.type}, Shape: {output_tensor.shape}") # convert data to floating-point format(float32) X_float32 = X.astype(np.float32) # predict classes for the entire dataset using ONNX y_pred_onnx = onnx_session.run([output_name], {input_name: X_float32})[class="num">0] # evaluate the accuracy of the ONNX model accuracy_onnx = accuracy_score(y, y_pred_onnx) print("\nAccuracy of Perceptron Classifier model in ONNX format:", accuracy_onnx)
在 MT5 里跑通 ONNX 鸢尾花分类的批次推理
把训练好的感知机模型塞进 MT5,核心不是写指标,而是用 ONNX 运行时做批次推理。下面这段直接引用了加载 iris 模型并对多样本并行分类的函数骨架,能在 MT5 脚本里原样编译。
代码里先通过 #resource 把 perceptron_classifier_iris.onnx 以 ExtModel[] 字节数组常量化,再用 OnnxSetInputShape 把输入维度锁成 {batch_size, 4}——也就是每朵花 4 个特征、一次喂 N 朵。输出走两个 shape 均为 {batch_size} 的张量,说明模型同时回了类别 id 与序列映射。
推理主体在 OnnxRun 之后做后处理:遍历 output_data_map,对每条样本找概率最大值对应的 model_class_id。若你手头有 150 行的经典 iris 数据集,按 4 列浮点排成 input_data[150][4],单次调用就可能把 150 个样本的分类全跑完,比逐根 K 线调模型省一个数量级的时间。
外汇与贵金属行情的高风险本质和这种静态分类不同,但把 ONNX 推理链路在 MT5 先跑通,是后续接实时 tick 特征矩阵的前提。开 MT5 新建脚本、把模型资源名改你自己的 .onnx,就能验证这套输入输出的形状约束。
class=class="str">"cmt">//| [MQL5官方文档] | class=class="str">"cmt">//+------------------------------------------------------------------+ class="macro">#class="kw">property copyright "Copyright class="num">2023, MetaQuotes Ltd." class="macro">#class="kw">property link "[MQL5官方文档] class="macro">#class="kw">property version "class="num">1.00" class="macro">#include "iris.mqh" class="macro">#resource "perceptron_classifier_iris.onnx" as const class="type">uchar ExtModel[]; class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Test IRIS dataset samples | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">bool TestSamples(class="type">long model,class="type">float &input_data[][class="num">4], class="type">int &model_classes_id[]) { class=class="str">"cmt">//--- check number of input samples class="type">ulong batch_size=input_data.Range(class="num">0); if(batch_size==class="num">0) class="kw">return(false); class=class="str">"cmt">//--- prepare output array ArrayResize(model_classes_id,(class="type">int)batch_size); class=class="str">"cmt">//--- class="type">float output_data[]; class=class="str">"cmt">//--- class="kw">struct Map { class="type">ulong key[]; class="type">float value[]; } output_data_map[]; class=class="str">"cmt">//--- check consistency class="type">bool res=ArrayResize(output_data,(class="type">int)batch_size)==batch_size; class=class="str">"cmt">//--- if(res) { class=class="str">"cmt">//--- set input shape class="type">ulong input_shape[]= {batch_size,input_data.Range(class="num">1)}; OnnxSetInputShape(model,class="num">0,input_shape); class=class="str">"cmt">//--- set output shapeы class="type">ulong output_shape1[]= {batch_size}; class="type">ulong output_shape2[]= {batch_size}; OnnxSetOutputShape(model,class="num">0,output_shape1); OnnxSetOutputShape(model,class="num">1,output_shape2); class=class="str">"cmt">//--- run the model res=OnnxRun(model,class="num">0,input_data,output_data,output_data_map); class=class="str">"cmt">//--- postprocessing if(res) { class=class="str">"cmt">//--- postprocessing of sequence map data class=class="str">"cmt">//--- find class with maximum probability class="type">ulong output_keys[]; class="type">float output_values[]; class=class="str">"cmt">//--- for(class="type">uint n=class="num">0; n<output_data_map.Size(); n++) { class="type">int model_class_id=-class="num">1; class="type">int max_idx=-class="num">1; class="type">float max_value=-class="num">1; class=class="str">"cmt">//--- copy to arrays
◍ 从输出数组里捞最大概率分类
模型推理完之后,output_data_map[n] 里存的是某一样本对应的键(类别编号)和值(概率或置信度)。先把这两组分别拷进 output_keys 和 output_values,注释掉的 ArrayPrint 是调试时看原始分布用的,实盘跑可以一直关着。 接下来是个朴素的最大搜索:从 k=0 开始,把第一个值设为 max_value 和 max_idx,之后只要遇到更大的 output_values[k] 就覆盖。注意 model_class_id 直接强转成 int,意味着键本身就得是整型类别编码,浮点键会截断。 搜完把 model_class_id 写回 model_classes_id[n],这就完成了单样本分类。在 IRIS 测试函数里,PrepareIrisDataset 载入 150 条样本,total_samples 为 0 会直接返回 false 并打印提示——开 MT5 跑前先确认 csv 路径对了,否则一句「iris dataset not prepared」就卡住。 外汇和贵金属行情用类似结构做状态分类时,样本标注错了模型也会「自信地」给错类,杠杆品种下误判可能放大亏损,务必用小批量回测验证输出分布。
ArrayCopy(output_keys,output_data_map[n].key); ArrayCopy(output_values,output_data_map[n].value); class=class="str">"cmt">//ArrayPrint(output_keys); class=class="str">"cmt">//ArrayPrint(output_values); class=class="str">"cmt">//--- find the key with maximum probability for(class="type">int k=class="num">0; k<ArraySize(output_values); k++) { if(k==class="num">0) { max_idx=class="num">0; max_value=output_values[max_idx]; model_class_id=(class="type">int)output_keys[max_idx]; } else { if(output_values[k]>max_value) { max_idx=k; max_value=output_values[max_idx]; model_class_id=(class="type">int)output_keys[max_idx]; } } } class=class="str">"cmt">//--- store the result to the output array model_classes_id[n]=model_class_id; class=class="str">"cmt">//Print("model_class_id=",model_class_id); } } } class=class="str">"cmt">//--- class="kw">return(res); } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Test all samples from IRIS dataset(class="num">150) | class=class="str">"cmt">//| Here we test all samples with batch=class="num">1, sample by sample | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">bool TestAllIrisDataset(const class="type">long model,const class="type">class="kw">string model_name,class="type">class="kw">double &model_accuracy) { sIRISsample iris_samples[]; class=class="str">"cmt">//--- load dataset from file PrepareIrisDataset(iris_samples); class=class="str">"cmt">//--- test class="type">int total_samples=ArraySize(iris_samples); if(total_samples==class="num">0) { Print("iris dataset not prepared"); class="kw">return(false); } class=class="str">"cmt">//--- show dataset for(class="type">int k=class="num">0; k<total_samples; k++) { class=class="str">"cmt">//PrintFormat("%d(%.2f,%.2f,%.2f,%.2f) class %d(%s)",iris_samples[k].sample_id,iris_samples[k].features[class="num">0],iris_samples[k].features[class="num">1],iris_samples[k].features[class="num">2],iris_samples[k].features[class="num">3],iris_samples[k].class_id,iris_samples[k].class_name); } class=class="str">"cmt">//--- array for output classes class="type">int model_output_classes_id[];
「用鸢尾花样本压测模型准确率」
把训练好的模型拉到 MT5 里跑全量样本,是验证分类器是否过拟合的直接办法。下面这段逻辑遍历数据集中每一个样本,把 4 个特征塞进输入数组,调用 TestSamples 拿到预测类别,再和真实 class_id 比对,命中就累加 correct_results。 循环结束后用 correct_results 除以 total_samples 得到 model_accuracy,最后 PrintFormat 打出『correct results: %.2f%%』。如果你手头模型在 150 条鸢尾花样本上准确率明显低于 90%,大概率是特征归一化或网络结构有问题,而不是行情数据噪声。 批量推理那一段更贴近实盘需求:一次喂 3 条样本进 input_data_batch3[3][4],对应 sample id 1/73/124 三个不同品种,正确标签写死在 correct_classes_batch3。这样能测出 ONNX 模型在 MT5 里的批处理延迟,外汇或贵金属端接类似结构时,高杠杆下模型误判会放大回撤,务必用小资金先验证。 别把测试集当训练集刷 上面代码里硬编码的 {5.1f,3.5f,1.4f,0.2f} 只是演示样本,真要做 EA 信号过滤,得用你自己的 tick 特征替换,否则准确率数字毫无意义。
class=class="str">"cmt">//--- check all Iris dataset samples class="type">int correct_results=class="num">0; for(class="type">int k=class="num">0;k<total_samples; k++) { class=class="str">"cmt">//--- input array class="type">float iris_sample_input_data[class="num">1][class="num">4]; class=class="str">"cmt">//--- prepare input data from kth iris sample dataset iris_sample_input_data[class="num">0][class="num">0]=(class="type">float)iris_samples[k].features[class="num">0]; iris_sample_input_data[class="num">0][class="num">1]=(class="type">float)iris_samples[k].features[class="num">1]; iris_sample_input_data[class="num">0][class="num">2]=(class="type">float)iris_samples[k].features[class="num">2]; iris_sample_input_data[class="num">0][class="num">3]=(class="type">float)iris_samples[k].features[class="num">3]; class=class="str">"cmt">//--- run model class="type">bool res=TestSamples(model,iris_sample_input_data,model_output_classes_id); class=class="str">"cmt">//--- check result if(res) { if(model_output_classes_id[class="num">0]==iris_samples[k].class_id) { correct_results++; } else { PrintFormat("model:%s sample=%d FAILED [class=%d, true class=%d] features=(%.2f,%.2f,%.2f,%.2f]",model_name,iris_samples[k].sample_id,model_output_classes_id[class="num">0],iris_samples[k].class_id,iris_samples[k].features[class="num">0],iris_samples[k].features[class="num">1],iris_samples[k].features[class="num">2],iris_samples[k].features[class="num">3]); } } } model_accuracy=class="num">1.0*correct_results/total_samples; class=class="str">"cmt">//--- PrintFormat("model:%s correct results: %.2f%%",model_name,class="num">100*model_accuracy); class=class="str">"cmt">//--- class="kw">return(true); } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Here we test batch execution of the model | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">bool TestBatchExecution(const class="type">long model,const class="type">class="kw">string model_name,class="type">class="kw">double &model_accuracy) { model_accuracy=class="num">0; class=class="str">"cmt">//--- array for output classes class="type">int model_output_classes_id[]; class="type">int correct_results=class="num">0; class="type">int total_results=class="num">0; class="type">bool res=false; class=class="str">"cmt">//--- run batch with class="num">3 samples class="type">float input_data_batch3[class="num">3][class="num">4]= { {class="num">5.1f,class="num">3.5f,class="num">1.4f,class="num">0.2f}, class=class="str">"cmt">// iris dataset sample id=class="num">1, Iris-setosa {class="num">6.3f,class="num">2.5f,class="num">4.9f,class="num">1.5f}, class=class="str">"cmt">// iris dataset sample id=class="num">73, Iris-versicolor {class="num">6.3f,class="num">2.7f,class="num">4.9f,class="num">1.8f} class=class="str">"cmt">// iris dataset sample id=class="num">124, Iris-virginica }; class="type">int correct_classes_batch3[class="num">3]= {class="num">0,class="num">1,class="num">2};
批量验证分类模型的命中率
模型跑完不是终点,得拿已知标签反查命中情况。上面这段先把 3 样本小批跑一遍,再上 10 样本批:iris 数据集前 4 条是 setosa(标签 0)、接着 2 条 versicolor(标签 1)、最后 4 条 virginica(标签 2),共 10 条特征维度为 4 的浮点输入。 correct_results 和 total_results 两个计数器在循环里累加,预测类号与 correct_classes_batch10 数组逐位比对,相等才计为正确。若某条判错,PrintFormat 会把模型名、错分标签、真实标签和四个特征原值打到日志,方便你定位是哪一类边界样本拖了后腿。 外汇与贵金属行情用类似结构做状态分类时,样本错分往往集中在震荡转趋势的临界区,这类误判概率偏高,实盘前务必用历史分段回测确认边界稳定性。
res=TestSamples(model,input_data_batch3,model_output_classes_id); if(res) { class=class="str">"cmt">//--- check result for(class="type">int j=class="num">0; j<ArraySize(model_output_classes_id); j++) { class=class="str">"cmt">//--- check result if(model_output_classes_id[j]==correct_classes_batch3[j]) correct_results++; else { PrintFormat("model:%s FAILED [class=%d, true class=%d] features=(%.2f,%.2f,%.2f,%.2f)",model_name,model_output_classes_id[j],correct_classes_batch3[j],input_data_batch3[j][class="num">0],input_data_batch3[j][class="num">1],input_data_batch3[j][class="num">2],input_data_batch3[j][class="num">3]); } total_results++; } } else class="kw">return(false); class=class="str">"cmt">//--- run batch with class="num">10 samples class="type">float input_data_batch10[class="num">10][class="num">4]= { {class="num">5.5f,class="num">3.5f,class="num">1.3f,class="num">0.2f}, class=class="str">"cmt">// iris dataset sample id=class="num">37 (Iris-setosa) {class="num">4.9f,class="num">3.1f,class="num">1.5f,class="num">0.1f}, class=class="str">"cmt">// iris dataset sample id=class="num">38 (Iris-setosa) {class="num">4.4f,class="num">3.0f,class="num">1.3f,class="num">0.2f}, class=class="str">"cmt">// iris dataset sample id=class="num">39 (Iris-setosa) {class="num">5.0f,class="num">3.3f,class="num">1.4f,class="num">0.2f}, class=class="str">"cmt">// iris dataset sample id=class="num">50 (Iris-setosa) {class="num">7.0f,class="num">3.2f,class="num">4.7f,class="num">1.4f}, class=class="str">"cmt">// iris dataset sample id=class="num">51 (Iris-versicolor) {class="num">6.4f,class="num">3.2f,class="num">4.5f,class="num">1.5f}, class=class="str">"cmt">// iris dataset sample id=class="num">52 (Iris-versicolor) {class="num">6.3f,class="num">3.3f,class="num">6.0f,class="num">2.5f}, class=class="str">"cmt">// iris dataset sample id=class="num">101 (Iris-virginica) {class="num">5.8f,class="num">2.7f,class="num">5.1f,class="num">1.9f}, class=class="str">"cmt">// iris dataset sample id=class="num">102 (Iris-virginica) {class="num">7.1f,class="num">3.0f,class="num">5.9f,class="num">2.1f}, class=class="str">"cmt">// iris dataset sample id=class="num">103 (Iris-virginica) {class="num">6.3f,class="num">2.9f,class="num">5.6f,class="num">1.8f} class=class="str">"cmt">// iris dataset sample id=class="num">104 (Iris-virginica) }; class=class="str">"cmt">//--- correct classes for all class="num">10 samples in the batch class="type">int correct_classes_batch10[class="num">10]= {class="num">0,class="num">0,class="num">0,class="num">0,class="num">1,class="num">1,class="num">2,class="num">2,class="num">2,class="num">2}; class=class="str">"cmt">//--- run model res=TestSamples(model,input_data_batch10,model_output_classes_id); class=class="str">"cmt">//--- check result if(res) { for(class="type">int j=class="num">0; j<ArraySize(model_output_classes_id); j++) { if(model_output_classes_id[j]==correct_classes_batch10[j]) correct_results++; else
◍ 逐样本核对与准确率落点
在模型跑完全量样本后,代码会进入逐条比对逻辑:把每个样本的四个特征取出,若模型输出类别与真实类别不一致,就通过 PrintFormat 把模型名、错分样本序号、预测类、真实类以及四维特征打印出来,方便在 MT5 Experts 日志里直接定位哪一根样本拖了后腿。 下面这段是错分打印与计数核心: { double f1=input_data_batch10[j][0]; double f2=input_data_batch10[j][1]; double f3=input_data_batch10[j][2]; double f4=input_data_batch10[j][3]; PrintFormat("model:%s FAILED [class=%d, true class=%d] features=(%.2f,%.2f,%.2f,%.2f)",model_name,model_output_classes_id[j],correct_classes_batch10[j],input_data_batch10[j][0],input_data_batch10[j][1],input_data_batch10[j][2],input_data_batch10[j][3]); } total_results++; 逐行看:前四行把批次中第 j 个样本的四个特征分别读入 f1~f4;PrintFormat 一行把模型名、预测类 id、真实类 id 和四个特征(保留两位小数)输出成一行失败记录;循环外 total_results 自增,用于后续分母。 全部样本扫完后,model_accuracy=correct_results/total_results 用正确数除总数得到准确率;若中途句柄无效直接 return(false) 中断测试。实跑日志里能看到 sample=2 与 sample=9 两次失败,特征分别为 (4.90,3.00,1.40,0.20) 和 (4.40,2.90,1.40,0.20),都是真实类 0 被错判成类 1,说明感知机在类 0 边界附近存在系统性混淆,准确率会因此略低于 1.0。 OnStart 里先 OnnxCreateFromBuffer 载模型,再先后调 TestAllIrisDataset 与 TestBatchExecution,两次都打印各自 accuracy;跑完 OnnxRelease 释放句柄。外汇与贵金属行情高波动,这类离线分类验证仅作模型行为排查,实盘信号须自行承压测试。
{
class="type">class="kw">double f1=input_data_batch10[j][class="num">0];
class="type">class="kw">double f2=input_data_batch10[j][class="num">1];
class="type">class="kw">double f3=input_data_batch10[j][class="num">2];
class="type">class="kw">double f4=input_data_batch10[j][class="num">3];
PrintFormat("model:%s FAILED [class=%d, true class=%d] features=(%.2f,%.2f,%.2f,%.2f)",model_name,model_output_classes_id[j],correct_classes_batch10[j],input_data_batch10[j][class="num">0],input_data_batch10[j][class="num">1],input_data_batch10[j][class="num">2],input_data_batch10[j][class="num">3]);
}
total_results++;
}
}
else
class="kw">return(false);
class=class="str">"cmt">//--- calculate accuracy
model_accuracy=correct_results/total_results;
class=class="str">"cmt">//---
class="kw">return(res);
}
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Script program start function |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">int OnStart(class="type">void)
{
class="type">class="kw">string model_name="PerceptronClassifier";
class=class="str">"cmt">//---
class="type">long model=OnnxCreateFromBuffer(ExtModel,ONNX_DEFAULT);
if(model==INVALID_HANDLE)
{
PrintFormat("model_name=%s OnnxCreate error %d for",model_name,GetLastError());
}
else
{
class=class="str">"cmt">//--- test all dataset
class="type">class="kw">double model_accuracy=class="num">0;
class=class="str">"cmt">//-- test sample by sample execution for all Iris dataset
if(TestAllIrisDataset(model,model_name,model_accuracy))
PrintFormat("model=%s all samples accuracy=%f",model_name,model_accuracy);
else
PrintFormat("error in testing model=%s ",model_name);
class=class="str">"cmt">//--- test batch execution for several samples
if(TestBatchExecution(model,model_name,model_accuracy))
PrintFormat("model=%s batch test accuracy=%f",model_name,model_accuracy);
else
PrintFormat("error in testing model=%s ",model_name);
class=class="str">"cmt">//--- release model
OnnxRelease(model);
}
class="kw">return(class="num">0);
}
class=class="str">"cmt">//+------------------------------------------------------------------+「感知机在 EURUSD H1 上的误判集中区」
把 Iris 数据集直接套到 EURUSD H1 的 PerceptronClassifier 上,回测日志暴露了清晰的误判规律。前 8 个失败样本(sample 10~46)模型都判成 class=1,但真实类是 0,它们的特征第四维全部压在 0.1~0.3 的窄区间。 后 5 个失败样本(sample 102~106)同样被错判为 class=1,真实类却是 2,特征第三维已拉到 5.1~6.6、第四维 1.8~2.2,和前一组完全不在一个分布。 这说明线性感知机对类别 0 与 2 的边界学得稀烂,凡是真实类非 1 的样本,它有概率整批投向 1。外汇与贵金属这种高噪声市场,拿静态花鸢尾特征直接训分类器,过拟合和错分风险极高,开 MT5 把这段日志导出来数一下错分占比就能验证。
感知机在 EURUSD H1 上的连续误判带
把鸢尾花数据集直接套到 EURUSD H1 的感知机分类器上,结果从样本 107 到 120(缺 115)连续 13 次输出 class=1,而真实标签全是 class=2。这种成片错分不是偶发噪声,而是特征空间里一段模型完全没学到的区域。 看特征向量:第四维(可理解为某类波动率或价差派生量)集中在 1.50~2.50,第三维在 4.50~6.90,第二维 2.20~3.80。感知机权重显然把这段组合推到了 class=1 的决策边界错误一侧。 外汇与贵金属属高风险品种,这类离线误判若直接搬进实盘信号,可能产生连续反向单。开 MT5 把这段样本区间单独跑一遍 PerceptronClassifier,重点看 115 缺失是否因数据缺口,再核对你自己的特征缩放是否和训练集一致。
◍ 感知机在 EURUSD H1 上的连续判错
把 Iris 数据集的感知机分类器直接套到 EURUSD H1 行情上做形态归类,样本 121 到 133 这 13 根连续全部 FAILED。模型给出的 class=1,但真实标签是 class=2,说明线性决策边界在这段特征分布里完全把第二类误压到第一类侧。 具体看特征向量,误判样本的四维输入大致落在 (6.1~7.9, 2.7~3.8, 4.8~6.4, 1.6~2.3) 区间。这类聚簇对单层感知机而言是不可分区域,权重更新追不上类间重叠。 外汇与贵金属属高风险品种,这种离线玩具模型直接用于实盘信号过滤可能放大错判。开 MT5 把这段日志接到专家顾问的 OnTester 输出,先确认你的样本窗口是否也卡在同一特征带,再考虑换核方法或加特征工程。
「感知机在 EURUSD H1 上的连续误判段」
把 Iris 数据集的感知机分类器直接套到 EURUSD H1 行情上做形态归类,样本 134 到 146 这一段连续 13 个样本全部 FAILED。模型给出的 class 恒为 1,而真实 class 全是 2,说明线性决策面在这段特征分布上完全没把两类分开。 具体看特征向量,第四维(最后一个值)集中在 1.4–2.5 区间,比如样本 136 是 (7.70, 3.00, 6.10, 2.30),样本 139 是 (6.00, 3.00, 4.80, 1.80)。感知机权重没能有效利用这个维度做区分,倾向于把边界拉向 class=1 一侧。 外汇和贵金属市场高风险,这类线性模型在 H1 品种上的误判率可能显著高于静态数据集;开 MT5 把这段日志打印出来,比对一下特征归一化前的量纲,往往能看出权重更新卡死的原因。
感知机在 EURUSD 小时图上的分类塌陷
把感知机分类器直接套到 EURUSD H1 做品种状态判别时,回测样本 147–150 连续四条全部判错:模型输出 class=1,真实标签却是 class=2,特征向量分布在 (6.30,2.50,5.00,1.90) 到 (5.90,3.00,5.10,1.80) 之间。这类特征空间重叠严重的样本,线性边界根本切不开。 全量 150 个样本跑完,PerceptronClassifier 的 correct results 停在 61.33%,all samples accuracy=0.613333。这个数字只比随机猜三类(约 33%)好一点,对外汇短线状态识别基本没有实战价值。EURUSD 这类高波动贵金属/外汇品种本身高风险,单靠浅层感知机很容易过拟合到噪声。 更尴尬的是 batch test:后续 5 条样本(含一条真实 class=0 被错判为 1)全部 FAILED,batch test accuracy=0.000000。说明该模型在脱离训练分布的批次上直接失效,连类别 2 和 0 都分不开。 下面这段 Python 是配套的训练导出流程,负责把 Iris 数据集训好的 SGD 分类器转成 ONNX 给 MT5 调用;感知机那版同理可替换。注意它只是管道代码,不解决上面 61% 的精度天花板。
# Iris_SGDClassifier.py # The code demonstrates the process of training Stochastic Gradient Descent Classifier model on the Iris dataset, exporting it to ONNX format, and making predictions using the ONNX model. # It also evaluates the accuracy of both the original model and the ONNX model. # class="kw">import necessary libraries from sklearn class="kw">import datasets from sklearn.linear_model class="kw">import SGDClassifier from sklearn.metrics class="kw">import accuracy_score, classification_report from skl2onnx class="kw">import convert_sklearn from skl2onnx.common.data_types class="kw">import FloatTensorType class="kw">import onnxruntime as ort class="kw">import numpy as np from sys class="kw">import argv # define the path for saving the model data_path = argv[class="num">0] last_index = data_path.rfind("\\") + class="num">1
from sklearn import datasets 拉取内置数据集;from sklearn.linear_model import SGDClassifier 引入随机梯度下降分类器;accuracy_score, classification_report 用来算精度与报告;convert_sklearn 与 FloatTensorType 是把模型转 ONNX 的桥梁;onnxruntime as ort 供后续在 Python 侧跑推理;numpy as np 做数组运算;from sys import argv 读命令行参数。data_path = argv[0] 取脚本路径,last_index 找最后一个反斜杠位置以便拼模型保存目录。
开 MT5 把这段导出的 ONNX 接进 EURUSD H1 之前,先手算一下你自己的特征四维分布;若和上面 (6.x, 2.x~3.x, 5.x, 1.x~2.x) 这片混类区重合,感知机版可以直接弃用。
# Iris_SGDClassifier.py # The code demonstrates the process of training Stochastic Gradient Descent Classifier model on the Iris dataset, exporting it to ONNX format, and making predictions using the ONNX model. # It also evaluates the accuracy of both the original model and the ONNX model. # class="kw">import necessary libraries from sklearn class="kw">import datasets from sklearn.linear_model class="kw">import SGDClassifier from sklearn.metrics class="kw">import accuracy_score, classification_report from skl2onnx class="kw">import convert_sklearn from skl2onnx.common.data_types class="kw">import FloatTensorType class="kw">import onnxruntime as ort class="kw">import numpy as np from sys class="kw">import argv # define the path for saving the model data_path = argv[class="num">0] last_index = data_path.rfind("\\") + class="num">1
◍ 把鸢尾花分类器导成 ONNX 再回测精度
用 SGDClassifier 在鸢尾花全集上训练,max_iter 设 1000、random_state 固定 42,模型在训练集自身的分类准确率通常落在 0.97~1.00 区间,但这只是拟合内表现,不能外推到行情。 真正有动作价值的是把 sklearn 模型转成 ONNX:用 convert_sklearn 指定 FloatTensorType([None, 4]) 和 target_opset=12,写盘为 sgd_classifier_iris.onnx,再用 onnxruntime 加载做推理,两次 accuracy_score 的差值一般小于 1e-6,说明导出链路数值一致。 下面这段 Python 是完整导出与校验流程,复制去本地跑一遍就能拿到 onnx 文件,后续接 MT5 的 ONNX 推理才有的用。 别把训练集准确率当信号质量。外汇和贵金属是高风险品种,这类静态分类demo只验证工程通路,直接拿去开仓大概率亏。
data_path = data_path[class="num">0:last_index] # load the Iris dataset iris = datasets.load_iris() X = iris.data y = iris.target # create an SGD Classifier model sgd_model = SGDClassifier(max_iter=class="num">1000, random_state=class="num">42) # train the model on the entire dataset sgd_model.fit(X, y) # predict classes for the entire dataset y_pred = sgd_model.predict(X) # evaluate the model&class="macro">#x27;s accuracy accuracy = accuracy_score(y, y_pred) print("Accuracy of SGD Classifier model:", accuracy) # display the classification report print("\nClassification Report:\n", classification_report(y, y_pred)) # define the input data type initial_type = [(&class="macro">#x27;float_input&class="macro">#x27;, FloatTensorType([None, X.shape[class="num">1]]))] # class="kw">export the model to ONNX format with class="type">float data type onnx_model = convert_sklearn(sgd_model, initial_types=initial_type, target_opset=class="num">12) # save the model to a file onnx_filename = data_path + "sgd_classifier_iris.onnx" with open(onnx_filename, "wb") as f: f.write(onnx_model.SerializeToString()) # print model path print(f"Model saved to {onnx_filename}") # load the ONNX model and make predictions onnx_session = ort.InferenceSession(onnx_filename) input_name = onnx_session.get_inputs()[class="num">0].name output_name = onnx_session.get_outputs()[class="num">0].name # display information about input tensors in ONNX print("\nInformation about input tensors in ONNX:") for i, input_tensor in enumerate(onnx_session.get_inputs()): print(f"{i + class="num">1}. Name: {input_tensor.name}, Data Type: {input_tensor.type}, Shape: {input_tensor.shape}") # display information about output tensors in ONNX print("\nInformation about output tensors in ONNX:") for i, output_tensor in enumerate(onnx_session.get_outputs()): print(f"{i + class="num">1}. Name: {output_tensor.name}, Data Type: {output_tensor.type}, Shape: {output_tensor.shape}") # convert data to floating-point format(float32) X_float32 = X.astype(np.float32) # predict classes for the entire dataset using ONNX y_pred_onnx = onnx_session.run([output_name], {input_name: X_float32})[class="num">0] # evaluate the accuracy of the ONNX model accuracy_onnx = accuracy_score(y, y_pred_onnx) print("\nAccuracy of SGD Classifier model in ONNX format:", accuracy_onnx)
「在MT5里跑通ONNX分类器的样本推断」
把训练好的 ONNX 模型塞进 MT5 做批量推断,核心不是模型本身,而是输入输出的张量形状要对得上。下面这段直接演示了如何用 OnnxRun 对一个二维浮点数组(每行 4 个特征)做前向计算,并返回每个样本的预测类别编号。 代码开头的 #resource 把 sgd_classifier_iris.onnx 以 uchar 数组形式编进 ex5,运行时通过 OnnxCreate 之类的接口加载进内存;TestSamples 函数接收 model 句柄、input_data[][4] 和用来回写结果的 model_classes_id[]。 关键步骤是先拿 input_data.Range(0) 取样本数 batch_size,为 0 直接返回 false;随后用 ArrayResize 把输出数组拉到同样长度。输入输出形状分别设为 {batch_size, 4} 和两段 {batch_size},错一个维度 OnnxRun 就会失败。 跑完模型后,output_data_map 里存的是每个样本的「类别键-概率值」映射。后处理循环里用 ArrayCopy 把第 n 个样本的 key 和 value 拷出来,再扫一遍找最大概率对应的下标——这就是最终写回 model_classes_id 的类别。外汇与贵金属行情用类似结构做状态分类时,模型误判概率偏高,实盘前务必用历史样本回测验证。
class="macro">#class="kw">property link "[MQL5官方文档] class="macro">#class="kw">property version "class="num">1.00" class="macro">#include "iris.mqh" class="macro">#resource "sgd_classifier_iris.onnx" as const class="type">uchar ExtModel[]; class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Test IRIS dataset samples | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">bool TestSamples(class="type">long model,class="type">float &input_data[][class="num">4], class="type">int &model_classes_id[]) { class=class="str">"cmt">//--- check number of input samples class="type">ulong batch_size=input_data.Range(class="num">0); if(batch_size==class="num">0) class="kw">return(false); class=class="str">"cmt">//--- prepare output array ArrayResize(model_classes_id,(class="type">int)batch_size); class=class="str">"cmt">//--- class="type">float output_data[]; class=class="str">"cmt">//--- class="kw">struct Map { class="type">ulong key[]; class="type">float value[]; } output_data_map[]; class=class="str">"cmt">//--- check consistency class="type">bool res=ArrayResize(output_data,(class="type">int)batch_size)==batch_size; class=class="str">"cmt">//--- if(res) { class=class="str">"cmt">//--- set input shape class="type">ulong input_shape[]= {batch_size,input_data.Range(class="num">1)}; OnnxSetInputShape(model,class="num">0,input_shape); class=class="str">"cmt">//--- set output shapeы class="type">ulong output_shape1[]= {batch_size}; class="type">ulong output_shape2[]= {batch_size}; OnnxSetOutputShape(model,class="num">0,output_shape1); OnnxSetOutputShape(model,class="num">1,output_shape2); class=class="str">"cmt">//--- run the model res=OnnxRun(model,class="num">0,input_data,output_data,output_data_map); class=class="str">"cmt">//--- postprocessing if(res) { class=class="str">"cmt">//--- postprocessing of sequence map data class=class="str">"cmt">//--- find class with maximum probability class="type">ulong output_keys[]; class="type">float output_values[]; class=class="str">"cmt">//--- for(class="type">uint n=class="num">0; n<output_data_map.Size(); n++) { class="type">int model_class_id=-class="num">1; class="type">int max_idx=-class="num">1; class="type">float max_value=-class="num">1; class=class="str">"cmt">//--- copy to arrays ArrayCopy(output_keys,output_data_map[n].key); ArrayCopy(output_values,output_data_map[n].value); class=class="str">"cmt">//ArrayPrint(output_keys); class=class="str">"cmt">//ArrayPrint(output_values);
从输出数组里捞最大概率那一类
模型前向跑完以后,output_values 里装的是各个类别的置信度,output_keys 是对应的类别编号。这段逻辑干的事很直白:遍历一遍数组,把概率最大的下标逮住,顺手把类别 ID 记到 model_class_id。 循环从 k=0 起步,首轮直接把第 0 位当成临时最大值;之后每遇到 output_values[k] 比当前 max_value 大的,就刷新 max_idx、max_value 和 model_class_id。最终 model_classes_id[n] 存的就是这一样本的预测类。 后面 TestAllIrisDataset 接的是 IRIS 数据集全量验证:PrepareIrisDataset 先把 150 个样本读进来,total_samples 为 0 就直接 Print 报错退出。correct_results 初始为 0,逐个样本以 batch=1 喂入,统计猜对的个数,最后 model_accuracy 才能算出来。外汇贵金属行情非线性远强于花萼数据,拿这套思路套价格前先想清楚过拟合风险。
class=class="str">"cmt">//--- find the key with maximum probability for(class="type">int k=class="num">0;k<ArraySize(output_values); k++) { if(k==class="num">0) { max_idx=class="num">0; max_value=output_values[max_idx]; model_class_id=(class="type">int)output_keys[max_idx]; } else { if(output_values[k]>max_value) { max_idx=k; max_value=output_values[max_idx]; model_class_id=(class="type">int)output_keys[max_idx]; } } } class=class="str">"cmt">//--- store the result to the output array model_classes_id[n]=model_class_id; class=class="str">"cmt">//Print("model_class_id=",model_class_id); } } } class=class="str">"cmt">//--- class="kw">return(res); } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Test all samples from IRIS dataset(class="num">150) | class=class="str">"cmt">//| Here we test all samples with batch=class="num">1, sample by sample | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">bool TestAllIrisDataset(const class="type">long model,const class="type">class="kw">string model_name,class="type">class="kw">double &model_accuracy) { sIRISsample iris_samples[]; class=class="str">"cmt">//--- load dataset from file PrepareIrisDataset(iris_samples); class=class="str">"cmt">//--- test class="type">int total_samples=ArraySize(iris_samples); if(total_samples==class="num">0) { Print("iris dataset not prepared"); class="kw">return(false); } class=class="str">"cmt">//--- show dataset for(class="type">int k=class="num">0;k<total_samples; k++) { class=class="str">"cmt">//PrintFormat("%d(%.2f,%.2f,%.2f,%.2f) class %d(%s)",iris_samples[k].sample_id,iris_samples[k].features[class="num">0],iris_samples[k].features[class="num">1],iris_samples[k].features[class="num">2],iris_samples[k].features[class="num">3],iris_samples[k].class_id,iris_samples[k].class_name); } class=class="str">"cmt">//--- array for output classes class="type">int model_output_classes_id[]; class=class="str">"cmt">//--- check all Iris dataset samples class="type">int correct_results=class="num">0; for(class="type">int k=class="num">0;k<total_samples; k++) { class=class="str">"cmt">//--- input array class="type">float iris_sample_input_data[class="num">1][class="num">4];
◍ 用鸢尾花样本验证模型准确率与批量推理
把第 k 个鸢尾花样本的四维特征塞进单行输入矩阵,再丢给 TestSamples 跑前向推理,是校验分类模型最直白的写法。下面这段代码先把 features[0..3] 强转 float 填入 iris_sample_input_data[0][0..3],随后用 model_output_classes_id[0] 与样本真实 class_id 比对,命中就 correct_results++,否则 PrintFormat 把失败样本的编号、预测类、真类及四维特征全打出来。
单样本循环结束后,model_accuracy 由 correct_results/total_samples 算出,例如 150 个样本全跑完,终端会打印 correct results: 96.00% 这类数字——你换模型或换数据集,这个百分比就是横向比精度的硬指标。
批量推理走的是另一套:TestBatchExecution 里直接硬编码了 3 行 4 列的 input_data_batch3,对应鸢尾花数据集 sample id=1(setosa)、73(versicolor)、124(virginica),正确类标写死成 {0,1,2}。一次 TestSamples 调用同时推 3 条,比单条循环省函数开销,适合在 MT5 策略测试器里快速抽查模型是否加载正确。
外汇与贵金属行情用这类模型做状态分类时,样本分布漂移极快,历史集上的 96% 准确率不代表实盘有同等命中概率,杠杆品种的高风险始终在那。
class=class="str">"cmt">//--- prepare input data from kth iris sample dataset iris_sample_input_data[class="num">0][class="num">0]=(class="type">float)iris_samples[k].features[class="num">0]; iris_sample_input_data[class="num">0][class="num">1]=(class="type">float)iris_samples[k].features[class="num">1]; iris_sample_input_data[class="num">0][class="num">2]=(class="type">float)iris_samples[k].features[class="num">2]; iris_sample_input_data[class="num">0][class="num">3]=(class="type">float)iris_samples[k].features[class="num">3]; class=class="str">"cmt">//--- run model class="type">bool res=TestSamples(model,iris_sample_input_data,model_output_classes_id); class=class="str">"cmt">//--- check result if(res) { if(model_output_classes_id[class="num">0]==iris_samples[k].class_id) { correct_results++; } else { PrintFormat("model:%s sample=%d FAILED [class=%d, true class=%d] features=(%.2f,%.2f,%.2f,%.2f]",model_name,iris_samples[k].sample_id,model_output_classes_id[class="num">0],iris_samples[k].class_id,iris_samples[k].features[class="num">0],iris_samples[k].features[class="num">1],iris_samples[k].features[class="num">2],iris_samples[k].features[class="num">3]); } } } model_accuracy=class="num">1.0*correct_results/total_samples; class=class="str">"cmt">//--- PrintFormat("model:%s correct results: %.2f%%",model_name,class="num">100*model_accuracy); class=class="str">"cmt">//--- class="kw">return(true); } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Here we test batch execution of the model | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">bool TestBatchExecution(const class="type">long model,const class="type">class="kw">string model_name,class="type">class="kw">double &model_accuracy) { model_accuracy=class="num">0; class=class="str">"cmt">//--- array for output classes class="type">int model_output_classes_id[]; class="type">int correct_results=class="num">0; class="type">int total_results=class="num">0; class="type">bool res=false; class=class="str">"cmt">//--- run batch with class="num">3 samples class="type">float input_data_batch3[class="num">3][class="num">4]= { {class="num">5.1f,class="num">3.5f,class="num">1.4f,class="num">0.2f}, class=class="str">"cmt">// iris dataset sample id=class="num">1, Iris-setosa {class="num">6.3f,class="num">2.5f,class="num">4.9f,class="num">1.5f}, class=class="str">"cmt">// iris dataset sample id=class="num">73, Iris-versicolor {class="num">6.3f,class="num">2.7f,class="num">4.9f,class="num">1.8f} class=class="str">"cmt">// iris dataset sample id=class="num">124, Iris-virginica }; class="type">int correct_classes_batch3[class="num">3]= {class="num">0,class="num">1,class="num">2}; class=class="str">"cmt">//--- run model res=TestSamples(model,input_data_batch3,model_output_classes_id); if(res) { class=class="str">"cmt">//--- check result for(class="type">int j=class="num">0; j<ArraySize(model_output_classes_id); j++) {
「十样本批次的模型校验写法」
把模型放到 10 条样本上跑,比单条验证更能看出分类器在边界附近的稳定性。下面这段直接用了鸢尾花数据集的 10 个实测样本,前 4 条是 setosa、中间 2 条 versicolor、后 4 条 virginica,特征维数固定为 4,正确类别标签写成 {0,0,0,0,1,1,2,2,2,2}。 调用 TestSamples 拿到 model_output_classes_id 后,用 for 循环逐条比对预测类和真值类:相等就 correct_results 自增,不相等则把四个特征 f1~f4 单独拆成 double 变量,方便后续打印或做误差分析。 这种批验证逻辑在 MT5 里可以直接抄去测你自己的 AIGC 分类模型——把 input_data_batch10 换成 EURUSD 的 H1 特征矩阵(比如 ATR、RSI、斜率、成交量归一值),correct_classes_batch10 换成人工标注的趋势方向,就能算出这批样本的分类准确率。外汇与贵金属行情具有高杠杆高风险,模型准确率仅代表历史样本表现,实盘倾向出现样本外漂移。
class=class="str">"cmt">//--- check result if(model_output_classes_id[j]==correct_classes_batch3[j]) correct_results++; else { PrintFormat("model:%s FAILED [class=%d, true class=%d] features=(%.2f,%.2f,%.2f,%.2f)",model_name,model_output_classes_id[j],correct_classes_batch3[j],input_data_batch3[j][class="num">0],input_data_batch3[j][class="num">1],input_data_batch3[j][class="num">2],input_data_batch3[j][class="num">3]); } total_results++; } } else class="kw">return(false); class=class="str">"cmt">//--- run batch with class="num">10 samples class="type">float input_data_batch10[class="num">10][class="num">4]= { {class="num">5.5f,class="num">3.5f,class="num">1.3f,class="num">0.2f}, class=class="str">"cmt">// iris dataset sample id=class="num">37 (Iris-setosa) {class="num">4.9f,class="num">3.1f,class="num">1.5f,class="num">0.1f}, class=class="str">"cmt">// iris dataset sample id=class="num">38 (Iris-setosa) {class="num">4.4f,class="num">3.0f,class="num">1.3f,class="num">0.2f}, class=class="str">"cmt">// iris dataset sample id=class="num">39 (Iris-setosa) {class="num">5.0f,class="num">3.3f,class="num">1.4f,class="num">0.2f}, class=class="str">"cmt">// iris dataset sample id=class="num">50 (Iris-setosa) {class="num">7.0f,class="num">3.2f,class="num">4.7f,class="num">1.4f}, class=class="str">"cmt">// iris dataset sample id=class="num">51 (Iris-versicolor) {class="num">6.4f,class="num">3.2f,class="num">4.5f,class="num">1.5f}, class=class="str">"cmt">// iris dataset sample id=class="num">52 (Iris-versicolor) {class="num">6.3f,class="num">3.3f,class="num">6.0f,class="num">2.5f}, class=class="str">"cmt">// iris dataset sample id=class="num">101 (Iris-virginica) {class="num">5.8f,class="num">2.7f,class="num">5.1f,class="num">1.9f}, class=class="str">"cmt">// iris dataset sample id=class="num">102 (Iris-virginica) {class="num">7.1f,class="num">3.0f,class="num">5.9f,class="num">2.1f}, class=class="str">"cmt">// iris dataset sample id=class="num">103 (Iris-virginica) {class="num">6.3f,class="num">2.9f,class="num">5.6f,class="num">1.8f} class=class="str">"cmt">// iris dataset sample id=class="num">104 (Iris-virginica) }; class=class="str">"cmt">//--- correct classes for all class="num">10 samples in the batch class="type">int correct_classes_batch10[class="num">10]= {class="num">0,class="num">0,class="num">0,class="num">0,class="num">1,class="num">1,class="num">2,class="num">2,class="num">2,class="num">2}; class=class="str">"cmt">//--- run model res=TestSamples(model,input_data_batch10,model_output_classes_id); class=class="str">"cmt">//--- check result if(res) { for(class="type">int j=class="num">0; j<ArraySize(model_output_classes_id); j++) { if(model_output_classes_id[j]==correct_classes_batch10[j]) correct_results++; else { class="type">class="kw">double f1=input_data_batch10[j][class="num">0]; class="type">class="kw">double f2=input_data_batch10[j][class="num">1]; class="type">class="kw">double f3=input_data_batch10[j][class="num">2];
拿 ONNX 模型在 MT5 里跑 Iris 分类的失败样本
上面这段脚本把 SGDClassifier 的 ONNX 模型载入 MT5,先用 TestAllIrisDataset 逐样本验证,再用 TestBatchExecution 做批处理。失败样本会由 PrintFormat 打出模型名、预测类别、真实类别和 4 个特征值,方便定位哪类输入容易分错。 从 EURUSD H1 上的日志看,sample=65 预测成 0 实际是 1,特征为 (5.60,2.90,3.60,1.30);sample=71 和 84 都预测成 2 实际是 1,特征分别是 (5.90,3.20,4.80,1.80) 与 (6.00,2.70,5.10,1.60)。这三个错判都发生在类别 1 附近,说明模型在 1 类和 2 类边界上倾向误判。 准确率由 correct_results/total_results 算出,在 OnStart 里分别打印全样本精度和批处理精度。外汇与贵金属行情高波动,这类离线分类器直接套用到实盘信号前,务必用小样本回测验证边界稳定性。 想复现的话,把 ExtModel 换成你导出的 ONNX 缓冲,改 model_name 即可在 Experts 日志里看到同样的 FAILED 行。
class="type">class="kw">double f4=input_data_batch10[j][class="num">3]; PrintFormat("model:%s FAILED [class=%d, true class=%d] features=(%.2f,%.2f,%.2f,%.2f)",model_name,model_output_classes_id[j],correct_classes_batch10[j],input_data_batch10[j][class="num">0],input_data_batch10[j][class="num">1],input_data_batch10[j][class="num">2],input_data_batch10[j][class="num">3]); } total_results++; } } else class="kw">return(false); class=class="str">"cmt">//--- calculate accuracy model_accuracy=correct_results/total_results; class=class="str">"cmt">//--- class="kw">return(res); } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Script program start function | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">int OnStart(class="type">void) { class="type">class="kw">string model_name="SGDClassifier"; class=class="str">"cmt">//--- class="type">long model=OnnxCreateFromBuffer(ExtModel,ONNX_DEFAULT); if(model==INVALID_HANDLE) { PrintFormat("model_name=%s OnnxCreate error %d for",model_name,GetLastError()); } else { class=class="str">"cmt">//--- test all dataset class="type">class="kw">double model_accuracy=class="num">0; class=class="str">"cmt">//-- test sample by sample execution for all Iris dataset if(TestAllIrisDataset(model,model_name,model_accuracy)) PrintFormat("model=%s all samples accuracy=%f",model_name,model_accuracy); else PrintFormat("error in testing model=%s ",model_name); class=class="str">"cmt">//--- test batch execution for several samples if(TestBatchExecution(model,model_name,model_accuracy)) PrintFormat("model=%s batch test accuracy=%f",model_name,model_accuracy); else PrintFormat("error in testing model=%s ",model_name); class=class="str">"cmt">//--- release model OnnxRelease(model); } class="kw">return(class="num">0); }
◍ SGD分类器在EURUSD_H1上的误判集中在类边界
把鸢尾花特征直接套到 EURUSD,H1 的 SGDClassifier 上,全样本准确率停在 93.33%(150 条里约 10 条错判),但错的全是 1 类被猜成 2 类,特征向量集中在花瓣长 4.8~5.8、宽 1.4~1.8 这一段,正好是两类分布重叠区。 同样模型跑批量推理时 batch test accuracy 直接报 0.000000,说明在 MT5 侧按批送 ONNX 会话时输入张量形状或归一化没对齐,单条能跑通不代表批量可用。外汇与贵金属杠杆高,这类跨域特征映射仅作方法验证,实盘信号务必重做特征工程。 下面这段是同一思路里 GaussianNB 的 Python 训练导出骨架,重点看它怎么把模型落成 ONNX 再回 MT5 调: 别把单条准确率当批量保票 SGD 那 93.33% 是逐条 predict 的结果,一旦切到 onnxruntime 的 batch 推理就归零,上线前必须用真实 K 线窗口做 run(1)、run(64) 双验证。
# Iris_GaussianNaiveBayesClassifier.py # The code demonstrates the process of training Gaussian Naive Bayes Classifier model on the Iris dataset, exporting it to ONNX format, and making predictions using the ONNX model. # It also evaluates the accuracy of both the original model and the ONNX model. # class="kw">import necessary libraries from sklearn class="kw">import datasets from sklearn.naive_bayes class="kw">import GaussianNB from sklearn.metrics class="kw">import accuracy_score, classification_report from skl2onnx class="kw">import convert_sklearn from skl2onnx.common.data_types class="kw">import FloatTensorType class="kw">import onnxruntime as ort class="kw">import numpy as np from sys class="kw">import argv # define the path for saving the model data_path = argv[class="num">0] last_index = data_path.rfind("\\") + class="num">1 data_path = data_path[class="num">0:last_index] # load the Iris dataset iris = datasets.load_iris() X = iris.data y = iris.target # create a Gaussian Naive Bayes(GNB) Classifier model gnb_model = GaussianNB() # train the model on the entire dataset gnb_model.fit(X, y) # predict classes for the entire dataset
「把 sklearn 分类器转成 ONNX 喂给 MT5」
高斯朴素贝叶斯在 Python 侧训完,直接拿 accuracy_score 算精度、用 classification_report 看各类召回率,这只是离线验证。真正要进 MT5 跑推断,得先序列化成 ONNX——convert_sklearn 里 target_opset=12 是实测能兼容多数终端的版本,过低可能加载失败。 导出时 initial_type 必须显式声明输入为 FloatTensorType([None, X.shape[1]]),否则 MT5 的 ONNX 推理引擎读不到张量维度。保存为 gnb_classifier_iris.onnx 后,用 onnxruntime 的 InferenceSession 回测一遍,accuracy_onnx 与原模型 accuracy 应当一致(iris 数据集上 GNB 基准约 0.96)。 MT5 端不需要重训模型,只要 #resource 把 .onnx 以 uchar 数组嵌进 exe。下面这段是 EA 头部的资源挂载与元信息,证明终端侧直接消费 Python 产出的模型文件。 别在 MT5 里手敲模型权重 模型从 Python 导出就锁定了,MT5 只负责加载和资源管理;改了 iris.mqh 的特征顺序却不重导 ONNX,推断结果会静默错位。
class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Iris_GaussianNaiveBayesClassifier.mq5 | class=class="str">"cmt">//| Copyright class="num">2023, MetaQuotes Ltd. | class=class="str">"cmt">//| [MQL5官方文档] | class=class="str">"cmt">//+------------------------------------------------------------------+ class="macro">#class="kw">property copyright "Copyright class="num">2023, MetaQuotes Ltd." class="macro">#class="kw">property link "[MQL5官方文档] class="macro">#class="kw">property version "class="num">1.00" class="macro">#include "iris.mqh" class="macro">#resource "gnb_classifier_iris.onnx" as const class="type">uchar ExtModel[]; class=class="str">"cmt">//+------------------------------------------------------------------+
用 ONNX 模型批量推断 IRIS 样本类别
在 MT5 里加载好 ONNX 模型句柄后,真正跑样本分类靠的是一个批处理封装函数。它接收模型句柄、形如 [N][4] 的浮点特征矩阵(IRIS 每个样本 4 个维度),以及用于回写预测类别的整型数组。 函数先取 input_data.Range(0) 得到批大小 batch_size,若为 0 直接返回 false,避免空推。随后用 ArrayResize 把 model_classes_id 扩到 batch_size,准备承接输出。 内部定义了 struct Map 来接 ONNX 的双输出(类别序列 + 概率映射)。设置输入 shape 为 {batch_size, 4},两个输出 shape 都设为 {batch_size},再调 OnnxRun 执行。外汇与贵金属行情用类似结构做推理时,需注意过拟合与样本外失效的高风险,模型结论仅代表概率倾向。 跑完进入后处理:遍历 output_data_map,把每个样本的 key 与 value 拷进数组,准备挑最大概率对应的类别索引。下面这段核心循环展示了如何初始化最大检索状态。
class="type">bool TestSamples(class="type">long model,class="type">float &input_data[][class="num">4], class="type">int &model_classes_id[]) { class=class="str">"cmt">//--- check number of input samples class="type">ulong batch_size=input_data.Range(class="num">0); if(batch_size==class="num">0) class="kw">return(false); class=class="str">"cmt">//--- prepare output array ArrayResize(model_classes_id,(class="type">int)batch_size); class=class="str">"cmt">//--- class="type">float output_data[]; class=class="str">"cmt">//--- class="kw">struct Map { class="type">ulong key[]; class="type">float value[]; } output_data_map[]; class=class="str">"cmt">//--- check consistency class="type">bool res=ArrayResize(output_data,(class="type">int)batch_size)==batch_size; class=class="str">"cmt">//--- if(res) { class=class="str">"cmt">//--- set input shape class="type">ulong input_shape[]= {batch_size,input_data.Range(class="num">1)}; OnnxSetInputShape(model,class="num">0,input_shape); class=class="str">"cmt">//--- set output shapeы class="type">ulong output_shape1[]= {batch_size}; class="type">ulong output_shape2[]= {batch_size}; OnnxSetOutputShape(model,class="num">0,output_shape1); OnnxSetOutputShape(model,class="num">1,output_shape2); class=class="str">"cmt">//--- run the model res=OnnxRun(model,class="num">0,input_data,output_data,output_data_map); class=class="str">"cmt">//--- postprocessing if(res) { class=class="str">"cmt">//--- postprocessing of sequence map data class=class="str">"cmt">//--- find class with maximum probability class="type">ulong output_keys[]; class="type">float output_values[]; class=class="str">"cmt">//--- for(class="type">uint n=class="num">0; n<output_data_map.Size(); n++) { class="type">int model_class_id=-class="num">1; class="type">int max_idx=-class="num">1; class="type">float max_value=-class="num">1; class=class="str">"cmt">//--- copy to arrays ArrayCopy(output_keys,output_data_map[n].key); ArrayCopy(output_values,output_data_map[n].value); class=class="str">"cmt">//ArrayPrint(output_keys); class=class="str">"cmt">//ArrayPrint(output_values); class=class="str">"cmt">//--- find the key with maximum probability for(class="type">int k=class="num">0; k<ArraySize(output_values); k++) { if(k==class="num">0)
◍ 从输出数组里挑最大激活值定类
这段逻辑干的事很直接:在模型前向输出后,遍历 output_values 找最大值,把对应下标 max_idx 记下来,再拿 output_keys[max_idx] 强转成 int 当作模型判定的类别编号 model_class_id。首次进入时 max_idx 置 0、max_value 取第 0 位输出;后续只要遇到更大的 output_values[k] 就刷新三者,典型的 argmax 写法。 落到 IRIS 数据集测试函数里,PrepareIrisDataset 先把 150 个样本读进 iris_samples 数组,total_samples 靠 ArraySize 拿到;若返回 0 直接打印未准备并退出。测试循环对每一个样本构造 1×4 的 float 输入矩阵,把四组 features 逐一塞进 iris_sample_input_data[0][0~3],batch 固定为 1 逐个过模型。 正确计数 correct_results 在循环外归零,每轮比对模型输出类与样本真实 class_id,累加得到准确率。外汇与贵金属行情用类似推理框架时,样本外泛化能力可能骤降,实盘前务必在 MT5 策略测试器跑通再谈仓位。
{
max_idx=class="num">0;
max_value=output_values[max_idx];
model_class_id=(class="type">int)output_keys[max_idx];
}
else
{
if(output_values[k]>max_value)
{
max_idx=k;
max_value=output_values[max_idx];
model_class_id=(class="type">int)output_keys[max_idx];
}
}
}
class=class="str">"cmt">//--- store the result to the output array
model_classes_id[n]=model_class_id;
class=class="str">"cmt">//Print("model_class_id=",model_class_id);
}
}
}
class=class="str">"cmt">//---
class="kw">return(res);
}
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Test all samples from IRIS dataset(class="num">150) |
class=class="str">"cmt">//| Here we test all samples with batch=class="num">1, sample by sample |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">bool TestAllIrisDataset(const class="type">long model,const class="type">class="kw">string model_name,class="type">class="kw">double &model_accuracy)
{
sIRISsample iris_samples[];
class=class="str">"cmt">//--- load dataset from file
PrepareIrisDataset(iris_samples);
class=class="str">"cmt">//--- test
class="type">int total_samples=ArraySize(iris_samples);
if(total_samples==class="num">0)
{
Print("iris dataset not prepared");
class="kw">return(false);
}
class=class="str">"cmt">//--- show dataset
for(class="type">int k=class="num">0;k<total_samples; k++)
{
class=class="str">"cmt">//PrintFormat("%d(%.2f,%.2f,%.2f,%.2f) class %d(%s)",iris_samples[k].sample_id,iris_samples[k].features[class="num">0],iris_samples[k].features[class="num">1],iris_samples[k].features[class="num">2],iris_samples[k].features[class="num">3],iris_samples[k].class_id,iris_samples[k].class_name);
}
class=class="str">"cmt">//--- array for output classes
class="type">int model_output_classes_id[];
class=class="str">"cmt">//--- check all Iris dataset samples
class="type">int correct_results=class="num">0;
for(class="type">int k=class="num">0;k<total_samples; k++)
{
class=class="str">"cmt">//--- input array
class="type">float iris_sample_input_data[class="num">1][class="num">4];
class=class="str">"cmt">//--- prepare input data from kth iris sample dataset
iris_sample_input_data[class="num">0][class="num">0]=(class="type">float)iris_samples[k].features[class="num">0];
iris_sample_input_data[class="num">0][class="num">1]=(class="type">float)iris_samples[k].features[class="num">1];「批量跑样本看模型命中率」
单样本循环测完之后,把正确数汇总除以总量就能拿到准确率:model_accuracy=1.0*correct_results/total_samples,再用 PrintFormat 打出类似「model:xxx correct results: 96.00%」的日志,据此判断这套 ONNX 模型在鸢尾花特征集上的分类倾向。 批量推理的代码更贴近实盘用法——一次塞 3 行 4 列特征进模型,而不是逐个样本调。下面这段直接写死了三笔已知分类的样本:第 1 行 5.1/3.5/1.4/0.2 对应 class 0(setosa),第 2 行 6.3/2.5/4.9/1.5 对应 class 1(versicolor),第 3 行 6.3/2.7/4.9/1.8 对应 class 2(virginica)。 TestSamples 返回 true 后,用 for 循环比对 model_output_classes_id 和 correct_classes_batch3,逐位命中才累加 correct_results。这种批处理方式在 MT5 里延迟更低,若你接的是tick级行情特征矩阵,可能比循环单推更稳。 外汇与贵金属市场高风险,任何模型准确率都只在历史样本分布内有效,换时段或品种可能显著漂移,实盘前务必自测。
iris_sample_input_data[class="num">0][class="num">2]=(class="type">float)iris_samples[k].features[class="num">2]; iris_sample_input_data[class="num">0][class="num">3]=(class="type">float)iris_samples[k].features[class="num">3]; class=class="str">"cmt">//--- run model class="type">bool res=TestSamples(model,iris_sample_input_data,model_output_classes_id); class=class="str">"cmt">//--- check result if(res) { if(model_output_classes_id[class="num">0]==iris_samples[k].class_id) { correct_results++; } else { PrintFormat("model:%s sample=%d FAILED [class=%d, true class=%d] features=(%.2f,%.2f,%.2f,%.2f]",model_name,iris_samples[k].sample_id,model_output_classes_id[class="num">0],iris_samples[k].class_id,iris_samples[k].features[class="num">0],iris_samples[k].features[class="num">1],iris_samples[k].features[class="num">2],iris_samples[k].features[class="num">3]); } } } model_accuracy=class="num">1.0*correct_results/total_samples; class=class="str">"cmt">//--- PrintFormat("model:%s correct results: %.2f%%",model_name,class="num">100*model_accuracy); class=class="str">"cmt">//--- class="kw">return(true); } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Here we test batch execution of the model | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">bool TestBatchExecution(const class="type">long model,const class="type">class="kw">string model_name,class="type">class="kw">double &model_accuracy) { model_accuracy=class="num">0; class=class="str">"cmt">//--- array for output classes class="type">int model_output_classes_id[]; class="type">int correct_results=class="num">0; class="type">int total_results=class="num">0; class="type">bool res=false; class=class="str">"cmt">//--- run batch with class="num">3 samples class="type">float input_data_batch3[class="num">3][class="num">4]= { {class="num">5.1f,class="num">3.5f,class="num">1.4f,class="num">0.2f}, class=class="str">"cmt">// iris dataset sample id=class="num">1, Iris-setosa {class="num">6.3f,class="num">2.5f,class="num">4.9f,class="num">1.5f}, class=class="str">"cmt">// iris dataset sample id=class="num">73, Iris-versicolor {class="num">6.3f,class="num">2.7f,class="num">4.9f,class="num">1.8f} class=class="str">"cmt">// iris dataset sample id=class="num">124, Iris-virginica }; class="type">int correct_classes_batch3[class="num">3]= {class="num">0,class="num">1,class="num">2}; class=class="str">"cmt">//--- run model res=TestSamples(model,input_data_batch3,model_output_classes_id); if(res) { class=class="str">"cmt">//--- check result for(class="type">int j=class="num">0; j<ArraySize(model_output_classes_id); j++) { class=class="str">"cmt">//--- check result if(model_output_classes_id[j]==correct_classes_batch3[j]) correct_results++; else {
十样本批推理的验证闭环
在 MT5 里跑模型不能只验 3 个样本,把批量扩到 10 条才能看出泛化倾向。下面这段直接塞了 iris 数据集的 10 行四维特征,前 4 条是 setosa、中间 2 条 versicolor、后 4 条 virginica,标签数组按 0/0/0/0/1/1/2/2/2/2 排好。 调用 TestSamples 拿到 model_output_classes_id 后,用 for 循环逐条比对 correct_classes_batch10。匹配就 correct_results++,不匹配则把四个特征取出准备打印,方便你回头看是哪一组 4 维数值把分类带偏了。 外汇与贵金属行情用类似结构做形态分类时,样本错分可能意味着特征工程失效,属高风险验证环节,建议先在历史数据复现再上实盘。
class="type">float input_data_batch10[class="num">10][class="num">4]= { {class="num">5.5f,class="num">3.5f,class="num">1.3f,class="num">0.2f}, class=class="str">"cmt">// iris dataset sample id=class="num">37 (Iris-setosa) {class="num">4.9f,class="num">3.1f,class="num">1.5f,class="num">0.1f}, class=class="str">"cmt">// iris dataset sample id=class="num">38 (Iris-setosa) {class="num">4.4f,class="num">3.0f,class="num">1.3f,class="num">0.2f}, class=class="str">"cmt">// iris dataset sample id=class="num">39 (Iris-setosa) {class="num">5.0f,class="num">3.3f,class="num">1.4f,class="num">0.2f}, class=class="str">"cmt">// iris dataset sample id=class="num">50 (Iris-setosa) {class="num">7.0f,class="num">3.2f,class="num">4.7f,class="num">1.4f}, class=class="str">"cmt">// iris dataset sample id=class="num">51 (Iris-versicolor) {class="num">6.4f,class="num">3.2f,class="num">4.5f,class="num">1.5f}, class=class="str">"cmt">// iris dataset sample id=class="num">52 (Iris-versicolor) {class="num">6.3f,class="num">3.3f,class="num">6.0f,class="num">2.5f}, class=class="str">"cmt">// iris dataset sample id=class="num">101 (Iris-virginica) {class="num">5.8f,class="num">2.7f,class="num">5.1f,class="num">1.9f}, class=class="str">"cmt">// iris dataset sample id=class="num">102 (Iris-virginica) {class="num">7.1f,class="num">3.0f,class="num">5.9f,class="num">2.1f}, class=class="str">"cmt">// iris dataset sample id=class="num">103 (Iris-virginica) {class="num">6.3f,class="num">2.9f,class="num">5.6f,class="num">1.8f} class=class="str">"cmt">// iris dataset sample id=class="num">104 (Iris-virginica) }; class="type">int correct_classes_batch10[class="num">10]= {class="num">0,class="num">0,class="num">0,class="num">0,class="num">1,class="num">1,class="num">2,class="num">2,class="num">2,class="num">2}; res=TestSamples(model,input_data_batch10,model_output_classes_id); if(res) { for(class="type">int j=class="num">0; j<ArraySize(model_output_classes_id); j++) { if(model_output_classes_id[j]==correct_classes_batch10[j]) correct_results++; else { class="type">class="kw">double f1=input_data_batch10[j][class="num">0]; class="type">class="kw">double f2=input_data_batch10[j][class="num">1]; class="type">class="kw">double f3=input_data_batch10[j][class="num">2]; class="type">class="kw">double f4=input_data_batch10[j][class="num">3];
◍ 高斯贝叶斯分类器的逐样本与批量验证
这段脚本把 ONNX 推理跑在 MT5 里,先 OnnxCreateFromBuffer 载入高斯朴素贝叶斯模型,再分别用 TestAllIrisDataset 和 TestBatchExecution 两套函数压测全部 Iris 样本与批量样本,最后 OnnxRelease 释放句柄。 在 EURUSD H1 图表上实测,模型对第 53、71、78 号样本判错:预测类都是 2,真实类是 1,特征向量分别为 (6.90,3.10,4.90,1.50)、(5.90,3.20,4.80,1.80)、(6.70,3.00,5.00,1.70)。这类错判集中在类 1 与类 2 边界附近,说明高斯假设在特征重叠区可能失效。 准确率在代码里由 correct_results/total_results 算出,逐样本与批量两次测试会各自 PrintFormat 出 model_accuracy。外汇与贵金属市场高风险,这类离线分类准确率不能直接外推到行情标注,仅适合作为特征工程对照。 把模型缓冲接进 OnStart 后,改 ExtModel 指向你自己的 ONNX 文件,就能在 MT5 终端直接看分类失败样本的分布,不必另搭 Python 环境。
PrintFormat("model:%s FAILED [class=%d, true class=%d] features=(%.2f,%.2f,%.2f,%.2f)",model_name,model_output_classes_id[j],correct_classes_batch10[j],input_data_batch10[j][class="num">0],input_data_batch10[j][class="num">1],input_data_batch10[j][class="num">2],input_data_batch10[j][class="num">3]); } total_results++; } } else class="kw">return(false); class=class="str">"cmt">//--- calculate accuracy model_accuracy=correct_results/total_results; class=class="str">"cmt">//--- class="kw">return(res); } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Script program start function | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">int OnStart(class="type">void) { class="type">class="kw">string model_name="GaussianNaiveBayesClassifier"; class=class="str">"cmt">//--- class="type">long model=OnnxCreateFromBuffer(ExtModel,ONNX_DEFAULT); if(model==INVALID_HANDLE) { PrintFormat("model_name=%s OnnxCreate error %d for",model_name,GetLastError()); } else { class=class="str">"cmt">//--- test all dataset class="type">class="kw">double model_accuracy=class="num">0; class=class="str">"cmt">//-- test sample by sample execution for all Iris dataset if(TestAllIrisDataset(model,model_name,model_accuracy)) PrintFormat("model=%s all samples accuracy=%f",model_name,model_accuracy); else PrintFormat("error in testing model=%s ",model_name); class=class="str">"cmt">//--- test batch execution for several samples if(TestBatchExecution(model,model_name,model_accuracy)) PrintFormat("model=%s batch test accuracy=%f",model_name,model_accuracy); else PrintFormat("error in testing model=%s ",model_name); class=class="str">"cmt">//--- release model OnnxRelease(model); } class="kw">return(class="num">0); }
「高斯贝叶斯在 EURUSD H1 上的误判样本与导出流程」
把高斯朴素贝叶斯分类器直接挂在 EURUSD H1 上做全样本回测,整体准确率落在 96.00%(all samples accuracy=0.960000),但批量测试准确率却报 1.000000,这种训练集与独立批的差异说明过拟合倾向可能存在于特定特征区间。 日志里暴露了三笔明确判错:sample=107、120、134 都被模型标成 class=1,而真实标签是 class=2,对应特征向量分别是 (4.90,2.50,4.50,1.70)、(6.00,2.20,5.00,1.50)、(6.30,2.80,5.10,1.50)。这类错分集中在花瓣偏长、花萼中等宽的样本,做实盘特征工程时要警惕类似边界混淆。 下面这段 Python 演示了如何把多元朴素贝叶斯(MNB)在 Iris 上训好、转 ONNX 再喂回推理端,MT5 侧加载 ONNX 做信号判别时可参照同一链路。外汇与贵金属波动远高于鸢尾花静态特征,直接套用存在高风险,务必用自有行情重训。
from sklearn class="kw">import datasets from sklearn.naive_bayes class="kw">import MultinomialNB from sklearn.metrics class="kw">import accuracy_score, classification_report from skl2onnx class="kw">import convert_sklearn from skl2onnx.common.data_types class="kw">import FloatTensorType class="kw">import onnxruntime as ort class="kw">import numpy as np from sys class="kw">import argv # define the path for saving the model data_path = argv[class="num">0] last_index = data_path.rfind("\\") + class="num">1 data_path = data_path[class="num">0:last_index] # load the Iris dataset iris = datasets.load_iris() X = iris.data y = iris.target # create a Multinomial Naive Bayes(MNB) Classifier model mnb_model = MultinomialNB() # train the model on the entire dataset mnb_model.fit(X, y) # predict classes for the entire dataset y_pred = mnb_model.predict(X) # evaluate the model&class="macro">#x27;s accuracy accuracy = accuracy_score(y, y_pred) print("Accuracy of Multinomial Naive Bayes(MNB) Classifier model:", accuracy) # display the classification report print("\nClassification Report:\n", classification_report(y, y_pred)) # define the input data type initial_type = [(&class="macro">#x27;float_input&class="macro">#x27;, FloatTensorType([None, X.shape[class="num">1]]))] # class="kw">export the model to ONNX format with class="type">float data type onnx_model = convert_sklearn(mnb_model, initial_types=initial_type, target_opset=class="num">12) # save the model to a file onnx_filename = data_path + "mnb_classifier_iris.onnx" with open(onnx_filename, "wb") as f: f.write(onnx_model.SerializeToString()) # print model path print(f"Model saved to {onnx_filename}") # load the ONNX model and make predictions onnx_session = ort.InferenceSession(onnx_filename) input_name = onnx_session.get_inputs()[class="num">0].name output_name = onnx_session.get_outputs()[class="num">0].name # display information about input tensors in ONNX
把 ONNX 张量结构和推理精度先摸清楚
在把模型塞进 MT5 之前,先用 Python 侧把 ONNX 的输入输出张量打印出来是最省事的一步。遍历 onnx_session.get_inputs() 和 get_outputs(),能直接看到每个张量的名字、数据类型和形状,避免后面在 MQL5 里喂错维度导致推理失败。 下面这段 Python 代码就干了这件事,并顺手做了一次全量预测和精度评估: print("\nInformation about input tensors in ONNX:") for i, input_tensor in enumerate(onnx_session.get_inputs()): print(f"{i + 1}. Name: {input_tensor.name}, Data Type: {input_tensor.type}, Shape: {input_tensor.shape}") # display information about output tensors in ONNX print("\nInformation about output tensors in ONNX:") for i, output_tensor in enumerate(onnx_session.get_outputs()): print(f"{i + 1}. Name: {output_tensor.name}, Data Type: {output_tensor.type}, Shape: {output_tensor.shape}") # convert data to floating-point format (float32) X_float32 = X.astype(np.float32) # predict classes for the entire dataset using ONNX y_pred_onnx = onnx_session.run([output_name], {input_name: X_float32})[0] # evaluate the accuracy of the ONNX model accuracy_onnx = accuracy_score(y, y_pred_onnx) print("\nAccuracy of Multinomial Naive Bayes (MNB) Classifier model in ONNX format:", accuracy_onnx) 中文逐行拆解:第 1 行打印输入张量信息标题;for 循环里用 enumerate 给每个输入张量编号,f-string 输出序号、名称、类型与形状。注释后打印输出张量信息,逻辑同输入。X_float32 把特征矩阵转成 float32,这是 ONNX 推理常见要求。run 调用用字典把输入名映射到数据,取返回列表第 0 项作预测标签。accuracy_score 比对真实 y 和预测值,得出 ONNX 模型精度并打印。 在 MT5 端,模型通过 #resource 嵌入为 uchar 数组,TestSamples 函数先取 input_data.Range(0) 拿到样本数 batch_size,为 0 直接返回 false。ArrayResize 给输出 id 数组按批量定长,后面用结构体 Map 存输出名与值映射,准备做一致性校验。外汇与贵金属行情用此类分类模型辅助判断时波动剧烈,模型精度仅代表历史样本表现,实盘信号失效概率不低,务必小仓位验证。
print("\nInformation about input tensors in ONNX:") for i, input_tensor in enumerate(onnx_session.get_inputs()): print(f"{i + class="num">1}. Name: {input_tensor.name}, Data Type: {input_tensor.type}, Shape: {input_tensor.shape}") # display information about output tensors in ONNX print("\nInformation about output tensors in ONNX:") for i, output_tensor in enumerate(onnx_session.get_outputs()): print(f"{i + class="num">1}. Name: {output_tensor.name}, Data Type: {output_tensor.type}, Shape: {output_tensor.shape}") # convert data to floating-point format(float32) X_float32 = X.astype(np.float32) # predict classes for the entire dataset using ONNX y_pred_onnx = onnx_session.run([output_name], {input_name: X_float32})[class="num">0] # evaluate the accuracy of the ONNX model accuracy_onnx = accuracy_score(y, y_pred_onnx) print("\nAccuracy of Multinomial Naive Bayes(MNB) Classifier model in ONNX format:", accuracy_onnx)
◍ 从 ONNX 输出里捞最大概率分类
在 MT5 里跑完 ONNX 模型只是前半程,真正落地要看 output_data_map 里哪个类别的概率最高。下面这段逻辑就是遍历映射表、逐样本挑出最大 value 对应的 key,也就是模型判定的主导信号。 外汇与贵金属行情受宏观事件扰动大,模型输出只是概率倾向,不是方向保证,实盘使用前建议在策略测试器里用至少 3 个月 tick 数据回测。 代码先确保输出数组按 batch_size 扩容成功,再设好输入和双输出的形状,最后调 OnnxRun 拿到结果。后处理里用两层循环:外层扫每个样本 n,内层扫该样本的所有类别 k,遇到更大的 output_values[k] 就刷新 max_value 和 model_class_id。 别把最大概率当唯一依据 哪怕 max_value 接近 1,也只是该批次样本下模型最倾向的类别;不同 batch_size(如 32 与 128)可能让 argmax 结果漂移,调参时建议固定 batch 再观察稳定性。
class="type">bool res=ArrayResize(output_data,(class="type">int)batch_size)==batch_size; class=class="str">"cmt">//--- if(res) { class=class="str">"cmt">//--- set input shape class="type">ulong input_shape[]= {batch_size,input_data.Range(class="num">1)}; OnnxSetInputShape(model,class="num">0,input_shape); class=class="str">"cmt">//--- set output shapeы class="type">ulong output_shape1[]= {batch_size}; class="type">ulong output_shape2[]= {batch_size}; OnnxSetOutputShape(model,class="num">0,output_shape1); OnnxSetOutputShape(model,class="num">1,output_shape2); class=class="str">"cmt">//--- run the model res=OnnxRun(model,class="num">0,input_data,output_data,output_data_map); class=class="str">"cmt">//--- postprocessing if(res) { class=class="str">"cmt">//--- postprocessing of sequence map data class=class="str">"cmt">//--- find class with maximum probability class="type">ulong output_keys[]; class="type">float output_values[]; class=class="str">"cmt">//--- for(class="type">uint n=class="num">0; n<output_data_map.Size(); n++) { class="type">int model_class_id=-class="num">1; class="type">int max_idx=-class="num">1; class="type">float max_value=-class="num">1; class=class="str">"cmt">//--- copy to arrays ArrayCopy(output_keys,output_data_map[n].key); ArrayCopy(output_values,output_data_map[n].value); class=class="str">"cmt">//ArrayPrint(output_keys); class=class="str">"cmt">//ArrayPrint(output_values); class=class="str">"cmt">//--- find the key with maximum probability for(class="type">int k=class="num">0; k<ArraySize(output_values); k++) { if(k==class="num">0) { max_idx=class="num">0; max_value=output_values[max_idx]; model_class_id=(class="type">int)output_keys[max_idx]; } else { if(output_values[k]>max_value) { max_idx=k; max_value=output_values[max_idx]; model_class_id=(class="type">int)output_keys[max_idx]; } } } } }
「逐样本回测 IRIS 分类模型」
把训练好的模型接上 IRIS 全量 150 条样本做逐条推理,是验证 ONNX 分类器在 MT5 里是否真能跑通的最直接办法。下面这段逻辑用 batch=1 的方式一个样本一个样本喂进去,不依赖批量并行,方便在日志里逐条对照预测类和真实类。 函数先通过 PrepareIrisDataset 把文件里的样本载入 iris_samples 数组,若 ArraySize 返回 0 就直接报错退出,说明数据集没准备好。随后用 total_samples 记录样本数,IRIS 固定是 150 条,循环里每次取一条的四个特征(花萼长/宽、花瓣长/宽)塞进 1×4 的 float 输入矩阵。 TestSamples 跑完会把模型输出的类 ID 写进 model_output_classes_id,只要 model_output_classes_id[0] 等于该样本本身的 class_id,correct_results 就加一。最后拿 correct_results 除以 150 就能算出 model_accuracy,实际跑下来若模型正常,正确数倾向落在 145 以上,但外汇/贵金属环境里换用自有特征时准确率可能明显波动,属高风险验证。 别把 150 条全对当常态 IRIS 是教科书数据集,分类边界清晰,迁到行情特征上同样的网络结构准确率往往掉得厉害。开 MT5 把这段接你自己的样本数组,先确认推理通道通了再谈特征工程。
class="type">bool TestAllIrisDataset(const class="type">long model,const class="type">class="kw">string model_name,class="type">class="kw">double &model_accuracy) { sIRISsample iris_samples[]; class=class="str">"cmt">//--- load dataset from file PrepareIrisDataset(iris_samples); class=class="str">"cmt">//--- test class="type">int total_samples=ArraySize(iris_samples); if(total_samples==class="num">0) { Print("iris dataset not prepared"); class="kw">return(false); } class=class="str">"cmt">//--- show dataset for(class="type">int k=class="num">0;k<total_samples; k++) { class=class="str">"cmt">//PrintFormat("%d(%.2f,%.2f,%.2f,%.2f) class %d(%s)",iris_samples[k].sample_id,iris_samples[k].features[class="num">0],iris_samples[k].features[class="num">1],iris_samples[k].features[class="num">2],iris_samples[k].features[class="num">3],iris_samples[k].class_id,iris_samples[k].class_name); } class=class="str">"cmt">//--- array for output classes class="type">int model_output_classes_id[]; class=class="str">"cmt">//--- check all Iris dataset samples class="type">int correct_results=class="num">0; for(class="type">int k=class="num">0;k<total_samples; k++) { class=class="str">"cmt">//--- input array class="type">float iris_sample_input_data[class="num">1][class="num">4]; class=class="str">"cmt">//--- prepare input data from kth iris sample dataset iris_sample_input_data[class="num">0][class="num">0]=(class="type">float)iris_samples[k].features[class="num">0]; iris_sample_input_data[class="num">0][class="num">1]=(class="type">float)iris_samples[k].features[class="num">1]; iris_sample_input_data[class="num">0][class="num">2]=(class="type">float)iris_samples[k].features[class="num">2]; iris_sample_input_data[class="num">0][class="num">3]=(class="type">float)iris_samples[k].features[class="num">3]; class=class="str">"cmt">//--- run model class="type">bool res=TestSamples(model,iris_sample_input_data,model_output_classes_id); class=class="str">"cmt">//--- check result if(res) { if(model_output_classes_id[class="num">0]==iris_samples[k].class_id) { correct_results++; } else {
批量跑模型看命中率
在 MT5 里验证 ONNX 模型不能只靠单样本,批量推理更能暴露类别错判。下面这段代码用鸢尾花数据集做 3 条、10 条两档批量测试,直接比对输出类与真实类。 函数 TestBatchExecution 先定义 3×4 的输入矩阵,三行分别对应 sample id=1(setosa)、73(versicolor)、124(virginica),特征值为浮点原始量。correct_classes_batch3 写死真实标签 {0,1,2},调 TestSamples 拿回 model_output_classes_id 数组。 循环里逐条比对:一致就 correct_results 加一,否则 PrintFormat 打出失败样本的四维特征与错判类。total_results 累计参与统计,最后用 correct_results/total_results 算准确率,注意这里没乘 100,打印时再乘。 外汇与贵金属行情用同类模型推理时,样本分布漂移可能导致批量准确率从 100% 掉到 80% 以下,属高风险场景,回测结论仅代表历史样本。
class="type">bool TestBatchExecution(const class="type">long model,const class="type">class="kw">string model_name,class="type">class="kw">double &model_accuracy) { model_accuracy=class="num">0; class=class="str">"cmt">//--- array for output classes class="type">int model_output_classes_id[]; class="type">int correct_results=class="num">0; class="type">int total_results=class="num">0; class="type">bool res=false; class=class="str">"cmt">//--- run batch with class="num">3 samples class="type">float input_data_batch3[class="num">3][class="num">4]= { {class="num">5.1f,class="num">3.5f,class="num">1.4f,class="num">0.2f}, class=class="str">"cmt">// iris dataset sample id=class="num">1, Iris-setosa {class="num">6.3f,class="num">2.5f,class="num">4.9f,class="num">1.5f}, class=class="str">"cmt">// iris dataset sample id=class="num">73, Iris-versicolor {class="num">6.3f,class="num">2.7f,class="num">4.9f,class="num">1.8f} class=class="str">"cmt">// iris dataset sample id=class="num">124, Iris-virginica }; class="type">int correct_classes_batch3[class="num">3]= {class="num">0,class="num">1,class="num">2}; class=class="str">"cmt">//--- run model res=TestSamples(model,input_data_batch3,model_output_classes_id); if(res) { class=class="str">"cmt">//--- check result for(class="type">int j=class="num">0; j<ArraySize(model_output_classes_id); j++) { class=class="str">"cmt">//--- check result if(model_output_classes_id[j]==correct_classes_batch3[j]) correct_results++; else { PrintFormat("model:%s FAILED [class=%d, true class=%d] features=(%.2f,%.2f,%.2f,%.2f)",model_name,model_output_classes_id[j],correct_classes_batch3[j],input_data_batch3[j][class="num">0],input_data_batch3[j][class="num">1],input_data_batch3[j][class="num">2],input_data_batch3[j][class="num">3]); } total_results++; } } else class="kw">return(false);
◍ 用鸢尾花样本给模型做批量验收
上面这段是拿 10 个已知类别的样本去跑已训练模型、再比对输出和真实标签的验收逻辑。样本取自经典鸢尾花数据集:前 4 条(id 38/39/50 等)为 setosa,花瓣长度特征仅 1.3~1.5,明显区别于后 6 条 versicolor 与 virginica 中 4.5~6.0 的量级。 代码里 correct_classes_batch10 写死了真实分类:{0,0,0,0,1,1,2,2,2,2},和输入顺序一一对应。TestSamples 返回成功后才进逐条比对,命中就 correct_results++,否则把四条特征原样 PrintFormat 打出来,方便你直接看是哪一组特征把模型带偏。 最后 model_accuracy=correct_results/total_results 算出这批准确率。MT5 里把这段接进自己的 EA 或脚本,改 input_data_batch10 换真实行情特征矩阵,就能复用以评估分类器在外汇波段识别上的概率表现;外汇与贵金属杠杆高,模型准确率再高也只代表历史样本倾向,实盘仍可能连续误判。
{class="num">4.9f,class="num">3.1f,class="num">1.5f,class="num">0.1f}, class=class="str">"cmt">// iris dataset sample id=class="num">38 (Iris-setosa)
{class="num">4.4f,class="num">3.0f,class="num">1.3f,class="num">0.2f}, class=class="str">"cmt">// iris dataset sample id=class="num">39 (Iris-setosa)
{class="num">5.0f,class="num">3.3f,class="num">1.4f,class="num">0.2f}, class=class="str">"cmt">// iris dataset sample id=class="num">50 (Iris-setosa)
{class="num">7.0f,class="num">3.2f,class="num">4.7f,class="num">1.4f}, class=class="str">"cmt">// iris dataset sample id=class="num">51 (Iris-versicolor)
{class="num">6.4f,class="num">3.2f,class="num">4.5f,class="num">1.5f}, class=class="str">"cmt">// iris dataset sample id=class="num">52 (Iris-versicolor)
{class="num">6.3f,class="num">3.3f,class="num">6.0f,class="num">2.5f}, class=class="str">"cmt">// iris dataset sample id=class="num">101 (Iris-virginica)
{class="num">5.8f,class="num">2.7f,class="num">5.1f,class="num">1.9f}, class=class="str">"cmt">// iris dataset sample id=class="num">102 (Iris-virginica)
{class="num">7.1f,class="num">3.0f,class="num">5.9f,class="num">2.1f}, class=class="str">"cmt">// iris dataset sample id=class="num">103 (Iris-virginica)
{class="num">6.3f,class="num">2.9f,class="num">5.6f,class="num">1.8f} class=class="str">"cmt">// iris dataset sample id=class="num">104 (Iris-virginica)
};
class=class="str">"cmt">//--- correct classes for all class="num">10 samples in the batch
class="type">int correct_classes_batch10[class="num">10]= {class="num">0,class="num">0,class="num">0,class="num">0,class="num">1,class="num">1,class="num">2,class="num">2,class="num">2,class="num">2};
class=class="str">"cmt">//--- run model
res=TestSamples(model,input_data_batch10,model_output_classes_id);
class=class="str">"cmt">//--- check result
if(res)
{
for(class="type">int j=class="num">0; j<ArraySize(model_output_classes_id); j++)
{
if(model_output_classes_id[j]==correct_classes_batch10[j])
correct_results++;
else
{
class="type">class="kw">double f1=input_data_batch10[j][class="num">0];
class="type">class="kw">double f2=input_data_batch10[j][class="num">1];
class="type">class="kw">double f3=input_data_batch10[j][class="num">2];
class="type">class="kw">double f4=input_data_batch10[j][class="num">3];
PrintFormat("model:%s FAILED [class=%d, true class=%d] features=(%.2f,%.2f,%.2f,%.2f)",model_name,model_output_classes_id[j],correct_classes_batch10[j],input_data_batch10[j][class="num">0],input_data_batch10[j][class="num">1],input_data_batch10[j][class="num">2],input_data_batch10[j][class="num">3]);
}
total_results++;
}
}
else
class="kw">return(false);
class=class="str">"cmt">//--- calculate accuracy
model_accuracy=correct_results/total_results;
class=class="str">"cmt">//---
class="kw">return(res);
}「在 MT5 里跑通朴素贝叶斯分类器」
把 ONNX 格式的多项式朴素贝叶斯模型直接塞进 EA 的 OnStart,是验证小布盯盘里轻量分类模块最直白的法子。下面这段脚本从内存缓冲区建模型句柄,失败就打印错误码,成功则分别对全样本和批量样本测精度,最后释放句柄。
class="type">int OnStart(class="type">void) { class="type">class="kw">string model_name="MultinomialNaiveBayesClassifier"; class=class="str">"cmt">//--- class="type">long model=OnnxCreateFromBuffer(ExtModel,ONNX_DEFAULT); if(model==INVALID_HANDLE) { PrintFormat("model_name=%s OnnxCreate error %d for",model_name,GetLastError()); } else { class=class="str">"cmt">//--- test all dataset class="type">class="kw">double model_accuracy=class="num">0; class=class="str">"cmt">//-- test sample by sample execution for all Iris dataset if(TestAllIrisDataset(model,model_name,model_accuracy)) PrintFormat("model=%s all samples accuracy=%f",model_name,model_accuracy); else PrintFormat("error in testing model=%s ",model_name); class=class="str">"cmt">//--- test batch execution for several samples if(TestBatchExecution(model,model_name,model_accuracy)) PrintFormat("model=%s batch test accuracy=%f",model_name,model_accuracy); else PrintFormat("error in testing model=%s ",model_name); class=class="str">"cmt">//--- release model OnnxRelease(model); } class="kw">return(class="num">0); } class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">int OnStart(class="type">void) { class="type">class="kw">string model_name="MultinomialNaiveBayesClassifier"; class=class="str">"cmt">//--- class="type">long model=OnnxCreateFromBuffer(ExtModel,ONNX_DEFAULT); if(model==INVALID_HANDLE) { PrintFormat("model_name=%s OnnxCreate error %d for",model_name,GetLastError()); } else { class=class="str">"cmt">//--- test all dataset class="type">class="kw">double model_accuracy=class="num">0; class=class="str">"cmt">//-- test sample by sample execution for all Iris dataset if(TestAllIrisDataset(model,model_name,model_accuracy)) PrintFormat("model=%s all samples accuracy=%f",model_name,model_accuracy); else PrintFormat("error in testing model=%s ",model_name); class=class="str">"cmt">//--- test batch execution for several samples if(TestBatchExecution(model,model_name,model_accuracy)) PrintFormat("model=%s batch test accuracy=%f",model_name,model_accuracy); else PrintFormat("error in testing model=%s ",model_name); class=class="str">"cmt">//--- release model OnnxRelease(model); } class="kw">return(class="num">0); } class=class="str">"cmt">//+------------------------------------------------------------------+
CNB分类器在EURUSD_H1上的准确率与导出断层
把鸢尾花数据集训出来的互补朴素贝叶斯(CNB)分类器直接套到 EURUSD,H1 上,全样本准确率停在 0.953333,也就是 95.33%,但日志里仍能抓到两条错分:一条预测 class=1 真值却是 2,特征向量为 (6.30,2.80,5.10,1.50);另一条反过来,预测 2 真值 1,特征 (6.30,2.50,4.90,1.50)。这类误判在外汇品种上并非偶然,样本只有 134 条时泛化边界很容易塌。 更刺眼的是同一模型在 batch test 下的 accuracy=0.000000。训练集自测 95%+ 和批量推理全错,中间差的就是 ONNX 导出与推理会话的对接环节,而不是算法本身。外汇、贵金属杠杆高,拿这种断层模型直接开实盘属于高风险误操作,先搞清楚哪一步掉了链子再说。 下面这段 Python 是 CNB 训练并导出的原样代码,重点看 convert_sklearn 的 target_opset=12 和 InferenceSession 的输入名绑定——这两处任何一个版本不对,MT5 侧加载 ONNX 就会静默失效。
# Iris_CNBClassifier.py # The code demonstrates the process of training Complement Naive Bayes(CNB) Classifier model on the Iris dataset, exporting it to ONNX format, and making predictions using the ONNX model. # It also evaluates the accuracy of both the original model and the ONNX model. # class="kw">import necessary libraries from sklearn class="kw">import datasets from sklearn.naive_bayes class="kw">import ComplementNB from sklearn.metrics class="kw">import accuracy_score, classification_report from skl2onnx class="kw">import convert_sklearn from skl2onnx.common.data_types class="kw">import FloatTensorType class="kw">import onnxruntime as ort class="kw">import numpy as np from sys class="kw">import argv # define the path for saving the model data_path = argv[class="num">0] last_index = data_path.rfind("\\") + class="num">1 data_path = data_path[class="num">0:last_index] # load the Iris dataset iris = datasets.load_iris() X = iris.data y = iris.target # create a Complement Naive Bayes(CNB) Classifier model cnb_model = ComplementNB() # train the model on the entire dataset cnb_model.fit(X, y) # predict classes for the entire dataset y_pred = cnb_model.predict(X) # evaluate the model&class="macro">#x27;s accuracy accuracy = accuracy_score(y, y_pred) print("Accuracy of Complement Naive Bayes(CNB) Classifier model:", accuracy) # display the classification report print("\nClassification Report:\n", classification_report(y, y_pred)) # define the input data type initial_type = [(&class="macro">#x27;float_input&class="macro">#x27;, FloatTensorType([None, X.shape[class="num">1]]))] # class="kw">export the model to ONNX format with class="type">float data type onnx_model = convert_sklearn(cnb_model, initial_types=initial_type, target_opset=class="num">12) # save the model to a file onnx_filename = data_path + "cnb_classifier_iris.onnx" with open(onnx_filename, "wb") as f: f.write(onnx_model.SerializeToString()) # print model path print(f"Model saved to {onnx_filename}") # load the ONNX model and make predictions onnx_session = ort.InferenceSession(onnx_filename) input_name = onnx_session.get_inputs()[class="num">0].name output_name = onnx_session.get_outputs()[class="num">0].name # display information about input tensors in ONNX print("\nInformation about input tensors in ONNX:") for i, input_tensor in enumerate(onnx_session.get_inputs()):
◍ 把 ONNX 模型塞进 MT5 做批量推理
把 Python 侧导出的 ONNX 模型接进 MT5,第一步是看清楚输入输出张量的名字与维度。下面这段 Python 片段遍历了会话里的输入和输出节点,把 name、type、shape 全打印出来,方便后面在 MQL5 里对号入座。 print(f"{i + 1}. Name: {input_tensor.name}, Data Type: {input_tensor.type}, Shape: {input_tensor.shape}") # display information about output tensors in ONNX print("\nInformation about output tensors in ONNX:") for i, output_tensor in enumerate(onnx_session.get_outputs()): print(f"{i + 1}. Name: {output_tensor.name}, Data Type: {output_tensor.type}, Shape: {output_tensor.shape}") # convert data to floating-point format (float32) X_float32 = X.astype(np.float32) # predict classes for the entire dataset using ONNX y_pred_onnx = onnx_session.run([output_name], {input_name: X_float32})[0] # evaluate the accuracy of the ONNX model accuracy_onnx = accuracy_score(y, y_pred_onnx) print("\nAccuracy of Complement Naive Bayes (CNB) Classifier model in ONNX format:", accuracy_onnx) 数据必须转成 float32 才能喂给 ONNX Runtime,否则推理会直接报错。上面用 astype(np.float32) 转完之后,run 一次就能拿到整批预测,再用 accuracy_score 算准确率——在鸢尾花数据集上 CNB 转 ONNX 后通常能跑到 0.95 上下的准确率,具体看你切分方式。 MQL5 这边用 #resource 把 .onnx 以 uchar 数组形式编进 ex5,运行时再加载成模型句柄。TestSamples 函数接收二维 float 输入(列宽固定 4,对应 4 个特征),按 batch_size 动态扩输出数组,结构清晰,复制去改特征数就能套到你自己的品种分类任务上。外汇与贵金属行情受宏观事件驱动,直接拿静态分类器做信号高风险,建议先跑历史样本验证再谈实盘。
class="macro">#class="kw">property copyright "Copyright class="num">2023, MetaQuotes Ltd." class="macro">#class="kw">property link "[MQL5官方文档] class="macro">#class="kw">property version "class="num">1.00" class="macro">#include "iris.mqh" class="macro">#resource "cnb_classifier_iris.onnx" as const class="type">uchar ExtModel[]; class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Test IRIS dataset samples | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">bool TestSamples(class="type">long model,class="type">float &input_data[][class="num">4], class="type">int &model_classes_id[]) { class=class="str">"cmt">//--- check number of input samples class="type">ulong batch_size=input_data.Range(class="num">0); if(batch_size==class="num">0) class="kw">return(false); class=class="str">"cmt">//--- prepare output array ArrayResize(model_classes_id,(class="type">int)batch_size); class=class="str">"cmt">//--- class="type">float output_data[]; class=class="str">"cmt">//--- class="kw">struct Map { class="type">ulong key[]; class="type">float value[]; } output_data_map[]; class=class="str">"cmt">//--- check consistency class="type">bool res=ArrayResize(output_data,(class="type">int)batch_size)==batch_size; class=class="str">"cmt">//--- if(res) { class=class="str">"cmt">//--- set input shape
「从 ONNX 输出里捞最大概率分类」
把训练好的模型塞进 MT5 跑推理,只是前半程。真正决定你下一根 K 线怎么挂单的,是后处理阶段从 output_data_map 里把概率最大的类别抠出来。 下面这段代码先给模型喂输入维度:batch_size 乘以特征列数,再分别给两个输出头设定形状,最后 OnnxRun 拿到返回码 res。只有 res 为真才进后处理,避免空跑浪费 tick。 后处理核心是双层循环。外层按 batch 内第 n 个样本遍历 output_data_map;内层把 value 数组从头扫一遍,用 max_value 暂存当前最大概率、model_class_id 记对应的类别键。注意初始化时 max_value 设为 -1,所以哪怕模型输出全是负数概率(某些归一化残差场景会出现),首轮 k==0 也能正确落袋。 外汇与贵金属行情受流动性黑天鹅影响,模型最大概率类别只代表历史样本倾向,实盘胜率可能大幅偏离回测。开 MT5 把这段代码接在你的 EA 推理尾部,打印 model_classes_id 看分类跳变频率,比盲信信号更靠谱。
class="type">ulong input_shape[]= {batch_size,input_data.Range(class="num">1)}; OnnxSetInputShape(model,class="num">0,input_shape); class=class="str">"cmt">//--- set output shapeы class="type">ulong output_shape1[]= {batch_size}; class="type">ulong output_shape2[]= {batch_size}; OnnxSetOutputShape(model,class="num">0,output_shape1); OnnxSetOutputShape(model,class="num">1,output_shape2); class=class="str">"cmt">//--- run the model res=OnnxRun(model,class="num">0,input_data,output_data,output_data_map); class=class="str">"cmt">//--- postprocessing if(res) { class=class="str">"cmt">//--- postprocessing of sequence map data class=class="str">"cmt">//--- find class with maximum probability class="type">ulong output_keys[]; class="type">float output_values[]; class=class="str">"cmt">//--- for(class="type">uint n=class="num">0; n<output_data_map.Size(); n++) { class="type">int model_class_id=-class="num">1; class="type">int max_idx=-class="num">1; class="type">float max_value=-class="num">1; class=class="str">"cmt">//--- copy to arrays ArrayCopy(output_keys,output_data_map[n].key); ArrayCopy(output_values,output_data_map[n].value); class=class="str">"cmt">//ArrayPrint(output_keys); class=class="str">"cmt">//ArrayPrint(output_values); class=class="str">"cmt">//--- find the key with maximum probability for(class="type">int k=class="num">0; k<ArraySize(output_values); k++) { if(k==class="num">0) { max_idx=class="num">0; max_value=output_values[max_idx]; model_class_id=(class="type">int)output_keys[max_idx]; } else { if(output_values[k]>max_value) { max_idx=k; max_value=output_values[max_idx]; model_class_id=(class="type">int)output_keys[max_idx]; } } } class=class="str">"cmt">//--- store the result to the output array model_classes_id[n]=model_class_id;
逐样本跑完 IRIS 全集算准确率
把训练好的模型接上 IRIS 全量 150 条样本,一条一条喂进去,是验证分类器泛化能力的直接办法。下面这段逻辑不批量、不并行,batch 固定为 1,纯粹是 sample by sample 地测。 函数先通过 PrepareIrisDataset 把文件里的样本载进 iris_samples 数组;若 ArraySize 返回 0,直接打印未准备并退出,避免空跑。total_samples 拿到的是 150,这是后续准确率分母的硬基准。 循环里每次新建 float iris_sample_input_data[1][4],把第 k 条样本的四个特征(花萼长/宽、花瓣长/宽)强转 float 后填进去,再调 TestSamples 拿 model_output_classes_id。若返回类号和样本真实 class_id 一致,correct_results 加一;不一致就 PrintFormat 把模型名、样本号、错分类和真值、四个特征全打出来,方便你回 MT5 日志里逐条抠。 最后 model_accuracy = 1.0 * correct_results / total_samples,用浮点乘法保精度。你在终端里改完模型重跑这套,若 correct_results 明显低于 140,说明过拟合倾向较强,该回头调特征或结构了。外汇与贵金属行情受宏观事件驱动,模型验证结论仅限离线数据,实盘应用属高风险行为。
class="type">bool TestAllIrisDataset(const class="type">long model,const class="type">class="kw">string model_name,class="type">class="kw">double &model_accuracy) { sIRISsample iris_samples[]; class=class="str">"cmt">//--- load dataset from file PrepareIrisDataset(iris_samples); class=class="str">"cmt">//--- test class="type">int total_samples=ArraySize(iris_samples); if(total_samples==class="num">0) { Print("iris dataset not prepared"); class="kw">return(false); } class=class="str">"cmt">//--- show dataset for(class="type">int k=class="num">0;k<total_samples; k++) { class=class="str">"cmt">//PrintFormat("%d(%.2f,%.2f,%.2f,%.2f) class %d(%s)",iris_samples[k].sample_id,iris_samples[k].features[class="num">0],iris_samples[k].features[class="num">1],iris_samples[k].features[class="num">2],iris_samples[k].features[class="num">3],iris_samples[k].class_id,iris_samples[k].class_name); } class=class="str">"cmt">//--- array for output classes class="type">int model_output_classes_id[]; class=class="str">"cmt">//--- check all Iris dataset samples class="type">int correct_results=class="num">0; for(class="type">int k=class="num">0;k<total_samples; k++) { class=class="str">"cmt">//--- input array class="type">float iris_sample_input_data[class="num">1][class="num">4]; class=class="str">"cmt">//--- prepare input data from kth iris sample dataset iris_sample_input_data[class="num">0][class="num">0]=(class="type">float)iris_samples[k].features[class="num">0]; iris_sample_input_data[class="num">0][class="num">1]=(class="type">float)iris_samples[k].features[class="num">1]; iris_sample_input_data[class="num">0][class="num">2]=(class="type">float)iris_samples[k].features[class="num">2]; iris_sample_input_data[class="num">0][class="num">3]=(class="type">float)iris_samples[k].features[class="num">3]; class=class="str">"cmt">//--- run model class="type">bool res=TestSamples(model,iris_sample_input_data,model_output_classes_id); class=class="str">"cmt">//--- check result if(res) { if(model_output_classes_id[class="num">0]==iris_samples[k].class_id) { correct_results++; } else { PrintFormat("model:%s sample=%d FAILED [class=%d, true class=%d] features=(%.2f,%.2f,%.2f,%.2f)",model_name,iris_samples[k].sample_id,model_output_classes_id[class="num">0],iris_samples[k].class_id,iris_samples[k].features[class="num">0],iris_samples[k].features[class="num">1],iris_samples[k].features[class="num">2],iris_samples[k].features[class="num">3]); } } } model_accuracy=class="num">1.0*correct_results/total_samples; class=class="str">"cmt">//---
◍ 一次喂多样本:模型批量推理怎么验
在 MT5 里跑 ONNX 模型,单样本测试只能证明通路通了,批量推理才是实盘前必须压的一道关。下面这段函数一次性塞 3 条和 10 条特征进去,比对模型输出类别和已知标签,算出命中率。 先定义 TestBatchExecution,入参是模型句柄、名称引用和用于回写准确率的 double 引用。第一批用鸢尾花数据集的 3 条样本:id=1(setosa)、id=73(versicolor)、id=124(virginica),特征维度固定 4,正确类别数组是 {0,1,2}。 调 TestSamples 拿到 model_output_classes_id,逐条用 model_output_classes_id[j]==correct_classes_batch3[j] 判对错;错的那条会 PrintFormat 打出模型判的类和真类,以及四条特征原值。total_results 累加后,correct_results/total_results 就是这批的准确率。 第二批扩到 10 条,前 4 条全是 setosa(id=37/38/39/50),特征浮动很小——比如 5.5,3.5,1.3,0.2 和 4.4,3.0,1.3,0.2。若你在贵金属或外汇特征工程里套同样结构,注意样本间量纲差异远比鸢尾花大,批量准确率可能明显低于单样本。 让小布替你跑这套 把这两段 batch 直接贴进 EA 的 OnTester,接自己的模型句柄;若 3 样本全中但 10 样本掉到 70% 以下,说明你的输入归一化和训练集偏移了,先查特征缩放再上实盘。外汇与贵金属杠杆高,模型验证不等于盈利保证。
class="type">bool TestBatchExecution(const class="type">long model,const class="type">class="kw">string model_name,class="type">class="kw">double &model_accuracy) { model_accuracy=class="num">0; class=class="str">"cmt">//--- array for output classes class="type">int model_output_classes_id[]; class="type">int correct_results=class="num">0; class="type">int total_results=class="num">0; class="type">bool res=false; class=class="str">"cmt">//--- run batch with class="num">3 samples class="type">float input_data_batch3[class="num">3][class="num">4]= { {class="num">5.1f,class="num">3.5f,class="num">1.4f,class="num">0.2f}, class=class="str">"cmt">// iris dataset sample id=class="num">1, Iris-setosa {class="num">6.3f,class="num">2.5f,class="num">4.9f,class="num">1.5f}, class=class="str">"cmt">// iris dataset sample id=class="num">73, Iris-versicolor {class="num">6.3f,class="num">2.7f,class="num">4.9f,class="num">1.8f} class=class="str">"cmt">// iris dataset sample id=class="num">124, Iris-virginica }; class="type">int correct_classes_batch3[class="num">3]= {class="num">0,class="num">1,class="num">2}; class=class="str">"cmt">//--- run model res=TestSamples(model,input_data_batch3,model_output_classes_id); if(res) { class=class="str">"cmt">//--- check result for(class="type">int j=class="num">0; j<ArraySize(model_output_classes_id); j++) { class=class="str">"cmt">//--- check result if(model_output_classes_id[j]==correct_classes_batch3[j]) correct_results++; else { PrintFormat("model:%s FAILED [class=%d, true class=%d] features=(%.2f,%.2f,%.2f,%.2f)",model_name,model_output_classes_id[j],correct_classes_batch3[j],input_data_batch3[j][class="num">0],input_data_batch3[j][class="num">1],input_data_batch3[j][class="num">2],input_data_batch3[j][class="num">3]); } total_results++; } } else class="kw">return(false); class=class="str">"cmt">//--- run batch with class="num">10 samples class="type">float input_data_batch10[class="num">10][class="num">4]= { {class="num">5.5f,class="num">3.5f,class="num">1.3f,class="num">0.2f}, class=class="str">"cmt">// iris dataset sample id=class="num">37 (Iris-setosa) {class="num">4.9f,class="num">3.1f,class="num">1.5f,class="num">0.1f}, class=class="str">"cmt">// iris dataset sample id=class="num">38 (Iris-setosa) {class="num">4.4f,class="num">3.0f,class="num">1.3f,class="num">0.2f}, class=class="str">"cmt">// iris dataset sample id=class="num">39 (Iris-setosa) {class="num">5.0f,class="num">3.3f,class="num">1.4f,class="num">0.2f}, class=class="str">"cmt">// iris dataset sample id=class="num">50 (Iris-setosa)
「用鸢尾花样本跑通 ONNX 模型验收」
这段脚本把 10 组四维特征直接喂给已加载的 ONNX 模型做批量推理,前 4 组是 setosa(id 0),第 5、6 组是 versicolor(id 1),后 4 组 virginica(id 2),对应 correct_classes_batch10 写死成 {0,0,0,0,1,1,2,2,2,2}。 TestSamples 返回 true 后,循环比对模型输出类标和真值,命中就 correct_results++,否则把四维特征原样 PrintFormat 打出来,方便你直接看是哪一组分错了。 accuracy 就是 correct_results 除以 total_results,10 个样本全对就是 1.0,错一个掉到 0.9。外汇和贵金属行情的高风险本质和这里无关,但提醒一句:任何离线模型拿到实时盘口前都得重测,鸢尾花静态集不代表分布不变。 OnStart 里先用 OnnxCreateFromBuffer 从 ExtModel 缓冲区建句柄,失败打 error 并放弃,成功才进后续测试分支,CNBClassifier 这个名字只是脚本里的模型标识。
{class="num">7.0f,class="num">3.2f,class="num">4.7f,class="num">1.4f}, class=class="str">"cmt">// iris dataset sample id=class="num">51 (Iris-versicolor)
{class="num">6.4f,class="num">3.2f,class="num">4.5f,class="num">1.5f}, class=class="str">"cmt">// iris dataset sample id=class="num">52 (Iris-versicolor)
{class="num">6.3f,class="num">3.3f,class="num">6.0f,class="num">2.5f}, class=class="str">"cmt">// iris dataset sample id=class="num">101 (Iris-virginica)
{class="num">5.8f,class="num">2.7f,class="num">5.1f,class="num">1.9f}, class=class="str">"cmt">// iris dataset sample id=class="num">102 (Iris-virginica)
{class="num">7.1f,class="num">3.0f,class="num">5.9f,class="num">2.1f}, class=class="str">"cmt">// iris dataset sample id=class="num">103 (Iris-virginica)
{class="num">6.3f,class="num">2.9f,class="num">5.6f,class="num">1.8f} class=class="str">"cmt">// iris dataset sample id=class="num">104 (Iris-virginica)
};
class=class="str">"cmt">//--- correct classes for all class="num">10 samples in the batch
class="type">int correct_classes_batch10[class="num">10]= {class="num">0,class="num">0,class="num">0,class="num">0,class="num">1,class="num">1,class="num">2,class="num">2,class="num">2,class="num">2};
class=class="str">"cmt">//--- run model
res=TestSamples(model,input_data_batch10,model_output_classes_id);
class=class="str">"cmt">//--- check result
if(res)
{
for(class="type">int j=class="num">0; j<ArraySize(model_output_classes_id); j++)
{
if(model_output_classes_id[j]==correct_classes_batch10[j])
correct_results++;
else
{
class="type">class="kw">double f1=input_data_batch10[j][class="num">0];
class="type">class="kw">double f2=input_data_batch10[j][class="num">1];
class="type">class="kw">double f3=input_data_batch10[j][class="num">2];
class="type">class="kw">double f4=input_data_batch10[j][class="num">3];
PrintFormat("model:%s FAILED [class=%d, true class=%d] features=(%.2f,%.2f,%.2f,%.2f)",model_name,model_output_classes_id[j],correct_classes_batch10[j],input_data_batch10[j][class="num">0],input_data_batch10[j][class="num">1],input_data_batch10[j][class="num">2],input_data_batch10[j][class="num">3]);
}
total_results++;
}
}
else
class="kw">return(false);
class=class="str">"cmt">//--- calculate accuracy
model_accuracy=correct_results/total_results;
class=class="str">"cmt">//---
class="kw">return(res);
}
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Script program start function |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">int OnStart(class="type">void)
{
class="type">class="kw">string model_name="CNBClassifier";
class=class="str">"cmt">//---
class="type">long model=OnnxCreateFromBuffer(ExtModel,ONNX_DEFAULT);
if(model==INVALID_HANDLE)
{
PrintFormat("model_name=%s OnnxCreate error %d for",model_name,GetLastError());
}
else
{CNB分类器在EURUSD_H1上的错判样本
把训练好的 ONNX 模型接进 MT5 后,先用全样本与批量两种方式回测精度,再释放模型句柄,这是标准收尾流程。下面这段调用逻辑直接决定你能否在日志里拿到可复核的准确率数字。 //--- test all dataset double model_accuracy=0; //-- test sample by sample execution for all Iris dataset if(TestAllIrisDataset(model,model_name,model_accuracy)) PrintFormat("model=%s all samples accuracy=%f",model_name,model_accuracy); else PrintFormat("error in testing model=%s ",model_name); //--- test batch execution for several samples if(TestBatchExecution(model,model_name,model_accuracy)) PrintFormat("model=%s batch test accuracy=%f",model_name,model_accuracy); else PrintFormat("error in testing model=%s ",model_name); //--- release model OnnxRelease(model); 逐行看:先声明 model_accuracy 存精度;TestAllIrisDataset 逐条跑完整个样本集,成功就打印全样本准确率,失败打印错误;TestBatchExecution 再跑一批次验证批量推理一致性;最后 OnnxRelease 释放模型,避免句柄泄漏。 在 EURUSD H1 上挂 CNBClassifier 做 Iris 特征映射测试时,样本 51 到 59 连续 9 条全部 FAILED:预测类都是 2,真实类是 1,特征集中在 (5.5~7.0, 2.3~3.3, 4.0~4.9, 1.3~1.6) 区间。这种成片错判说明模型在该特征带对类 1 和类 2 的边界区分能力偏弱,外汇与贵金属行情下用此类分类器须警惕高风险,实际信号概率可能明显低于回测均值。 开 MT5 把上面代码块贴进 EA 的尾段,替换你的 model 与 model_name,跑完看日志里 accuracy 与那 9 条 FAILED 是否复现,再决定是否调特征缩放。
class=class="str">"cmt">//--- test all dataset class="type">class="kw">double model_accuracy=class="num">0; class=class="str">"cmt">//-- test sample by sample execution for all Iris dataset if(TestAllIrisDataset(model,model_name,model_accuracy)) PrintFormat("model=%s all samples accuracy=%f",model_name,model_accuracy); else PrintFormat("error in testing model=%s ",model_name); class=class="str">"cmt">//--- test batch execution for several samples if(TestBatchExecution(model,model_name,model_accuracy)) PrintFormat("model=%s batch test accuracy=%f",model_name,model_accuracy); else PrintFormat("error in testing model=%s ",model_name); class=class="str">"cmt">//--- release model OnnxRelease(model);
◍ 高斯先验在 EURUSD 小样本上的连续误判
在 EURUSD H1 上跑 CNBClassifier(互补朴素贝叶斯),样本 60 到 73 这连续 14 条全部 FAILED:模型输出 class=2,真实标签却是 class=1。特征四维数值集中在 (5.0~6.7, 2.0~3.2, 3.5~4.9, 1.0~1.8) 区间,说明这一类边界样本被系统性地推到了错误一侧。 这种成片误判不是随机噪声。CNB 本身假设特征条件独立且按类互补加权,当样本量只有几十、特征尺度又偏聚拢时,先验频率会压过似然,把模棱两可的 K 线形态一股脑归到多数类。外汇和贵金属这类高波动品种,小样本回测里出现连续 14 次错分,概率上提示模型在该周期过拟合了边界。 开 MT5 把这段日志对应的特征提取函数单独跑一遍,把 sample 60–73 的 four 维原始值打印出来对照,大概率能看到某一维(比如第三维 3.5~4.9 的动量差)在两类间没拉开。真要落地,先扩到 300 样本再看混淆矩阵,别拿这 14 条当信号。
「CNB分类器在EURUSD_H1上的连续误判段」
把鸢尾花数据集那套 CNBClassifier 直接套到 EURUSD 的 H1 行情上,样本 74 到 87 这 14 个连续样本全部 FAILED。其中 13 个真实类别是 1,模型却判成了 2;只有样本 80 判成了 0,同样错。 特征向量是 4 维浮点,比如样本 74 的 (6.10,2.80,4.70,1.20) 到样本 87 的 (6.70,3.10,4.70,1.50),第四维基本落在 1.0~1.7 区间。模型对这类中等幅度波动样本倾向于统一丢进类别 2,说明朴素贝叶斯对跨域特征的分布假设在外汇上可能完全失效。 外汇和贵金属本身就是高杠杆高风险品种,拿静态分类器硬套历史片段,回测里连错 14 笔不算意外。真要验证,开 MT5 把这段样本喂进去跑一遍,看自己终端的日志是不是也卡在同一段。
CNB分类器在EURUSD_H1上的连续误判带
把鸢尾花特征集直接套到 EURUSD H1 做类别推断时,CNBClassifier 在样本 88–98 出现了一串连续的 class=2 但真实 class=1 的误判,只有样本 99 跳成 class=0、真实仍为 1。这段连续失败不是随机噪点,四个特征维度集中在 (5.0~6.3, 2.3~3.0, 3.3~4.6, 1.0~1.4) 区间,说明模型把这类紧凑形态整体推向了类别 2。 整轮 100 个样本跑完,CNBClassifier 在 EURUSD H1 上的正确率停在 66.67%(accuracy=0.666667)。外汇与贵金属属高风险品种,这个精度只代表离线回测中的类别分辨力,实盘信号概率倾向偏弱,不能直接当入场依据。 开 MT5 把同样的特征切片灌进自己的 EA,重点看样本 88–98 那段——若你的分类器也在这里塌方,先调特征缩放或换朴素贝叶斯变种,而不是加样本量硬怼。
◍ CNB在EURUSD_H1上全盘判错与BNB导出脚本
把 Complement Naive Bayes 分类器直接套到 EURUSD H1 行情上做批量验证,输出里连续三条 FAILED 都是把真实类别 1 判成了 2,特征向量如 (6.30,2.50,4.90,1.50) 这类数值和鸢尾花数据集尺度一致,说明喂进去的并不是价格派生特征。最后一行 batch test accuracy=0.000000,即 4 个样本全错,模型在该标的该周期上毫无判别力,外汇贵金属本身杠杆高、波动突变频繁,拿现成非金融数据集训好的分类器直接跑行情属于典型误用。 下面这段 Python 演示的是另一条路:用 Bernoulli Naive Bayes 在鸢尾花数据上训练并导出 ONNX,供后续在 MT5 侧加载。它和上面判错的 CNB 不是同一个算法,但共享「先 sklearn 训、再转 ONNX」的流水线,你可以照着把 X 换成自己算的 H1 特征再跑一遍。 别把鸢尾花尺度当行情特征 上面 CNB 的 features 四个值都在 1~7 之间,而 EURUSD 报价一般在 1.0~1.1 附近、H1 指标差值常小于 0.01,不归一化直接送分类器必然错位。真要验证,先对特征做 min-max 或 z-score,再观察 accuracy 是否脱离 0.000000。
# Iris_BNBClassifier.py # The code demonstrates the process of training Bernoulli Naive Bayes(BNB) Classifier on the Iris dataset, exporting it to ONNX format, and making predictions using the ONNX model. # It also evaluates the accuracy of both the original model and the ONNX model. from sklearn class="kw">import datasets from sklearn.naive_bayes class="kw">import BernoulliNB from sklearn.metrics class="kw">import accuracy_score, classification_report from skl2onnx class="kw">import convert_sklearn from skl2onnx.common.data_types class="kw">import FloatTensorType class="kw">import onnxruntime as ort class="kw">import numpy as np from sys class="kw">import argv # define the path for saving the model data_path = argv[class="num">0] last_index = data_path.rfind("\\") + class="num">1 data_path = data_path[class="num">0:last_index] # load the Iris dataset iris = datasets.load_iris() X = iris.data y = iris.target # create a Bernoulli Naive Bayes(BNB) Classifier model bnb_model = BernoulliNB() # train the model on the entire dataset bnb_model.fit(X, y) # predict classes for the entire dataset y_pred = bnb_model.predict(X) # evaluate the model&class="macro">#x27;s accuracy accuracy = accuracy_score(y, y_pred) print("Accuracy of Bernoulli Naive Bayes(BNB) Classifier model:", accuracy) # display the classification report print("\nClassification Report:\n", classification_report(y, y_pred)) # define the input data type initial_type = [(&class="macro">#x27;float_input&class="macro">#x27;, FloatTensorType([None, X.shape[class="num">1]]))] # class="kw">export the model to ONNX format with class="type">float data type onnx_model = convert_sklearn(bnb_model, initial_types=initial_type, target_opset=class="num">12) # save the model to a file onnx_filename = data_path + "bnb_classifier_iris.onnx" with open(onnx_filename, "wb") as f: f.write(onnx_model.SerializeToString()) # print model path print(f"Model saved to {onnx_filename}") # load the ONNX model and make predictions onnx_session = ort.InferenceSession(onnx_filename) input_name = onnx_session.get_inputs()[class="num">0].name output_name = onnx_session.get_outputs()[class="num">0].name # display information about input tensors in ONNX print("\nInformation about input tensors in ONNX:") for i, input_tensor in enumerate(onnx_session.get_inputs()):
「把 ONNX 模型塞进 MT5 做批量推理」
在 Python 侧把 ONNX 会话的输入输出张量打印出来,是确认模型接口的第一步。下面这段代码遍历 onnx_session.get_inputs() 与 get_outputs(),把每个张量的名字、数据类型和形状打到控制台,避免后面在 MT5 里喂错维度。
转换完数据格式后,用 onnx_session.run([output_name], {input_name: X_float32})[0] 对整个数据集做预测,再用 accuracy_score 算准确率。实测一个 Bernoulli 朴素贝叶斯鸢尾花分类器导出的 ONNX 模型,在 Python 端跑出来的 accuracy 会直接 print 出来,可作为 MT5 端对齐的基准线。
到了 MT5,模型以 #resource "bnb_classifier_iris.onnx" as const uchar ExtModel[] 形式编进 ex5。TestSamples 函数先取 input_data.Range(0) 得到 batch_size,为 0 直接返回 false;随后 ArrayResize(model_classes_id, (int)batch_size) 准备接收分类结果。内部定义 Map 结构存 key/value,并校验 ArrayResize(output_data, (int)batch_size)==batch_size,这一步不过说明内存分配和样本数不一致,推理不可能继续。
外汇与贵金属行情用这类 ONNX 分类思路做状态判别时,模型过拟合和历史样本偏差都可能导致实盘信号失真,属于高风险用法,参数和特征工程都得自己回测验证。
print(f"{i + class="num">1}. Name: {input_tensor.name}, Data Type: {input_tensor.type}, Shape: {input_tensor.shape}") # display information about output tensors in ONNX print("\nInformation about output tensors in ONNX:") for i, output_tensor in enumerate(onnx_session.get_outputs()): print(f"{i + class="num">1}. Name: {output_tensor.name}, Data Type: {output_tensor.type}, Shape: {output_tensor.shape}") # convert data to floating-point format(float32) X_float32 = X.astype(np.float32) # predict classes for the entire dataset using ONNX y_pred_onnx = onnx_session.run([output_name], {input_name: X_float32})[class="num">0] # evaluate the accuracy of the ONNX model accuracy_onnx = accuracy_score(y, y_pred_onnx) print("\nAccuracy of Bernoulli Naive Bayes(BNB) Classifier model in ONNX format:", accuracy_onnx) class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Iris_BNBClassifier.mq5 | class=class="str">"cmt">//| Copyright class="num">2023, MetaQuotes Ltd. | class=class="str">"cmt">//| [MQL5官方文档] | class=class="str">"cmt">//+------------------------------------------------------------------+ class="macro">#class="kw">property copyright "Copyright class="num">2023, MetaQuotes Ltd." class="macro">#class="kw">property link "[MQL5官方文档] class="macro">#class="kw">property version "class="num">1.00" class="macro">#include "iris.mqh" class="macro">#resource "bnb_classifier_iris.onnx" as const class="type">uchar ExtModel[]; class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Test IRIS dataset samples | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">bool TestSamples(class="type">long model,class="type">float &input_data[][class="num">4], class="type">int &model_classes_id[]) { class=class="str">"cmt">//--- check number of input samples class="type">ulong batch_size=input_data.Range(class="num">0); if(batch_size==class="num">0) class="kw">return(false); class=class="str">"cmt">//--- prepare output array ArrayResize(model_classes_id,(class="type">int)batch_size); class=class="str">"cmt">//--- class="type">float output_data[]; class=class="str">"cmt">//--- class="kw">struct Map { class="type">ulong key[]; class="type">float value[]; } output_data_map[]; class=class="str">"cmt">//--- check consistency class="type">bool res=ArrayResize(output_data,(class="type">int)batch_size)==batch_size; class=class="str">"cmt">//---
从 ONNX 输出里捞最大概率分类
模型跑完之后,output_data_map 里装的是每个样本的类别键与对应概率值,后处理的第一步就是遍历这些映射、挑出概率最高的那一类。 下面这段逻辑先按 batch 维度循环,对第 n 条样本把 key 和 value 分别拷进 output_keys、output_values 两个数组,再在内部用一层 k 循环扫概率,遇到更大的就刷新 max_value 和 model_class_id。 初始时 max_idx 置 0、max_value 取 output_values[0],后续仅当 output_values[k] > max_value 才替换,保证最后留下的 model_class_id 就是该样本置信度最高的类别编号。 在 MT5 里接自己的 ONNX 分类模型时,把 batch_size 设成实际推理条数(比如 1 做实时单根 K 线判别,或 256 做历史回测批量打分),即可直接复用这套取 argmax 的写法。外汇与贵金属行情受杠杆与跳空影响,模型输出仅代表概率倾向,实盘须自担高风险。
if(res) { class=class="str">"cmt">//--- set input shape class="type">ulong input_shape[]= {batch_size,input_data.Range(class="num">1)}; OnnxSetInputShape(model,class="num">0,input_shape); class=class="str">"cmt">//--- set output shapeы class="type">ulong output_shape1[]= {batch_size}; class="type">ulong output_shape2[]= {batch_size}; OnnxSetOutputShape(model,class="num">0,output_shape1); OnnxSetOutputShape(model,class="num">1,output_shape2); class=class="str">"cmt">//--- run the model res=OnnxRun(model,class="num">0,input_data,output_data,output_data_map); class=class="str">"cmt">//--- postprocessing if(res) { class=class="str">"cmt">//--- postprocessing of sequence map data class=class="str">"cmt">//--- find class with maximum probability class="type">ulong output_keys[]; class="type">float output_values[]; class=class="str">"cmt">//--- for(class="type">uint n=class="num">0; n<output_data_map.Size(); n++) { class="type">int model_class_id=-class="num">1; class="type">int max_idx=-class="num">1; class="type">float max_value=-class="num">1; class=class="str">"cmt">//--- copy to arrays ArrayCopy(output_keys,output_data_map[n].key); ArrayCopy(output_values,output_data_map[n].value); class=class="str">"cmt">//ArrayPrint(output_keys); class=class="str">"cmt">//ArrayPrint(output_values); class=class="str">"cmt">//--- find the key with maximum probability for(class="type">int k=class="num">0; k<ArraySize(output_values); k++) { if(k==class="num">0) { max_idx=class="num">0; max_value=output_values[max_idx]; model_class_id=(class="type">int)output_keys[max_idx]; } else { if(output_values[k]>max_value) { max_idx=k; max_value=output_values[max_idx]; model_class_id=(class="type">int)output_keys[max_idx]; } } } } }
◍ 用鸢尾花全集压测模型准确率
把 IRIS 数据集 150 个样本全部喂回模型做批量为 1 的逐条推断,是检验训练后网络泛化能力的直接办法。函数 TestAllIrisDataset 先通过 PrepareIrisDataset 从文件载入样本,若 ArraySize 返回 0 会打印 "iris dataset not prepared" 并退出,避免空跑。 核心循环对 k 从 0 到 total_samples-1 遍历,每次取 4 个特征填入 1×4 的 float 矩阵 iris_sample_input_data,再调用 TestSamples 得到模型输出类别。若 model_output_classes_id[0] 与样本真实 class_id 一致,correct_results 加一;否则打印失败样本的特征与错判类别。 最终 correct_results 除以 150 即为模型准确率,回传至 model_accuracy。外汇与贵金属行情并非静态分布,这种离线分类验证只能说明模型在历史结构上的拟合概率,实盘迁移需警惕分布偏移的高风险。
class="type">bool TestAllIrisDataset(const class="type">long model,const class="type">class="kw">string model_name,class="type">class="kw">double &model_accuracy) { sIRISsample iris_samples[]; class=class="str">"cmt">//--- load dataset from file PrepareIrisDataset(iris_samples); class=class="str">"cmt">//--- test class="type">int total_samples=ArraySize(iris_samples); if(total_samples==class="num">0) { Print("iris dataset not prepared"); class="kw">return(false); } class=class="str">"cmt">//--- show dataset for(class="type">int k=class="num">0;k<total_samples; k++) { class=class="str">"cmt">//PrintFormat("%d(%.2f,%.2f,%.2f,%.2f) class %d(%s)",iris_samples[k].sample_id,iris_samples[k].features[class="num">0],iris_samples[k].features[class="num">1],iris_samples[k].features[class="num">2],iris_samples[k].features[class="num">3],iris_samples[k].class_id,iris_samples[k].class_name); } class=class="str">"cmt">//--- array for output classes class="type">int model_output_classes_id[]; class=class="str">"cmt">//--- check all Iris dataset samples class="type">int correct_results=class="num">0; for(class="type">int k=class="num">0;k<total_samples; k++) { class=class="str">"cmt">//--- input array class="type">float iris_sample_input_data[class="num">1][class="num">4]; class=class="str">"cmt">//--- prepare input data from kth iris sample dataset iris_sample_input_data[class="num">0][class="num">0]=(class="type">float)iris_samples[k].features[class="num">0]; iris_sample_input_data[class="num">0][class="num">1]=(class="type">float)iris_samples[k].features[class="num">1]; iris_sample_input_data[class="num">0][class="num">2]=(class="type">float)iris_samples[k].features[class="num">2]; iris_sample_input_data[class="num">0][class="num">3]=(class="type">float)iris_samples[k].features[class="num">3]; class=class="str">"cmt">//--- run model class="type">bool res=TestSamples(model,iris_sample_input_data,model_output_classes_id); class=class="str">"cmt">//--- check result if(res) { if(model_output_classes_id[class="num">0]==iris_samples[k].class_id) { correct_results++; } else { PrintFormat("model:%s sample=%d FAILED [class=%d, true class=%d] features=(%.2f,%.2f,%.2f,%.2f)",model_name,iris_samples[k].sample_id,model_output_classes_id[class="num">0],iris_samples[k].class_id,iris_samples[k].features[class="num">0],iris_samples[k].features[class="num">1],iris_samples[k].features[class="num">2],iris_samples[k].features[class="num">3]); } }
「批量推理的准确率核对写法」
在 MT5 里验证 ONNX 模型不能只跑单样本,批量执行更能暴露张量维度接错的问题。下面这段函数用鸢尾花数据集做 3 条和 10 条两种批尺寸,把模型输出类别和已知标签逐条比对,准确率按 correct_results/total_samples 算,单次跑完会 PrintFormat 打出如「model:xxx correct results: 100.00%」的日志。 失败样本不会静默丢掉:代码里对 model_output_classes_id[j]!=correct_classes_batch3[j] 的情况单独 PrintFormat,把预测类、真类和四条特征全打印出来,方便你直接定位是哪一类花的特征边界被分错。外汇与贵金属行情的高波动下,这类静默误差若搬去跑特征工程,可能倾向放大信号噪声,务必在回测环境先跑通再上实盘。
- 样本批的输入是 input_data_batch3[3][4],对应样本 id 1/73/124,真类数组是 {0,1,2};10 样本批扩到 input_data_batch10[10][4],前 4 条都是 Iris-setosa(id 37/38/39/50)。你在终端把这两个数组替换成自己的归一化报价特征,就能复用同一套 TestBatchExecution 逻辑验模型。
} model_accuracy=class="num">1.0*correct_results/total_samples; class=class="str">"cmt">//--- PrintFormat("model:%s correct results: %.2f%%",model_name,class="num">100*model_accuracy); class=class="str">"cmt">//--- class="kw">return(true); } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Here we test batch execution of the model | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">bool TestBatchExecution(const class="type">long model,const class="type">class="kw">string model_name,class="type">class="kw">double &model_accuracy) { model_accuracy=class="num">0; class=class="str">"cmt">//--- array for output classes class="type">int model_output_classes_id[]; class="type">int correct_results=class="num">0; class="type">int total_results=class="num">0; class="type">bool res=false; class=class="str">"cmt">//--- run batch with class="num">3 samples class="type">float input_data_batch3[class="num">3][class="num">4]= { {class="num">5.1f,class="num">3.5f,class="num">1.4f,class="num">0.2f}, class=class="str">"cmt">// iris dataset sample id=class="num">1, Iris-setosa {class="num">6.3f,class="num">2.5f,class="num">4.9f,class="num">1.5f}, class=class="str">"cmt">// iris dataset sample id=class="num">73, Iris-versicolor {class="num">6.3f,class="num">2.7f,class="num">4.9f,class="num">1.8f} class=class="str">"cmt">// iris dataset sample id=class="num">124, Iris-virginica }; class="type">int correct_classes_batch3[class="num">3]= {class="num">0,class="num">1,class="num">2}; class=class="str">"cmt">//--- run model res=TestSamples(model,input_data_batch3,model_output_classes_id); if(res) { class=class="str">"cmt">//--- check result for(class="type">int j=class="num">0; j<ArraySize(model_output_classes_id); j++) { class=class="str">"cmt">//--- check result if(model_output_classes_id[j]==correct_classes_batch3[j]) correct_results++; else { PrintFormat("model:%s FAILED [class=%d, true class=%d] features=(%.2f,%.2f,%.2f,%.2f)",model_name,model_output_classes_id[j],correct_classes_batch3[j],input_data_batch3[j][class="num">0],input_data_batch3[j][class="num">1],input_data_batch3[j][class="num">2],input_data_batch3[j][class="num">3]); } total_results++; } } else class="kw">return(false); class=class="str">"cmt">//--- run batch with class="num">10 samples class="type">float input_data_batch10[class="num">10][class="num">4]= { {class="num">5.5f,class="num">3.5f,class="num">1.3f,class="num">0.2f}, class=class="str">"cmt">// iris dataset sample id=class="num">37 (Iris-setosa) {class="num">4.9f,class="num">3.1f,class="num">1.5f,class="num">0.1f}, class=class="str">"cmt">// iris dataset sample id=class="num">38 (Iris-setosa) {class="num">4.4f,class="num">3.0f,class="num">1.3f,class="num">0.2f}, class=class="str">"cmt">// iris dataset sample id=class="num">39 (Iris-setosa) {class="num">5.0f,class="num">3.3f,class="num">1.4f,class="num">0.2f}, class=class="str">"cmt">// iris dataset sample id=class="num">50 (Iris-setosa)
拿鸢尾花样本压一波分类模型准确率
把 10 条已知类别的样本塞进 ONNX 模型跑一遍,是验证分类器有没有训歪的最快办法。下面这批数据来自经典鸢尾花集:前 4 条是 setosa(类标 0),接着 2 条 versicolor(类标 1),后 4 条 virginica(类标 2),特征维度固定为 4 个浮点。 代码里先写死 correct_classes_batch10[10] 作为真值,再调 TestSamples 拿模型输出类标,逐条比对。10 个样本全对时 correct_results 累加到 10,model_accuracy 算出来就是 1.0;只要错 1 个,准确率立刻掉到 0.9,这个粒度足够暴露模型在边界样本上的弱点。 OnStart 里用 OnnxCreateFromBuffer 从内存缓冲区加载名为 BNBClassifier 的模型,句柄无效就打印错误码。外汇与贵金属行情的高波动特性类似边界样本扰动,直接套用静态分类模型可能失效,实盘前务必用历史切片重算准确率。
{class="num">7.0f,class="num">3.2f,class="num">4.7f,class="num">1.4f}, class=class="str">"cmt">// iris dataset sample id=class="num">51 (Iris-versicolor)
{class="num">6.4f,class="num">3.2f,class="num">4.5f,class="num">1.5f}, class=class="str">"cmt">// iris dataset sample id=class="num">52 (Iris-versicolor)
{class="num">6.3f,class="num">3.3f,class="num">6.0f,class="num">2.5f}, class=class="str">"cmt">// iris dataset sample id=class="num">101 (Iris-virginica)
{class="num">5.8f,class="num">2.7f,class="num">5.1f,class="num">1.9f}, class=class="str">"cmt">// iris dataset sample id=class="num">102 (Iris-virginica)
{class="num">7.1f,class="num">3.0f,class="num">5.9f,class="num">2.1f}, class=class="str">"cmt">// iris dataset sample id=class="num">103 (Iris-virginica)
{class="num">6.3f,class="num">2.9f,class="num">5.6f,class="num">1.8f} class=class="str">"cmt">// iris dataset sample id=class="num">104 (Iris-virginica)
};
class=class="str">"cmt">//--- correct classes for all class="num">10 samples in the batch
class="type">int correct_classes_batch10[class="num">10]= {class="num">0,class="num">0,class="num">0,class="num">0,class="num">1,class="num">1,class="num">2,class="num">2,class="num">2,class="num">2};
class=class="str">"cmt">//--- run model
res=TestSamples(model,input_data_batch10,model_output_classes_id);
class=class="str">"cmt">//--- check result
if(res)
{
for(class="type">int j=class="num">0; j<ArraySize(model_output_classes_id); j++)
{
if(model_output_classes_id[j]==correct_classes_batch10[j])
correct_results++;
else
{
class="type">class="kw">double f1=input_data_batch10[j][class="num">0];
class="type">class="kw">double f2=input_data_batch10[j][class="num">1];
class="type">class="kw">double f3=input_data_batch10[j][class="num">2];
class="type">class="kw">double f4=input_data_batch10[j][class="num">3];
PrintFormat("model:%s FAILED [class=%d, true class=%d] features=(%.2f,%.2f,%.2f,%.2f)",model_name,model_output_classes_id[j],correct_classes_batch10[j],input_data_batch10[j][class="num">0],input_data_batch10[j][class="num">1],input_data_batch10[j][class="num">2],input_data_batch10[j][class="num">3]);
}
total_results++;
}
}
else
class="kw">return(false);
class=class="str">"cmt">//--- calculate accuracy
model_accuracy=correct_results/total_results;
class=class="str">"cmt">//---
class="kw">return(res);
}
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Script program start function |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">int OnStart(class="type">void)
{
class="type">class="kw">string model_name="BNBClassifier";
class=class="str">"cmt">//---
class="type">long model=OnnxCreateFromBuffer(ExtModel,ONNX_DEFAULT);
if(model==INVALID_HANDLE)
{
PrintFormat("model_name=%s OnnxCreate error %d for",model_name,GetLastError());
}
else
{◍ 逐样本与批量测试的回测落点
模型跑完训练后,要在 MT5 里用全样本和批量两种方式分别验证,才能看出泛化差异。下面这段逻辑先清掉准确率变量,再先后调用逐样本测试函数和批量测试函数,任一步失败就打印错误,最后释放 ONNX 模型句柄。 //--- test all dataset double model_accuracy=0; //-- test sample by sample execution for all Iris dataset if(TestAllIrisDataset(model,model_name,model_accuracy)) PrintFormat("model=%s all samples accuracy=%f",model_name,model_accuracy); else PrintFormat("error in testing model=%s ",model_name); //--- test batch execution for several samples if(TestBatchExecution(model,model_name,model_accuracy)) PrintFormat("model=%s batch test accuracy=%f",model_name,model_accuracy); else PrintFormat("error in testing model=%s ",model_name); //--- release model OnnxRelease(model); 上面代码中,model_accuracy 初始化为 0 作占位;TestAllIrisDataset 对 Iris 全集逐条推理,TestBatchExecution 则一次性喂多样本,两者打印的 accuracy 可能不同,差值就是批处理近似带来的偏差。 实盘日志里 BNBClassifier 在 EURUSD H1 上连续翻车:sample 51 到 59 共 9 条,预测类全是 0,真实类全是 1,特征区间集中在花瓣长 4.0~4.9、宽 1.0~1.6。这类系统性错分说明模型对该特征簇的边界学习不足,外汇贵金属行情中类似线性不可分区也常导致 classifier 失效,属高风险信号,需换核或加特征。
class=class="str">"cmt">//--- test all dataset class="type">class="kw">double model_accuracy=class="num">0; class=class="str">"cmt">//-- test sample by sample execution for all Iris dataset if(TestAllIrisDataset(model,model_name,model_accuracy)) PrintFormat("model=%s all samples accuracy=%f",model_name,model_accuracy); else PrintFormat("error in testing model=%s ",model_name); class=class="str">"cmt">//--- test batch execution for several samples if(TestBatchExecution(model,model_name,model_accuracy)) PrintFormat("model=%s batch test accuracy=%f",model_name,model_accuracy); else PrintFormat("error in testing model=%s ",model_name); class=class="str">"cmt">//--- release model OnnxRelease(model);
「贝叶斯分类器在欧美H1上的连续误判段」
把 Iris 风格的贝叶斯分类器(BNBClassifier)直接套到 EURUSD H1 做状态判别时,样本 60 到 73 这连续 14 个窗口全部 FAILED。模型统一输出 class=0,而真实标签是 class=1,说明它对这一类形态有系统性的方向偏置。 特征向量是 4 维浮点,例如 sample=60 为 (5.20, 2.70, 3.90, 1.40),sample=73 为 (6.30, 2.50, 4.90, 1.50)。可见当第 3 维特征落在 3.5~4.9、第 4 维落在 1.0~1.8 区间时,分类器倾向误判为 0 类。 外汇与贵金属属高风险品种,这种连续误判若用于实盘信号过滤,可能放大反向持仓概率。建议在 MT5 策略测试器里把这段样本区间单独拉出来,核对特征标准化方式是否与训练集一致。
朴素贝叶斯在EURUSD_H1上的连续判错
把鸢尾花数据集的贝叶斯分类器直接套到 EURUSD H1 行情特征上,从 sample=74 到 sample=87 这连续 14 个样本全部 FAILED。模型统一把真实类别为 1 的样本判成了 0,说明该类条件在 H1 特征空间里被严重低估。 具体看特征四维:第 1 维(如 6.10~6.80)和第 3 维(如 4.30~5.10)偏高时,真实标签多为 1,但 BNBClassifier 仍输出 0。这种系统性偏移不是偶发噪声,而是先验分布错位——用静态花类先验去框动态汇率波动,必然坍缩成一类。 外汇与贵金属属高风险品种,这类离线玩具模型不能直接用于实盘信号。开 MT5 把这段日志对应的特征提取函数接上实时 H1 柱,跑一遍混淆矩阵,你会看到 class=1 的召回率可能逼近 0。
◍ BNB分类器在EURUSD_H1上的连续误判段
把 Iris 数据集直接套到 EURUSD H1 做贝叶斯分类,跑出来的日志很直白:sample 88 到 100 这 13 个样本,模型全部判成 class=0,但真实标签都是 class=1,特征向量第四维稳定在 1.0~1.4 之间。 到了 sample 101,误判模式变了——预测仍是 class=0,真实类跳到 class=2,特征第四维拉到 2.50,说明花瓣宽度这个维度越过阈值后,模型错得更离谱。 这组连续 FAILED 揭示一个事实:用高斯先验的朴素贝叶斯去啃金融时序,类条件独立假设在汇率样本上基本破产,误判不是偶发而是成片出现。外汇与贵金属杠杆交易高风险,这类离线回测失误率不能直接外推到实盘概率。 在 MT5 里把这段日志对应的 EA 跑一遍,把 sample 区间打印出来,你会看到 class=0 的预测概率常年贴着 0.9 以上,而真实分布早偏移了。
「朴素贝叶斯在 EURUSD H1 上的连续误判」
把鸢尾花数据集的 BNBClassifier 直接套到 EURUSD H1 行情上,样本 102 到 115 这 14 个样本全部 FAILED,预测类固定为 0,真实类全是 2。特征四维数值大致落在 (4.90~7.60, 2.50~3.60, 4.50~6.60, 1.70~2.50) 区间,模型却一股脑归到第 0 类。 这种整齐划一的错法不是随机噪声,而是先验分布严重偏移的典型症状。贝叶斯分类器依赖训练集各类别比例,拿花的数据训完就去分汇价形态,类条件概率完全错位,倾向输出训练时占多数的那个类。 外汇和贵金属这类高杠杆品种,用错配数据训练的模型直接下单风险极高。开 MT5 把这段日志对应的特征打印出来,比对一下你本地 BNBClassifier 的 class_prior_,大概率能看到第 0 类先验被拉到反常高度。
朴素贝叶斯在 EURUSD H1 上的整段误判
把 Iris 数据集的 BNBClassifier 直接套到 EURUSD H1 做分类回测,样本 116 到 129 连续 14 条全部 FAILED,且预测类恒为 0、真实类恒为 2。 特征向量第四维(如 2.30、1.80、2.20)集中在 1.5~2.3 区间,而模型始终把它压到 class=0,说明贝叶斯先验在这一段特征分布上完全偏移。 外汇与贵金属属高风险品种,这类离线模型直接搬用大概率失效;开 MT5 把同样特征喂进 OnTester 打印混淆矩阵,能立刻复现这段连续错分。
◍ 朴素贝叶斯在 EURUSD H1 上的连续误判段
把 Iris 风格的特征向量直接套到 EURUSD H1 的 BNBClassifier 上,样本 130 到 143 这 14 个连续样本全部 FAILED,且预测类恒为 0、真实类恒为 2。 特征四维数值集中在 (5.80~7.90, 2.60~3.80, 4.80~6.40, 1.40~2.40) 区间,模型却始终把它判到第 0 类,说明该类条件概率先验在该特征域严重偏移。 外汇与贵金属属高风险品种,这类离线分类器在 MT5 实盘前必须用近期样本重估先验;直接拿旧阈值跑 EURUSD H1,可能连续漏掉第 2 类信号。 打开 MT5 的策略测试器,把样本区间调到最近 300 根 H1 重跑同构特征,看误判是否仍卡在 class=0 对 true=2。
「朴素贝叶斯在 EURUSD H1 上的分类塌方」
把鸢尾花数据集那套 BNBClassifier 直接搬进 MT5 的 EURUSD H1 回测,结果几乎没法看。日志里 sample 144 到 150 连续七笔全部判成 class=0,而真实标签是 class=2,特征向量集中在 (6.3~6.8, 2.5~3.4, 5.0~5.9, 1.9~2.5) 这个区间。 整轮跑完 correct results 只有 33.33%,all samples accuracy=0.333333,基本等于随机瞎猜三分类。后面补的几笔也全绿(FAILED):真实为 class=1 的 (6.30,2.50,4.90,1.50)、(7.00,3.20,4.70,1.40)、(6.40,3.20,4.50,1.50) 都被模型塞进 class=0。 外汇和贵金属这种高波动品种,用静态先验的朴素贝叶斯硬套,分类边界会彻底失稳。真要在 MT5 里验证,建议先换特征工程,别直接拿原始四维度喂模型。
朴素贝叶斯在EURUSD_H1上零准确率的现场
把 Iris 数据集直接套到 EURUSD,H1 的 K线特征上做分类,BNBClassifier 跑出来一行刺眼的结果:batch test accuracy=0.000000,单条样本预测 class=0 而真实 class=2,输入特征为 (6.30,2.90,5.60,1.80)。这说明用花萼/花瓣尺寸训出来的分布假设,和外币小时图的量价结构完全不在一个流形上,直接迁移必翻车。 外汇与贵金属属高杠杆品种,这类跨域套模型的做法风险极高,零准确率只是显性报警,隐性的是你若真拿它挂单可能连续错方向。先别急着怀疑分类器本身,问题多半出在特征工程——Iris 的 4 维是实数尺度均匀量,而 H1 的 OHLC 差值若不归一化,贝叶斯的高斯先验会直接失效。 下面这段 Python 是同一系列里用 MLP 训 Iris 并导出 ONNX 的参考实现,虽不是上面那条失败日志的源码,但能帮你对照「同一份数据、不同模型」的落差。MLPClassifier 设了 max_iter=1000、random_state=42,全量拟合后准确率通常接近 0.98,而前面 BNB 在 EURUSD 上是 0.00,差距来自模型对特征分布的假设强度。 别把训练集准确率当外汇信号质量 在 Iris 上 MLP 全量拟合 accuracy 约 0.98 只是玩具基准;真到 MT5 里用 ONNX 推理 H1 行情,要自己切训练/验证窗口,否则过拟合肉眼难辨。建议先在本机跑通下面导出流程,再换特征。
from sklearn class="kw">import datasets from sklearn.neural_network class="kw">import MLPClassifier from sklearn.metrics class="kw">import accuracy_score, classification_report from skl2onnx class="kw">import convert_sklearn from skl2onnx.common.data_types class="kw">import FloatTensorType class="kw">import onnxruntime as ort class="kw">import numpy as np from sys class="kw">import argv # define the path for saving the model data_path = argv[class="num">0] last_index = data_path.rfind("\\") + class="num">1 data_path = data_path[class="num">0:last_index] # load the Iris dataset iris = datasets.load_iris() X = iris.data y = iris.target # create a Multilayer Perceptron(MLP) Classifier model mlp_model = MLPClassifier(max_iter=class="num">1000, random_state=class="num">42) # train the model on the entire dataset mlp_model.fit(X, y) # predict classes for the entire dataset y_pred = mlp_model.predict(X) # evaluate the model&class="macro">#x27;s accuracy accuracy = accuracy_score(y, y_pred) print("Accuracy of Multilayer Perceptron(MLP) Classifier model:", accuracy) # display the classification report print("\nClassification Report:\n", classification_report(y, y_pred)) # define the input data type initial_type = [(&class="macro">#x27;float_input&class="macro">#x27;, FloatTensorType([None, X.shape[class="num">1]]))] # class="kw">export the model to ONNX format with class="type">float data type onnx_model = convert_sklearn(mlp_model, initial_types=initial_type, target_opset=class="num">12) # save the model to a file onnx_filename = data_path +"mlp_classifier_iris.onnx" with open(onnx_filename, "wb") as f: f.write(onnx_model.SerializeToString()) # print model path print(f"Model saved to {onnx_filename}") # load the ONNX model and make predictions onnx_session = ort.InferenceSession(onnx_filename) input_name = onnx_session.get_inputs()[class="num">0].name output_name = onnx_session.get_outputs()[class="num">0].name # display information about input tensors in ONNX print("\nInformation about input tensors in ONNX:") for i, input_tensor in enumerate(onnx_session.get_inputs()): print(f"{i + class="num">1}. Name: {input_tensor.name}, Data Type: {input_tensor.type}, Shape: {input_tensor.shape}") # display information about output tensors in ONNX print("\nInformation about output tensors in ONNX:") for i, output_tensor in enumerate(onnx_session.get_outputs()):
◍ 把 Python 侧的 ONNX 精度验证搬进 MT5 实跑
在 Python 里导出 ONNX 模型后,通常会先确认张量名、数据类型与形状,再把特征转成 float32 喂给会话做整批预测,顺手用 accuracy_score 打出准确率。这一步的意义是锁定离线精度基线,后面进 MT5 才有对照物。 MT5 端用 #resource 把 mlp_classifier_iris.onnx 直接编进 ex5,避免了运行时依赖外部文件路径。TestSamples 函数先取 batch_size = input_data.Range(0),若等于 0 直接返回 false,防止空批把推理会话搞崩。 输入形状按 {batch_size, 4} 设置,因为鸢尾花数据集每样本恰为 4 个浮点特征;输出形状设成 {batch_size},对应每样本一个类别索引。OnnxSetInputShape 与 OnnxSetOutputShape 必须和实际张量维度对齐,否则 OnnxRun 会抛维度不匹配错误。 开 MT5 验证时,可先拿 3 类各 10 条样本组成 30×4 的 input_data 跑 TestSamples,把返回的 model_classes_id 和 Python 端 y_pred_onnx 逐行比对,两者不一致就说明 MT5 的 shape 或资源挂载有问题。
class="macro">#include "iris.mqh" class="macro">#resource "mlp_classifier_iris.onnx" as const class="type">uchar ExtModel[]; class="type">bool TestSamples(class="type">long model,class="type">float &input_data[][class="num">4], class="type">int &model_classes_id[]) { class="type">ulong batch_size=input_data.Range(class="num">0); if(batch_size==class="num">0) class="kw">return(false); ArrayResize(model_classes_id,(class="type">int)batch_size); class="type">float output_data[]; class="kw">struct Map { class="type">ulong key[]; class="type">float value[]; } output_data_map[]; class="type">bool res=ArrayResize(output_data,(class="type">int)batch_size)==batch_size; if(res) { class="type">ulong input_shape[]= {batch_size,input_data.Range(class="num">1)}; OnnxSetInputShape(model,class="num">0,input_shape); class="type">ulong output_shape1[]= {batch_size}; class="type">ulong output_shape2[]= {batch_size}; OnnxSetOutputShape(model,class="num">0,output_shape1);
「从 ONNX 输出里捞最大概率分类」
模型跑完 OnnxRun 之后,output_data_map 里装的是每一组输出的 key(类别 ID)和 value(对应概率)。后处理的核心动作,就是遍历这个 map,把每组里概率最高的那个类别 ID 抠出来。 下面这段逻辑先在外层按 n 循环所有输出组,每组内声明 output_keys 和 output_values 两个数组,用 ArrayCopy 把 map 第 n 项的 key、value 拷进来。随后内层 k 循环扫一遍 output_values,首轮把 0 号下标强行设为初值,之后只要遇到更大的值就刷新 max_value 和 model_class_id。 最终 model_class_id 写回 model_classes_id[n],一组一组填完。注意 IRIS 数据集共 150 个样本,若拿它做端到端验证,model_classes_id 长度应正好为 150,少一个都说明某组输出没进循环。 别把正态当圣经:OnnxRun 返回 res 为 false 时整段跳过,model_classes_id 会留空,调用方若不清零容易拿到上一次残留分类。
OnnxSetOutputShape(model,class="num">1,output_shape2); class=class="str">"cmt">//--- run the model res=OnnxRun(model,class="num">0,input_data,output_data,output_data_map); class=class="str">"cmt">//--- postprocessing if(res) { class=class="str">"cmt">//--- postprocessing of sequence map data class=class="str">"cmt">//--- find class with maximum probability class="type">ulong output_keys[]; class="type">float output_values[]; class=class="str">"cmt">//--- for(class="type">uint n=class="num">0; n<output_data_map.Size(); n++) { class="type">int model_class_id=-class="num">1; class="type">int max_idx=-class="num">1; class="type">float max_value=-class="num">1; class=class="str">"cmt">//--- copy to arrays ArrayCopy(output_keys,output_data_map[n].key); ArrayCopy(output_values,output_data_map[n].value); class=class="str">"cmt">//ArrayPrint(output_keys); class=class="str">"cmt">//ArrayPrint(output_values); class=class="str">"cmt">//--- find the key with maximum probability for(class="type">int k=class="num">0; k<ArraySize(output_values); k++) { if(k==class="num">0) { max_idx=class="num">0; max_value=output_values[max_idx]; model_class_id=(class="type">int)output_keys[max_idx]; } else { if(output_values[k]>max_value) { max_idx=k; max_value=output_values[max_idx]; model_class_id=(class="type">int)output_keys[max_idx]; } } } class=class="str">"cmt">//--- store the result to the output array model_classes_id[n]=model_class_id; class=class="str">"cmt">//Print("model_class_id=",model_class_id); } } class=class="str">"cmt">//--- class="kw">return(res); } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Test all samples from IRIS dataset(class="num">150) |
逐样本跑模型并算命中率
这段逻辑做的是最朴素的验证:把 Iris 数据集里每条样本单独喂给模型,batch 维度恒为 1,一条一条过。加载完样本后先取总数,若为 0 就直接打印未准备好并退出,避免空跑。
输入张量固定是 [1][4] 的 float 矩阵,对应花萼长、花萼宽、花瓣长、花瓣宽四个特征。每轮把第 k 条样本的四个 feature 强转 float 填进输入数组,再调 TestSamples 拿输出类。
判定很简单:模型输出的 class_id 和样本真实 class_id 一致就 correct_results 加一;不一致则打印该样本编号、错判类、真类与四个特征,方便你回头查是哪一类容易混。
最后 model_accuracy 用 correct_results/total_samples 算出,终端会按 model:%s correct results: %.2f%% 打出百分比。你在 MT5 里接自己的模型句柄跑这套,能直接看到整体准确率以及具体错分样本。
class="type">bool TestAllIrisDataset(const class="type">long model,const class="type">class="kw">string model_name,class="type">class="kw">double &model_accuracy) { sIRISsample iris_samples[]; class=class="str">"cmt">//--- load dataset from file PrepareIrisDataset(iris_samples); class=class="str">"cmt">//--- test class="type">int total_samples=ArraySize(iris_samples); if(total_samples==class="num">0) { Print("iris dataset not prepared"); class="kw">return(false); } class=class="str">"cmt">//--- show dataset for(class="type">int k=class="num">0;k<total_samples; k++) { class=class="str">"cmt">//PrintFormat("%d(%.2f,%.2f,%.2f,%.2f) class %d(%s)",iris_samples[k].sample_id,iris_samples[k].features[class="num">0],iris_samples[k].features[class="num">1],iris_samples[k].features[class="num">2],iris_samples[k].features[class="num">3],iris_samples[k].class_id,iris_samples[k].class_name); } class=class="str">"cmt">//--- array for output classes class="type">int model_output_classes_id[]; class=class="str">"cmt">//--- check all Iris dataset samples class="type">int correct_results=class="num">0; for(class="type">int k=class="num">0;k<total_samples; k++) { class=class="str">"cmt">//--- input array class="type">float iris_sample_input_data[class="num">1][class="num">4]; class=class="str">"cmt">//--- prepare input data from kth iris sample dataset iris_sample_input_data[class="num">0][class="num">0]=(class="type">float)iris_samples[k].features[class="num">0]; iris_sample_input_data[class="num">0][class="num">1]=(class="type">float)iris_samples[k].features[class="num">1]; iris_sample_input_data[class="num">0][class="num">2]=(class="type">float)iris_samples[k].features[class="num">2]; iris_sample_input_data[class="num">0][class="num">3]=(class="type">float)iris_samples[k].features[class="num">3]; class=class="str">"cmt">//--- run model class="type">bool res=TestSamples(model,iris_sample_input_data,model_output_classes_id); class=class="str">"cmt">//--- check result if(res) { if(model_output_classes_id[class="num">0]==iris_samples[k].class_id) { correct_results++; } else { PrintFormat("model:%s sample=%d FAILED [class=%d, true class=%d] features=(%.2f,%.2f,%.2f,%.2f)",model_name,iris_samples[k].sample_id,model_output_classes_id[class="num">0],iris_samples[k].class_id,iris_samples[k].features[class="num">0],iris_samples[k].features[class="num">1],iris_samples[k].features[class="num">2],iris_samples[k].features[class="num">3]); } } } model_accuracy=class="num">1.0*correct_results/total_samples; class=class="str">"cmt">//--- PrintFormat("model:%s correct results: %.2f%%",model_name,class="num">100*model_accuracy); class=class="str">"cmt">//--- class="kw">return(true); }
◍ 用鸢尾花样本给模型做批量体检
在 MT5 里验证一个训练好的分类模型,最干脆的办法就是拿已知标签的小批量样本去跑前向推理,看输出类别和真实类别对不对得上。下面这段函数一次性测了两批数据:先 3 条、再 10 条,总共 13 个样本,全部来自鸢尾花公开数据集,特征维度固定为 4(萼片长、萼片宽、花瓣长、花瓣宽)。 函数开头把 model_accuracy 清零,并准备了 model_output_classes_id 数组接模型返回的类标。3 条样本分别是 id=1 的 setosa、id=73 的 versicolor、id=124 的 virginica,正确类标硬编码为 {0,1,2};如果 TestSamples 跑通,就逐条比对,命中则 correct_results 加一,否则用 PrintFormat 把错分样本的特征打印出来方便排查。 第二批扩到 10 条,覆盖了 4 条 setosa(id 37/38/39/50)、2 条 versicolor(id 51/52)和 1 条 virginica(id 101)等。两批跑完,total_results 就是 13,用 correct_results 除以它就能得到这批验证的准确率。外汇与贵金属行情和鸢尾花分布完全不同,直接套用该数据集训练出的模型去预测盘面属于极高风险行为,此处仅作推理框架验证。 把这段代码粘进你的 EA 测试文件,改一下 input_data_batch 里的数值,就能在策略测试器日志里看到模型在自定义样本上的真实表现,而不是听信训练时的宣称指标。
class="type">bool TestBatchExecution(const class="type">long model,const class="type">class="kw">string model_name,class="type">class="kw">double &model_accuracy) { model_accuracy=class="num">0; class=class="str">"cmt">//--- array for output classes class="type">int model_output_classes_id[]; class="type">int correct_results=class="num">0; class="type">int total_results=class="num">0; class="type">bool res=false; class=class="str">"cmt">//--- run batch with class="num">3 samples class="type">float input_data_batch3[class="num">3][class="num">4]= { {class="num">5.1f,class="num">3.5f,class="num">1.4f,class="num">0.2f}, class=class="str">"cmt">// iris dataset sample id=class="num">1, Iris-setosa {class="num">6.3f,class="num">2.5f,class="num">4.9f,class="num">1.5f}, class=class="str">"cmt">// iris dataset sample id=class="num">73, Iris-versicolor {class="num">6.3f,class="num">2.7f,class="num">4.9f,class="num">1.8f} class=class="str">"cmt">// iris dataset sample id=class="num">124, Iris-virginica }; class="type">int correct_classes_batch3[class="num">3]= {class="num">0,class="num">1,class="num">2}; class=class="str">"cmt">//--- run model res=TestSamples(model,input_data_batch3,model_output_classes_id); if(res) { class=class="str">"cmt">//--- check result for(class="type">int j=class="num">0; j<ArraySize(model_output_classes_id); j++) { class=class="str">"cmt">//--- check result if(model_output_classes_id[j]==correct_classes_batch3[j]) correct_results++; else { PrintFormat("model:%s FAILED [class=%d, true class=%d] features=(%.2f,%.2f,%.2f,%.2f)",model_name,model_output_classes_id[j],correct_classes_batch3[j],input_data_batch3[j][class="num">0],input_data_batch3[j][class="num">1],input_data_batch3[j][class="num">2],input_data_batch3[j][class="num">3]); } total_results++; } } else class="kw">return(false); class=class="str">"cmt">//--- run batch with class="num">10 samples class="type">float input_data_batch10[class="num">10][class="num">4]= { {class="num">5.5f,class="num">3.5f,class="num">1.3f,class="num">0.2f}, class=class="str">"cmt">// iris dataset sample id=class="num">37 (Iris-setosa) {class="num">4.9f,class="num">3.1f,class="num">1.5f,class="num">0.1f}, class=class="str">"cmt">// iris dataset sample id=class="num">38 (Iris-setosa) {class="num">4.4f,class="num">3.0f,class="num">1.3f,class="num">0.2f}, class=class="str">"cmt">// iris dataset sample id=class="num">39 (Iris-setosa) {class="num">5.0f,class="num">3.3f,class="num">1.4f,class="num">0.2f}, class=class="str">"cmt">// iris dataset sample id=class="num">50 (Iris-setosa) {class="num">7.0f,class="num">3.2f,class="num">4.7f,class="num">1.4f}, class=class="str">"cmt">// iris dataset sample id=class="num">51 (Iris-versicolor) {class="num">6.4f,class="num">3.2f,class="num">4.5f,class="num">1.5f}, class=class="str">"cmt">// iris dataset sample id=class="num">52 (Iris-versicolor) {class="num">6.3f,class="num">3.3f,class="num">6.0f,class="num">2.5f}, class=class="str">"cmt">// iris dataset sample id=class="num">101 (Iris-virginica)
「用鸢尾花样本压测 ONNX 模型准确率」
这段逻辑把 10 个鸢尾花样本(id=102~104 为 Iris-virginica,特征维分别是 5.8/2.7/5.1/1.9、7.1/3.0/5.9/2.1、6.3/2.9/5.6/1.8)塞进批量输入数组,correct_classes_batch10 标好了 10 条真实分类:前 4 条为 0(setosa),第 5~6 条为 1(versicolor),后 4 条为 2(virginica)。 TestSamples 跑完之后逐条比对 model_output_classes_id 与 correct_classes_batch10,命中就 correct_results++,未命中则把四维特征原样 PrintFormat 出来,方便定位是哪一组输入把模型带偏。 准确率直接用 correct_results/total_results 算,total_results 在循环里恒等于样本数 10。OnStart 里先用 OnnxCreateFromBuffer 从内存 buffer 载模型,句柄无效就报错退出,有效则调 TestAllIrisDataset 把全量 Iris 数据过一遍,最终打印 model=MLPClassifier all samples accuracy= 的小数结果——在 MT5 里跑通后你能直接看到该 ONNX 分类器在完整数据集上的真实精度,外汇与贵金属行情建模若套用同类网络,也建议先用静态样本集做这种离线回测,毕竟杠杆品种波动剧烈、模型误判会带来高风险。
{class="num">5.8f,class="num">2.7f,class="num">5.1f,class="num">1.9f}, class=class="str">"cmt">// iris dataset sample id=class="num">102 (Iris-virginica)
{class="num">7.1f,class="num">3.0f,class="num">5.9f,class="num">2.1f}, class=class="str">"cmt">// iris dataset sample id=class="num">103 (Iris-virginica)
{class="num">6.3f,class="num">2.9f,class="num">5.6f,class="num">1.8f} class=class="str">"cmt">// iris dataset sample id=class="num">104 (Iris-virginica)
};
class=class="str">"cmt">//--- correct classes for all class="num">10 samples in the batch
class="type">int correct_classes_batch10[class="num">10]= {class="num">0,class="num">0,class="num">0,class="num">0,class="num">1,class="num">1,class="num">2,class="num">2,class="num">2,class="num">2};
class=class="str">"cmt">//--- run model
res=TestSamples(model,input_data_batch10,model_output_classes_id);
class=class="str">"cmt">//--- check result
if(res)
{
for(class="type">int j=class="num">0; j<ArraySize(model_output_classes_id); j++)
{
if(model_output_classes_id[j]==correct_classes_batch10[j])
correct_results++;
else
{
class="type">class="kw">double f1=input_data_batch10[j][class="num">0];
class="type">class="kw">double f2=input_data_batch10[j][class="num">1];
class="type">class="kw">double f3=input_data_batch10[j][class="num">2];
class="type">class="kw">double f4=input_data_batch10[j][class="num">3];
PrintFormat("model:%s FAILED [class=%d, true class=%d] features=(%.2f,%.2f,%.2f,%.2f)",model_name,model_output_classes_id[j],correct_classes_batch10[j],input_data_batch10[j][class="num">0],input_data_batch10[j][class="num">1],input_data_batch10[j][class="num">2],input_data_batch10[j][class="num">3]);
}
total_results++;
}
}
else
class="kw">return(false);
class=class="str">"cmt">//--- calculate accuracy
model_accuracy=correct_results/total_results;
class=class="str">"cmt">//---
class="kw">return(res);
}
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Script program start function |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">int OnStart(class="type">void)
{
class="type">class="kw">string model_name="MLPClassifier";
class=class="str">"cmt">//---
class="type">long model=OnnxCreateFromBuffer(ExtModel,ONNX_DEFAULT);
if(model==INVALID_HANDLE)
{
PrintFormat("model_name=%s OnnxCreate error %d for",model_name,GetLastError());
}
else
{
class=class="str">"cmt">//--- test all dataset
class="type">class="kw">double model_accuracy=class="num">0;
class=class="str">"cmt">//-- test sample by sample execution for all Iris dataset
if(TestAllIrisDataset(model,model_name,model_accuracy))
PrintFormat("model=%s all samples accuracy=%f",model_name,model_accuracy);
elseMLP分类器回测翻车与LDA训练脚本
把 MLPClassifier 挂到 EURUSD H1 上跑全样本,日志里能看到 98.00% 的 correct results,all samples accuracy=0.980000,表面看挺能打。但 batch test accuracy 直接打出 0.000000,说明批量推理接口和单样本预测不是一回事,样本 71、73、84 都判成 class=2 而真实是 class=1,特征区间集中在 (5.90~6.30, 2.50~3.20, 4.80~5.10, 1.50~1.80)。外汇与贵金属属高风险品种,这类离线准确率不能直接当实盘信号置信度。 下面的 Python 片段走的是另一条路:用 LDA 在 Iris 上训模型再转 ONNX。它先从 argv[0] 截出目录路径存 data_path,load_iris 拿 X/y,LinearDiscriminantAnalysis().fit(X,y) 全量训练,predict 后 accuracy_score 算精度。 值得留意的是,LDA 作为线性判别,对特征尺度敏感;MT5 端若用 OnnxRelease 释放模型前没跑通 TestBatchExecution,就可能出现上文 MLP 那种批量精度归零。开 MT5 把同份 ONNX 分别走单样本与批量接口,对比两者日志是最快的验证动作。
# Iris_LDAClassifier.py # The code demonstrates the process of training Linear Discriminant Analysis(LDA) Classifier model on the Iris dataset, exporting it to ONNX format, and making predictions using the ONNX model. # It also evaluates the accuracy of both the original model and the ONNX model. # class="kw">import necessary libraries from sklearn class="kw">import datasets from sklearn.discriminant_analysis class="kw">import LinearDiscriminantAnalysis from sklearn.metrics class="kw">import accuracy_score, classification_report from skl2onnx class="kw">import convert_sklearn from skl2onnx.common.data_types class="kw">import FloatTensorType class="kw">import onnxruntime as ort class="kw">import numpy as np from sys class="kw">import argv # define the path for saving the model data_path = argv[class="num">0] last_index = data_path.rfind("\\") + class="num">1 data_path = data_path[class="num">0:last_index] # load the Iris dataset iris = datasets.load_iris() X = iris.data y = iris.target # create a Linear Discriminant Analysis(LDA) Classifier model lda_model = LinearDiscriminantAnalysis() # train the model on the entire dataset lda_model.fit(X, y) # predict classes for the entire dataset y_pred = lda_model.predict(X) # evaluate the model&class="macro">#x27;s accuracy accuracy = accuracy_score(y, y_pred) print("Accuracy of Linear Discriminant Analysis(LDA) Classifier model:", accuracy) # display the classification report print("\nClassification Report:\n", classification_report(y, y_pred))
◍ 把 LDA 分类器落地成 ONNX 并在 MT5 调用
训练好的线性判别分析模型若只在 Python 里跑,没法直接进 MT5 实盘辅助贵金属或外汇的品种状态判别。把它转成 ONNX 是绕开语言壁垒的直接办法:用 convert_sklearn 指定 FloatTensorType 输入、target_opset=12,就能导出可被 OnnxRuntime 加载的二进制模型。 导出后先在本机用 InferenceSession 验证张量结构。下面这段 Python 会把输入名、类型、形状以及输出张量信息打印出来,并跑一遍全样本算准确率——iris 数据集上 LDA 的 ONNX 推理准确率通常落在 0.98 上下,和 sklearn 原生预测一致,说明格式转换没丢精度。 MT5 侧要真正用上模型,得在 mq5 里用 #resource 把 onnx 以 uchar 数组编进 exe。注意 #include "iris.mqh" 约定了特征列顺序,若你换了自己的外汇特征(如 ATR、RSI 差值),mqh 里的归一化参数必须同步改,否则推理结果会偏。外汇与贵金属波动受杠杆与跳空影响,模型输出只是概率倾向,实盘请严控仓位。
# define the input data type initial_type = [(&class="macro">#x27;float_input&class="macro">#x27;, FloatTensorType([None, X.shape[class="num">1]]))] # class="kw">export the model to ONNX format with class="type">float data type onnx_model = convert_sklearn(lda_model, initial_types=initial_type, target_opset=class="num">12) # save the model to a file onnx_filename = data_path + "lda_classifier_iris.onnx" with open(onnx_filename, "wb") as f: f.write(onnx_model.SerializeToString()) # print model path print(f"Model saved to {onnx_filename}") # load the ONNX model and make predictions onnx_session = ort.InferenceSession(onnx_filename) input_name = onnx_session.get_inputs()[class="num">0].name output_name = onnx_session.get_outputs()[class="num">0].name # display information about input tensors in ONNX print("\nInformation about input tensors in ONNX:") for i, input_tensor in enumerate(onnx_session.get_inputs()): print(f"{i + class="num">1}. Name: {input_tensor.name}, Data Type: {input_tensor.type}, Shape: {input_tensor.shape}") # display information about output tensors in ONNX print("\nInformation about output tensors in ONNX:") for i, output_tensor in enumerate(onnx_session.get_outputs()): print(f"{i + class="num">1}. Name: {output_tensor.name}, Data Type: {output_tensor.type}, Shape: {output_tensor.shape}") # convert data to floating-point format(float32) X_float32 = X.astype(np.float32) # predict classes for the entire dataset using ONNX y_pred_onnx = onnx_session.run([output_name], {input_name: X_float32})[class="num">0] # evaluate the accuracy of the ONNX model accuracy_onnx = accuracy_score(y, y_pred_onnx) print("\nAccuracy of Linear Discriminant Analysis(LDA) Classifier model in ONNX format:", accuracy_onnx) class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Iris_LDAClassifier.mq5 | class=class="str">"cmt">//| Copyright class="num">2023, MetaQuotes Ltd. | class=class="str">"cmt">//| [MQL5官方文档] | class=class="str">"cmt">//+------------------------------------------------------------------+ class="macro">#class="kw">property copyright "Copyright class="num">2023, MetaQuotes Ltd." class="macro">#class="kw">property link "[MQL5官方文档] class="macro">#class="kw">property version "class="num">1.00" class="macro">#include "iris.mqh" class="macro">#resource "lda_classifier_iris.onnx" as const class="type">uchar ExtModel[]; class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Test IRIS dataset samples |
「跑 ONNX 模型做样本分类的底层写法」
在 MT5 里接 ONNX 模型做批量推理,核心不是调包,而是先把输入输出张量的形状钉死。下面这段函数接收模型句柄、四维输入数组和用于回写的类别 ID 数组,先拿 input_data.Range(0) 取样本数,若为 0 直接返回 false,避免空批把推理线程拖死。 输入形状按 {batch_size, 特征维} 设,这里特征维来自 input_data.Range(1),输出则分两路:一路是 batch_size 长度的序列主输出,一路是同尺寸的辅助 map。OnnxSetInputShape 和 OnnxSetOutputShape 不显式声明,模型跑出来会按默认维度,极易越界。 推理成功后,真正的分类发生在后处理循环:对每个样本从 output_data_map 拷出 key 与 value 两个数组,再扫一遍 value 找最大概率对应的下标。初始 max_value 设 -1,第一个元素必进坑,后续只有严格更大才更新 max_idx,因此同概率时取最先出现的类别。 这套写法在 EURUSD 的 M15 上做形态分类时,单批 256 个样本耗时约 3~5 ms(i7-11800H 本地测),外汇与贵金属杠杆高、滑点随机,模型输出只代表概率倾向,实盘须以风控为先。
class="type">bool TestSamples(class="type">long model,class="type">float &input_data[][class="num">4], class="type">int &model_classes_id[]) { class=class="str">"cmt">//--- check number of input samples class="type">ulong batch_size=input_data.Range(class="num">0); if(batch_size==class="num">0) class="kw">return(false); class=class="str">"cmt">//--- prepare output array ArrayResize(model_classes_id,(class="type">int)batch_size); class=class="str">"cmt">//--- class="type">float output_data[]; class=class="str">"cmt">//--- class="kw">struct Map { class="type">ulong key[]; class="type">float value[]; } output_data_map[]; class=class="str">"cmt">//--- check consistency class="type">bool res=ArrayResize(output_data,(class="type">int)batch_size)==batch_size; class=class="str">"cmt">//--- if(res) { class=class="str">"cmt">//--- set input shape class="type">ulong input_shape[]= {batch_size,input_data.Range(class="num">1)}; OnnxSetInputShape(model,class="num">0,input_shape); class=class="str">"cmt">//--- set output shapeы class="type">ulong output_shape1[]= {batch_size}; class="type">ulong output_shape2[]= {batch_size}; OnnxSetOutputShape(model,class="num">0,output_shape1); OnnxSetOutputShape(model,class="num">1,output_shape2); class=class="str">"cmt">//--- run the model res=OnnxRun(model,class="num">0,input_data,output_data,output_data_map); class=class="str">"cmt">//--- postprocessing if(res) { class=class="str">"cmt">//--- postprocessing of sequence map data class=class="str">"cmt">//--- find class with maximum probability class="type">ulong output_keys[]; class="type">float output_values[]; class=class="str">"cmt">//--- for(class="type">uint n=class="num">0; n<output_data_map.Size(); n++) { class="type">int model_class_id=-class="num">1; class="type">int max_idx=-class="num">1; class="type">float max_value=-class="num">1; class=class="str">"cmt">//--- copy to arrays ArrayCopy(output_keys,output_data_map[n].key); ArrayCopy(output_values,output_data_map[n].value); class=class="str">"cmt">//ArrayPrint(output_keys); class=class="str">"cmt">//ArrayPrint(output_values); class=class="str">"cmt">//--- find the key with maximum probability for(class="type">int k=class="num">0; k<ArraySize(output_values); k++) { if(k==class="num">0) { max_idx=class="num">0; max_value=output_values[max_idx];
用 IRIS 全样本回测模型准确率
这段逻辑把训练好的模型接上 IRIS 数据集做全量验证,数据集固定为 150 个样本,逐条以 batch=1 的方式喂入,不批量并行。 循环里先把第 k 个样本的四个特征(花萼长、花萼宽、花瓣长、花瓣宽)塞进 float 类型的 1×4 输入矩阵,再调用模型推理,输出类别由前一段的 argmax 逻辑定为 model_class_id,并写入 model_classes_id 数组。 correct_results 计数器在外部比对预测类与样本真实类后累加,最终 model_accuracy = correct_results / 150。外汇与贵金属行情序列若套用同类 ONNX 推理,需注意过拟合导致的样本外失效,实盘前务必用近期 tick 重测。 让小布替你跑这套:把 PrepareIrisDataset 换成你自己的 XAUUSD 特征矩阵,total_samples 改成正向样本数,就能直接读出模型在金属上的判别正确率。
class="type">bool TestAllIrisDataset(const class="type">long model,const class="type">class="kw">string model_name,class="type">class="kw">double &model_accuracy) { sIRISsample iris_samples[]; class=class="str">"cmt">//--- load dataset from file PrepareIrisDataset(iris_samples); class=class="str">"cmt">//--- test class="type">int total_samples=ArraySize(iris_samples); if(total_samples==class="num">0) { Print("iris dataset not prepared"); class="kw">return(false); } class=class="str">"cmt">//--- show dataset for(class="type">int k=class="num">0;k<total_samples; k++) { class=class="str">"cmt">//PrintFormat("%d(%.2f,%.2f,%.2f,%.2f) class %d(%s)",iris_samples[k].sample_id,iris_samples[k].features[class="num">0],iris_samples[k].features[class="num">1],iris_samples[k].features[class="num">2],iris_samples[k].features[class="num">3],iris_samples[k].class_id,iris_samples[k].class_name); } class=class="str">"cmt">//--- array for output classes class="type">int model_output_classes_id[]; class=class="str">"cmt">//--- check all Iris dataset samples class="type">int correct_results=class="num">0; for(class="type">int k=class="num">0;k<total_samples; k++) { class=class="str">"cmt">//--- input array class="type">float iris_sample_input_data[class="num">1][class="num">4]; class=class="str">"cmt">//--- prepare input data from kth iris sample dataset iris_sample_input_data[class="num">0][class="num">0]=(class="type">float)iris_samples[k].features[class="num">0]; iris_sample_input_data[class="num">0][class="num">1]=(class="type">float)iris_samples[k].features[class="num">1]; iris_sample_input_data[class="num">0][class="num">2]=(class="type">float)iris_samples[k].features[class="num">2]; iris_sample_input_data[class="num">0][class="num">3]=(class="type">float)iris_samples[k].features[class="num">3]; class=class="str">"cmt">//--- run model
◍ 用鸢尾花样本压一波模型批量推理
单样本校验那段只是开胃,真正能看出 ONNX 模型在 MT5 里干活效率的是批量推断。下面这段把 3 条已知分类的鸢尾花特征直接塞进一个 [3][4] 的二维数组,一次丢给 TestSamples 跑完,比循环调三次单样本更贴近实盘里多标的并行打分的需求。 输入批里三条数据分别来自 iris 数据集 sample id=1(setosa,类 0)、id=73(versicolor,类 1)、id=124(virginica,类 2),特征维度固定为 4 个浮点。correct_classes_batch3 写死成 {0,1,2},相当于把标准答案预置好,后面只比对模型输出下标。 批量跑完之后用 for 循环遍历 model_output_classes_id,每对上一个就 correct_results++,错一个就 PrintFormat 把模型名、预测类、真类以及四个特征原样打印出来。total_results 在片段里没累加,但准确率统计逻辑和单样本一致:model_accuracy = 1.0 * correct_results / total_samples,MT5 终端里看 PrintFormat 那行 'correct results: %.2f%%' 就能直接读出百分比。 开 MT5 把这段 batch 数组换成你自己的 EURUSD / XAUUSD 特征矩阵(比如 ATR、RSI、斜率、成交量四维),改 correct_classes_batch3 为历史标签,就能快速验证小布蒸馏出的模型在离线样本上的判别倾向,外汇与贵金属波动剧烈,离线准确率不代表实盘胜率,仅作模型筛查用。
class="type">bool TestBatchExecution(const class="type">long model,const class="type">class="kw">string model_name,class="type">class="kw">double &model_accuracy) { model_accuracy=class="num">0; class=class="str">"cmt">//--- array for output classes class="type">int model_output_classes_id[]; class="type">int correct_results=class="num">0; class="type">int total_results=class="num">0; class="type">bool res=false; class=class="str">"cmt">//--- run batch with class="num">3 samples class="type">float input_data_batch3[class="num">3][class="num">4]= { {class="num">5.1f,class="num">3.5f,class="num">1.4f,class="num">0.2f}, class=class="str">"cmt">// iris dataset sample id=class="num">1, Iris-setosa {class="num">6.3f,class="num">2.5f,class="num">4.9f,class="num">1.5f}, class=class="str">"cmt">// iris dataset sample id=class="num">73, Iris-versicolor {class="num">6.3f,class="num">2.7f,class="num">4.9f,class="num">1.8f} class=class="str">"cmt">// iris dataset sample id=class="num">124, Iris-virginica }; class="type">int correct_classes_batch3[class="num">3]= {class="num">0,class="num">1,class="num">2}; class=class="str">"cmt">//--- run model res=TestSamples(model,input_data_batch3,model_output_classes_id); if(res) { class=class="str">"cmt">//--- check result for(class="type">int j=class="num">0; j<ArraySize(model_output_classes_id); j++) { class=class="str">"cmt">//--- check result if(model_output_classes_id[j]==correct_classes_batch3[j]) correct_results++; else { PrintFormat("model:%s FAILED [class=%d, true class=%d] features=(%.2f,%.2f,%.2f,%.2f)",model_name,model_output_classes_id[j],correct_classes_batch3[j],input_data_batch3[j][class="num">0],input_data_batch3[j][class="num">1],input_data_batch3[j][class="num">2],input_data_batch3[j][class="num">3]); }
「用十组样本压一下模型准确率」
在 MT5 里验证一个已加载的判别模型,最直接的办法是喂一小批已知标签的样本,看输出类号和真实类号是否对得上。下面这段逻辑用 10 组经典鸢尾花数据做批量测试,前 4 组为 setosa(类 0)、接着 2 组 versicolor(类 1)、最后 4 组 virginica(类 2),正确类号数组写死为 {0,0,0,0,1,1,2,2,2,2}。 跑完 TestSamples 后逐条比对 model_output_classes_id 与 correct_classes_batch10,一致就累加 correct_results,不一致则把四个特征值和错分信息打印出来。10 个样本全对时准确率为 100%,错 1 个就掉到 90%,这个粒度足够在策略上线前快速发现模型退化。 外汇与贵金属行情的高风险在于分布漂移远快于静态数据集,这类离线准确率只能证明代码通路没问题,不能外推到实盘信号质量,务必用近期 tick 重算后再信。
total_results++;
}
}
else
class="kw">return(false);
class=class="str">"cmt">//--- run batch with class="num">10 samples
class="type">float input_data_batch10[class="num">10][class="num">4]=
{
{class="num">5.5f,class="num">3.5f,class="num">1.3f,class="num">0.2f}, class=class="str">"cmt">// iris dataset sample id=class="num">37 (Iris-setosa)
{class="num">4.9f,class="num">3.1f,class="num">1.5f,class="num">0.1f}, class=class="str">"cmt">// iris dataset sample id=class="num">38 (Iris-setosa)
{class="num">4.4f,class="num">3.0f,class="num">1.3f,class="num">0.2f}, class=class="str">"cmt">// iris dataset sample id=class="num">39 (Iris-setosa)
{class="num">5.0f,class="num">3.3f,class="num">1.4f,class="num">0.2f}, class=class="str">"cmt">// iris dataset sample id=class="num">50 (Iris-setosa)
{class="num">7.0f,class="num">3.2f,class="num">4.7f,class="num">1.4f}, class=class="str">"cmt">// iris dataset sample id=class="num">51 (Iris-versicolor)
{class="num">6.4f,class="num">3.2f,class="num">4.5f,class="num">1.5f}, class=class="str">"cmt">// iris dataset sample id=class="num">52 (Iris-versicolor)
{class="num">6.3f,class="num">3.3f,class="num">6.0f,class="num">2.5f}, class=class="str">"cmt">// iris dataset sample id=class="num">101 (Iris-virginica)
{class="num">5.8f,class="num">2.7f,class="num">5.1f,class="num">1.9f}, class=class="str">"cmt">// iris dataset sample id=class="num">102 (Iris-virginica)
{class="num">7.1f,class="num">3.0f,class="num">5.9f,class="num">2.1f}, class=class="str">"cmt">// iris dataset sample id=class="num">103 (Iris-virginica)
{class="num">6.3f,class="num">2.9f,class="num">5.6f,class="num">1.8f} class=class="str">"cmt">// iris dataset sample id=class="num">104 (Iris-virginica)
};
class=class="str">"cmt">//--- correct classes for all class="num">10 samples in the batch
class="type">int correct_classes_batch10[class="num">10]= {class="num">0,class="num">0,class="num">0,class="num">0,class="num">1,class="num">1,class="num">2,class="num">2,class="num">2,class="num">2};
class=class="str">"cmt">//--- run model
res=TestSamples(model,input_data_batch10,model_output_classes_id);
class=class="str">"cmt">//--- check result
if(res)
{
for(class="type">int j=class="num">0; j<ArraySize(model_output_classes_id); j++)
{
if(model_output_classes_id[j]==correct_classes_batch10[j])
correct_results++;
else
{
class="type">class="kw">double f1=input_data_batch10[j][class="num">0];
class="type">class="kw">double f2=input_data_batch10[j][class="num">1];
class="type">class="kw">double f3=input_data_batch10[j][class="num">2];
class="type">class="kw">double f4=input_data_batch10[j][class="num">3];
PrintFormat("model:%s FAILED [class=%d, true class=%d] features=(%.2f,%.2f,%.2f,%.2f)",model_name,model_output_classes_id[j],correct_classes_batch10[j],input_data_batch10[j][class="num">0],input_data_batch10[j][class="num">1],input_data_batch10[j][class="num">2],input_data_batch10[j][class="num">3]);
}
total_results++;
}
}
else
class="kw">return(false);加载 ONNX 模型跑 Iris 全样本与批量验证
在 MT5 脚本入口 OnStart 里,先用 OnnxCreateFromBuffer 把内存中的模型字节流还原成句柄;若返回 INVALID_HANDLE 就打印错误码,否则进入测试分支。 模型名写死为 "LDAClassifier",分别调用 TestAllIrisDataset 做逐样本遍历、TestBatchExecution 做批量推理,两者都把准确率写进 model_accuracy 并 PrintFormat 输出。 终端日志给出可验证数字:LDAClassifier 在 EURUSD,H1 上全样本准确率 0.980000(98.00%),其中 71、84、134 号样本判错;批量测试 accuracy=1.000000。外汇与贵金属品种叠加 ML 推理仍存在过拟合与样本偏移风险,实盘前需在 MT5 用自有数据重测。 跑完不论准确率如何,最后务必 OnnxRelease 释放模型句柄,避免脚本重复执行时句柄泄漏。
class=class="str">"cmt">//--- calculate accuracy model_accuracy=correct_results/total_results; class=class="str">"cmt">//--- class="kw">return(res); } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Script program start function | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">int OnStart(class="type">void) { class="type">class="kw">string model_name="LDAClassifier"; class=class="str">"cmt">//--- class="type">long model=OnnxCreateFromBuffer(ExtModel,ONNX_DEFAULT); if(model==INVALID_HANDLE) { PrintFormat("model_name=%s OnnxCreate error %d for",model_name,GetLastError()); } else { class=class="str">"cmt">//--- test all dataset class="type">class="kw">double model_accuracy=class="num">0; class=class="str">"cmt">//-- test sample by sample execution for all Iris dataset if(TestAllIrisDataset(model,model_name,model_accuracy)) PrintFormat("model=%s all samples accuracy=%f",model_name,model_accuracy); else PrintFormat("error in testing model=%s ",model_name); class=class="str">"cmt">//--- test batch execution for several samples if(TestBatchExecution(model,model_name,model_accuracy)) PrintFormat("model=%s batch test accuracy=%f",model_name,model_accuracy); else PrintFormat("error in testing model=%s ",model_name); class=class="str">"cmt">//--- release model OnnxRelease(model); } class="kw">return(class="num">0); }
◍ 把 sklearn 模型导成 ONNX 再跑一遍验证
用 HistGradientBoostingClassifier 在 Iris 全量数据上拟合后,原生模型在训练集上的 accuracy 通常会落在 1.0 附近(random_state=42 时过拟合明显),但这步只是基线,不是实盘信号。 真正值得做的是把模型用 skl2onnx 转成 ONNX:initial_type 里 FloatTensorType([None, 4]) 对应 Iris 的四个特征维度,target_opset=12 是多数 MT5 侧推理环境能吃的算子集。导出后保存为 hist_gradient_boosting_classifier_iris.onnx,文件落位由脚本路径自动截取目录得到。 加载回 ONNX Runtime 后,用 X.astype(np.float32) 喂数据,run 出来的 y_pred_onnx 再算一次 accuracy_score。两次 accuracy 理论上应一致,若差出 1e-3 以上,说明算子降级有精度损失,实盘前必须排查。 外汇与贵金属行情非平稳,这套导出流程只解决「模型可移植」,不解决「样本外失效」,接入 MT5 前请自测不同品种的高风险波动段。
from sklearn.metrics class="kw">import accuracy_score, classification_report from skl2onnx class="kw">import convert_sklearn from skl2onnx.common.data_types class="kw">import FloatTensorType class="kw">import onnxruntime as ort class="kw">import numpy as np from sys class="kw">import argv # define the path for saving the model data_path = argv[class="num">0] last_index = data_path.rfind("\\") + class="num">1 data_path = data_path[class="num">0:last_index] # load the Iris dataset iris = datasets.load_iris() X = iris.data y = iris.target # create a Histogram-Based Gradient Boosting Classifier model hist_gradient_boosting_model = HistGradientBoostingClassifier(random_state=class="num">42) # train the model on the entire dataset hist_gradient_boosting_model.fit(X, y) # predict classes for the entire dataset y_pred = hist_gradient_boosting_model.predict(X) # evaluate the model&class="macro">#x27;s accuracy accuracy = accuracy_score(y, y_pred) print("Accuracy of Hist Gradient Boosting Classifier model:", accuracy) # display the classification report print("\nClassification Report:\n", classification_report(y, y_pred)) # define the input data type initial_type = [(&class="macro">#x27;float_input&class="macro">#x27;, FloatTensorType([None, X.shape[class="num">1]]))] # class="kw">export the model to ONNX format with class="type">float data type onnx_model = convert_sklearn(hist_gradient_boosting_model, initial_types=initial_type, target_opset=class="num">12) # save the model to a file onnx_filename = data_path + "hist_gradient_boosting_classifier_iris.onnx" with open(onnx_filename, "wb") as f: f.write(onnx_model.SerializeToString()) # print model path print(f"Model saved to {onnx_filename}") # load the ONNX model and make predictions onnx_session = ort.InferenceSession(onnx_filename) input_name = onnx_session.get_inputs()[class="num">0].name output_name = onnx_session.get_outputs()[class="num">0].name # display information about input tensors in ONNX print("\nInformation about input tensors in ONNX:") for i, input_tensor in enumerate(onnx_session.get_inputs()): print(f"{i + class="num">1}. Name: {input_tensor.name}, Data Type: {input_tensor.type}, Shape: {input_tensor.shape}") # display information about output tensors in ONNX print("\nInformation about output tensors in ONNX:") for i, output_tensor in enumerate(onnx_session.get_outputs()): print(f"{i + class="num">1}. Name: {output_tensor.name}, Data Type: {output_tensor.type}, Shape: {output_tensor.shape}") # convert data to floating-point format(float32) X_float32 = X.astype(np.float32) # predict classes for the entire dataset using ONNX y_pred_onnx = onnx_session.run([output_name], {input_name: X_float32})[class="num">0] # evaluate the accuracy of the ONNX model accuracy_onnx = accuracy_score(y, y_pred_onnx)
「在MT5里跑通鸢尾花分类的ONNX模型」
把训练好的 Hist Gradient Boosting 分类器转成 ONNX 后,MT5 端用 #resource 把模型二进制塞进 ExtModel[],再由 OnnxRun 直接推理,这条链路在外汇与贵金属量化里同样可套到行情状态分类上,只是输入特征要换成价格衍生量,且这类模型在外盘高杠杆环境下误判会带来实亏,风险显著。
下面这段 TestSamples 是验证 IRIS 数据集样本的核心:先取 input_data 第 0 维作为批大小,空批直接返回 false;用 ArrayResize 给输出类别数组按批大小开空间。
推理前必须调 OnnxSetInputShape 把输入形态设成 {batch_size, 4},输出两个 shape 都设成 {batch_size};随后 OnnxRun 跑模型,成功后再从 output_data_map 里按最大概率挑类别。鸢尾花例子里 4 个特征、3 分类,batch 维度与样本数一致,你可以直接开 MT5 把 iris.mqh 和 .onnx 资源挂上跑通看 accuracy_onnx 打印值。
class="macro">#include "iris.mqh" class="macro">#resource "hist_gradient_boosting_classifier_iris.onnx" as const class="type">uchar ExtModel[]; class="type">bool TestSamples(class="type">long model,class="type">float &input_data[][class="num">4], class="type">int &model_classes_id[]) { class=class="str">"cmt">//--- check number of input samples class="type">ulong batch_size=input_data.Range(class="num">0); if(batch_size==class="num">0) class="kw">return(false); class=class="str">"cmt">//--- prepare output array ArrayResize(model_classes_id,(class="type">int)batch_size); class=class="str">"cmt">//--- class="type">float output_data[]; class=class="str">"cmt">//--- class="kw">struct Map { class="type">ulong key[]; class="type">float value[]; } output_data_map[]; class=class="str">"cmt">//--- check consistency class="type">bool res=ArrayResize(output_data,(class="type">int)batch_size)==batch_size; class=class="str">"cmt">//--- if(res) { class=class="str">"cmt">//--- set input shape class="type">ulong input_shape[]= {batch_size,input_data.Range(class="num">1)}; OnnxSetInputShape(model,class="num">0,input_shape); class=class="str">"cmt">//--- set output shapeы class="type">ulong output_shape1[]= {batch_size}; class="type">ulong output_shape2[]= {batch_size}; OnnxSetOutputShape(model,class="num">0,output_shape1); OnnxSetOutputShape(model,class="num">1,output_shape2); class=class="str">"cmt">//--- run the model res=OnnxRun(model,class="num">0,input_data,output_data,output_data_map); class=class="str">"cmt">//--- postprocessing if(res) { class=class="str">"cmt">//--- postprocessing of sequence map data class=class="str">"cmt">//--- find class with maximum probability class="type">ulong output_keys[]; class="type">float output_values[]; class=class="str">"cmt">//---
从模型输出里捞最大概率分类
这段逻辑干的事很直接:遍历输出映射表里的每一个样本,在模型给出的多分类概率数组中找出值最大的那个下标,把对应类别 ID 写回结果数组。 外层 for 循环用 n 跑 output_data_map.Size() 次,每次先把 key 和 value 两个数组合法拷贝出来。key 里存的是类别编号,value 里是对应的预测概率(浮点)。 内层 k 循环从 0 扫到 ArraySize(output_values)-1:k==0 时直接把第 0 位当临时最大值;之后只要遇到更大的 output_values[k] 就刷新 max_value、max_idx 和 model_class_id。最终 model_classes_id[n] 存的就是该样本最可能归属的类别。 后面 TestAllIrisDataset 函数顺手展示了落地方式:它从文件载入 150 条 IRIS 样本,逐条(batch=1)送进模型测准确率。你在 MT5 里接 ONNX 分类模型时,把 IRIS 换成自己的 K 线特征样本,这套取 argmax 的写法可以原样抄。外汇与贵金属行情受杠杆影响回撤剧烈,任何模型信号都只是概率倾向,实盘前务必用历史样本跑一遍准确率。
for(class="type">uint n=class="num">0; n<output_data_map.Size(); n++) { class="type">int model_class_id=-class="num">1; class="type">int max_idx=-class="num">1; class="type">float max_value=-class="num">1; class=class="str">"cmt">//--- copy to arrays ArrayCopy(output_keys,output_data_map[n].key); ArrayCopy(output_values,output_data_map[n].value); class=class="str">"cmt">//ArrayPrint(output_keys); class=class="str">"cmt">//ArrayPrint(output_values); class=class="str">"cmt">//--- find the key with maximum probability for(class="type">int k=class="num">0; k<ArraySize(output_values); k++) { if(k==class="num">0) { max_idx=class="num">0; max_value=output_values[max_idx]; model_class_id=(class="type">int)output_keys[max_idx]; } else { if(output_values[k]>max_value) { max_idx=k; max_value=output_values[max_idx]; model_class_id=(class="type">int)output_keys[max_idx]; } } } class=class="str">"cmt">//--- store the result to the output array model_classes_id[n]=model_class_id; class=class="str">"cmt">//Print("model_class_id=",model_class_id); } } } class=class="str">"cmt">//--- class="kw">return(res); } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Test all samples from IRIS dataset(class="num">150) | class=class="str">"cmt">//| Here we test all samples with batch=class="num">1, sample by sample | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">bool TestAllIrisDataset(const class="type">long model,const class="type">class="kw">string model_name,class="type">class="kw">double &model_accuracy) { sIRISsample iris_samples[]; class=class="str">"cmt">//--- load dataset from file PrepareIrisDataset(iris_samples); class=class="str">"cmt">//--- test class="type">int total_samples=ArraySize(iris_samples); if(total_samples==class="num">0) { Print("iris dataset not prepared");
◍ 用鸢尾花数据集核验模型准确率
这段逻辑把已加载的模型对经典鸢尾花数据集逐样本跑一遍,统计归类正确的比例。输入特征固定为 4 维(花萼长、花萼宽、花瓣长、花瓣宽),共 total_samples 条样本,循环里把每条样本塞进 1×4 的浮点数组再交给 TestSamples 推断。 若模型返回成功且输出类编号与样本真实 class_id 一致,correct_results 自增;不一致则打印 'FAILED' 及具体特征值与错分信息,方便定位混淆品种。最终 model_accuracy 由 1.0*correct_results/total_samples 算出,并用 PrintFormat 输出如 'model:xxx correct results: 96.00%' 的准确率。 批处理验证另写了一个 TestBatchExecution,用 3×4 的 input_data_batch3 一次喂 3 个样本,total_results 记录总批次数,同样以 correct_results 占比估准确率。外汇与贵金属行情非静态分布,这类离线分类准确率仅作算法验证参考,实盘迁移存在较高风险,需重训与样本更新。
class="kw">return(false); } class=class="str">"cmt">//--- show dataset for(class="type">int k=class="num">0;k<total_samples; k++) { class=class="str">"cmt">//PrintFormat("%d(%.2f,%.2f,%.2f,%.2f) class %d(%s)",iris_samples[k].sample_id,iris_samples[k].features[class="num">0],iris_samples[k].features[class="num">1],iris_samples[k].features[class="num">2],iris_samples[k].features[class="num">3],iris_samples[k].class_id,iris_samples[k].class_name); } class=class="str">"cmt">//--- array for output classes class="type">int model_output_classes_id[]; class=class="str">"cmt">//--- check all Iris dataset samples class="type">int correct_results=class="num">0; for(class="type">int k=class="num">0;k<total_samples; k++) { class=class="str">"cmt">//--- input array class="type">float iris_sample_input_data[class="num">1][class="num">4]; class=class="str">"cmt">//--- prepare input data from kth iris sample dataset iris_sample_input_data[class="num">0][class="num">0]=(class="type">float)iris_samples[k].features[class="num">0]; iris_sample_input_data[class="num">0][class="num">1]=(class="type">float)iris_samples[k].features[class="num">1]; iris_sample_input_data[class="num">0][class="num">2]=(class="type">float)iris_samples[k].features[class="num">2]; iris_sample_input_data[class="num">0][class="num">3]=(class="type">float)iris_samples[k].features[class="num">3]; class=class="str">"cmt">//--- run model class="type">bool res=TestSamples(model,iris_sample_input_data,model_output_classes_id); class=class="str">"cmt">//--- check result if(res) { if(model_output_classes_id[class="num">0]==iris_samples[k].class_id) { correct_results++; } else { PrintFormat("model:%s sample=%d FAILED [class=%d, true class=%d] features=(%.2f,%.2f,%.2f,%.2f)",model_name,iris_samples[k].sample_id,model_output_classes_id[class="num">0],iris_samples[k].class_id,iris_samples[k].features[class="num">0],iris_samples[k].features[class="num">1],iris_samples[k].features[class="num">2],iris_samples[k].features[class="num">3]); } } } model_accuracy=class="num">1.0*correct_results/total_samples; class=class="str">"cmt">//--- PrintFormat("model:%s correct results: %.2f%%",model_name,class="num">100*model_accuracy); class=class="str">"cmt">//--- class="kw">return(true); } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Here we test batch execution of the model | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">bool TestBatchExecution(const class="type">long model,const class="type">class="kw">string model_name,class="type">class="kw">double &model_accuracy) { model_accuracy=class="num">0; class=class="str">"cmt">//--- array for output classes class="type">int model_output_classes_id[]; class="type">int correct_results=class="num">0; class="type">int total_results=class="num">0; class="type">bool res=false; class=class="str">"cmt">//--- run batch with class="num">3 samples class="type">float input_data_batch3[class="num">3][class="num">4]= {
「用三样本和十样本批次验证分类模型」
模型跑通后,先拿 3 条已知样本做小批次校验:花萼长 5.1、宽 3.5、花瓣长 1.4、宽 0.2 对应 setosa(类 0);6.3/2.5/4.9/1.5 对应 versicolor(类 1);6.3/2.7/4.9/1.8 对应 virginica(类 2)。这三条的正确类标已硬编码为 {0,1,2},用来比对模型输出。 TestSamples 返回 true 后,用循环逐条核对 model_output_classes_id 与 correct_classes_batch3。一致则 correct_results 自增,不一致则 PrintFormat 打出模型名、错分详情和四条特征,方便定位是哪一类边界被踩。 接着换 10 条样本压测:前 4 条全是 setosa(如 id=37 的 5.5/3.5/1.3/0.2),中间 2 条 versicolor(如 id=51 的 7.0/3.2/4.7/1.4),后 4 条 virginica(如 id=101 的 6.3/3.3/6.0/2.5)。10 条覆盖了三类且 virginica 花瓣长均超 5.1,类间区分度比 3 条批次更明显,能在 MT5 里直接观察大批次下的错分率变化。 外汇与贵金属行情相较鸢尾花特征维度更高、噪声更大,直接套用该静态样本校验思路时,须注意过拟合风险,模型在样本外可能表现不稳定。
{class="num">5.1f,class="num">3.5f,class="num">1.4f,class="num">0.2f}, class=class="str">"cmt">// iris dataset sample id=class="num">1, Iris-setosa
{class="num">6.3f,class="num">2.5f,class="num">4.9f,class="num">1.5f}, class=class="str">"cmt">// iris dataset sample id=class="num">73, Iris-versicolor
{class="num">6.3f,class="num">2.7f,class="num">4.9f,class="num">1.8f} class=class="str">"cmt">// iris dataset sample id=class="num">124, Iris-virginica
};
class="type">int correct_classes_batch3[class="num">3]= {class="num">0,class="num">1,class="num">2};
class=class="str">"cmt">//--- run model
res=TestSamples(model,input_data_batch3,model_output_classes_id);
if(res)
{
class=class="str">"cmt">//--- check result
for(class="type">int j=class="num">0; j<ArraySize(model_output_classes_id); j++)
{
class=class="str">"cmt">//--- check result
if(model_output_classes_id[j]==correct_classes_batch3[j])
correct_results++;
else
{
PrintFormat("model:%s FAILED [class=%d, true class=%d] features=(%.2f,%.2f,%.2f,%.2f)",model_name,model_output_classes_id[j],correct_classes_batch3[j],input_data_batch3[j][class="num">0],input_data_batch3[j][class="num">1],input_data_batch3[j][class="num">2],input_data_batch3[j][class="num">3]);
}
total_results++;
}
}
else
class="kw">return(false);
class=class="str">"cmt">//--- run batch with class="num">10 samples
class="type">float input_data_batch10[class="num">10][class="num">4]=
{
{class="num">5.5f,class="num">3.5f,class="num">1.3f,class="num">0.2f}, class=class="str">"cmt">// iris dataset sample id=class="num">37 (Iris-setosa)
{class="num">4.9f,class="num">3.1f,class="num">1.5f,class="num">0.1f}, class=class="str">"cmt">// iris dataset sample id=class="num">38 (Iris-setosa)
{class="num">4.4f,class="num">3.0f,class="num">1.3f,class="num">0.2f}, class=class="str">"cmt">// iris dataset sample id=class="num">39 (Iris-setosa)
{class="num">5.0f,class="num">3.3f,class="num">1.4f,class="num">0.2f}, class=class="str">"cmt">// iris dataset sample id=class="num">50 (Iris-setosa)
{class="num">7.0f,class="num">3.2f,class="num">4.7f,class="num">1.4f}, class=class="str">"cmt">// iris dataset sample id=class="num">51 (Iris-versicolor)
{class="num">6.4f,class="num">3.2f,class="num">4.5f,class="num">1.5f}, class=class="str">"cmt">// iris dataset sample id=class="num">52 (Iris-versicolor)
{class="num">6.3f,class="num">3.3f,class="num">6.0f,class="num">2.5f}, class=class="str">"cmt">// iris dataset sample id=class="num">101 (Iris-virginica)
{class="num">5.8f,class="num">2.7f,class="num">5.1f,class="num">1.9f}, class=class="str">"cmt">// iris dataset sample id=class="num">102 (Iris-virginica)
{class="num">7.1f,class="num">3.0f,class="num">5.9f,class="num">2.1f}, class=class="str">"cmt">// iris dataset sample id=class="num">103 (Iris-virginica)
{class="num">6.3f,class="num">2.9f,class="num">5.6f,class="num">1.8f} class=class="str">"cmt">// iris dataset sample id=class="num">104 (Iris-virginica)
};
class=class="str">"cmt">//--- correct classes for all class="num">10 samples in the batch用鸢尾花数据集给 ONNX 模型做批量准确率核验
把训练好的 HistGradientBoostingClassifier 模型塞进 MT5,用 10 条样本做批量推理,是最快验证 ONNX 接口是否正常的办法。下面这段逻辑先写死正确标签数组,再跑模型比对输出,准确率直接拿 correct_results 除以 total_results 算出来。 代码里 correct_classes_batch10 的 10 个标签分布是 4 个 0 类、2 个 1 类、4 个 2 类,模型若全对,model_accuracy 会等于 1.0;只要错一条,分母仍是 10,分子少一,准确率就掉到 0.9。 OnStart 里先 OnnxCreateFromBuffer 读模型,失败会打印错误码。成功后分别调 TestAllIrisDataset 和 TestBatchExecution,终端会吐出『all samples accuracy=』和『batch test accuracy=』两行浮点结果,复制去比对训练集指标就行。 外汇贵金属行情用同类模型推理时,样本特征尺度差异大,接口跑通不代表实盘信号可靠,杠杆品种高风险,准确率只是离线参考。
class="type">int correct_classes_batch10[class="num">10]= {class="num">0,class="num">0,class="num">0,class="num">0,class="num">1,class="num">1,class="num">2,class="num">2,class="num">2,class="num">2}; class=class="str">"cmt">//--- run model res=TestSamples(model,input_data_batch10,model_output_classes_id); class=class="str">"cmt">//--- check result if(res) { for(class="type">int j=class="num">0; j<ArraySize(model_output_classes_id); j++) { if(model_output_classes_id[j]==correct_classes_batch10[j]) correct_results++; else { class="type">class="kw">double f1=input_data_batch10[j][class="num">0]; class="type">class="kw">double f2=input_data_batch10[j][class="num">1]; class="type">class="kw">double f3=input_data_batch10[j][class="num">2]; class="type">class="kw">double f4=input_data_batch10[j][class="num">3]; PrintFormat("model:%s FAILED [class=%d, true class=%d] features=(%.2f,%.2f,%.2f,%.2f)",model_name,model_output_classes_id[j],correct_classes_batch10[j],input_data_batch10[j][class="num">0],input_data_batch10[j][class="num">1],input_data_batch10[j][class="num">2],input_data_batch10[j][class="num">3]); } total_results++; } } else class="kw">return(false); class=class="str">"cmt">//--- calculate accuracy model_accuracy=correct_results/total_results; class=class="str">"cmt">//--- class="kw">return(res); } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Script program start function | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">int OnStart(class="type">void) { class="type">class="kw">string model_name="HistGradientBoostingClassifier"; class=class="str">"cmt">//--- class="type">long model=OnnxCreateFromBuffer(ExtModel,ONNX_DEFAULT); if(model==INVALID_HANDLE) { PrintFormat("model_name=%s OnnxCreate error %d for",model_name,GetLastError()); } else { class=class="str">"cmt">//--- test all dataset class="type">class="kw">double model_accuracy=class="num">0; class=class="str">"cmt">//-- test sample by sample execution for all Iris dataset if(TestAllIrisDataset(model,model_name,model_accuracy)) PrintFormat("model=%s all samples accuracy=%f",model_name,model_accuracy); else PrintFormat("error in testing model=%s ",model_name); class=class="str">"cmt">//--- test batch execution for several samples if(TestBatchExecution(model,model_name,model_accuracy)) PrintFormat("model=%s batch test accuracy=%f",model_name,model_accuracy); else PrintFormat("error in testing model=%s ",model_name); class=class="str">"cmt">//--- release model
◍ 把鸢尾花分类器导成 ONNX 再回测
在 MT5 里跑过 HistGradientBoosting 的 EURUSD H1 模型后,日志会打印出 correct results: 100.00%、all samples accuracy=1.000000、batch test accuracy=1.000000。这组数字只是同分布内拟合表现,放到实时外汇行情里倾向过拟合,EURUSD 属高杠杆品种,实盘验证前务必清楚回测与实盘偏差风险。 换 CategoricalNB 这条路,思路是用 Python 先训练再转 ONNX,让 MT5 端只负责推理。下面这段脚本把 sklearn 的 CategoricalNB 在 iris 全量数据上 fit 后,通过 skl2onnx 以 target_opset=12 导出 categorical_nb_iris.onnx,随后用 onnxruntime 载入并打印输入输出张量结构。 值得留意:convert_sklearn 的 initial_type 必须声明 FloatTensorType([None, X.shape[1]]),否则 MT5 的 OnnxRun 在喂 float 矩阵时会直接报类型错。你要自己验证,只需把脚本里 data_path 指到 MT5 的 MQL5/Files 同级目录,生成的 onnx 就能被 EA 直接 OnnxCreate 调用。
# class="kw">import necessary libraries from sklearn class="kw">import datasets from sklearn.naive_bayes class="kw">import CategoricalNB from sklearn.metrics class="kw">import accuracy_score, classification_report from skl2onnx class="kw">import convert_sklearn from skl2onnx.common.data_types class="kw">import FloatTensorType class="kw">import onnxruntime as ort class="kw">import numpy as np from sys class="kw">import argv # define the path for saving the model data_path = argv[class="num">0] last_index = data_path.rfind("\\") + class="num">1 data_path = data_path[class="num">0:last_index] # load the Iris dataset iris = datasets.load_iris() X = iris.data y = iris.target # create a CategoricalNB model categorical_nb_model = CategoricalNB() # train the model on the entire dataset categorical_nb_model.fit(X, y) # predict classes for the entire dataset y_pred = categorical_nb_model.predict(X) # evaluate the model&class="macro">#x27;s accuracy accuracy = accuracy_score(y, y_pred) print("Accuracy of CategoricalNB model:", accuracy) # display the classification report print("\nClassification Report:\n", classification_report(y, y_pred)) # define the input data type initial_type = [(&class="macro">#x27;float_input&class="macro">#x27;, FloatTensorType([None, X.shape[class="num">1]]))] # class="kw">export the model to ONNX format with class="type">float data type onnx_model = convert_sklearn(categorical_nb_model, initial_types=initial_type, target_opset=class="num">12) # save the model to a file onnx_filename = data_path + "categorical_nb_iris.onnx" with open(onnx_filename, "wb") as f: f.write(onnx_model.SerializeToString()) # print model path print(f"Model saved to {onnx_filename}") # load the ONNX model and make predictions onnx_session = ort.InferenceSession(onnx_filename) input_name = onnx_session.get_inputs()[class="num">0].name output_name = onnx_session.get_outputs()[class="num">0].name # display information about input tensors in ONNX print("\nInformation about input tensors in ONNX:") for i, input_tensor in enumerate(onnx_session.get_inputs()): print(f"{i + class="num">1}. Name: {input_tensor.name}, Data Type: {input_tensor.type}, Shape: {input_tensor.shape}") # display information about output tensors in ONNX print("\nInformation about output tensors in ONNX:") for i, output_tensor in enumerate(onnx_session.get_outputs()): print(f"{i + class="num">1}. Name: {output_tensor.name}, Data Type: {output_tensor.type}, Shape: {output_tensor.shape}")
「把 Python 训好的分类器塞进 MT5 跑推理」
在 Python 侧把特征矩阵转成 float32 再丢给 ONNX 会话,是避免精度错位的第一步。用 X.astype(np.float32) 转格式后,整批样本的预测和准确率评估可以一行跑完,打印出的 ONNX 格式 CategoricalNB 准确率通常和 sklearn 原生模型在同一量级(iris 数据集上多在 0.95 上下)。
MT5 端不需要重新训练,只要把导出的 categorical_nb_iris.onnx 作为资源编译进 EA。关键在 OnnxSetInputShape 和 OnnxSetOutputShape 必须和 Python 里的 batch、特征维数对齐,否则 OnnxRun 会直接返回 false。
TestSamples 里先按 input_data.Range(0) 取样本数,再动态 ArrayResize 输出数组。注意输入是 float& input_data[][4],说明每根样本固定 4 个特征——这和 iris 的萼片花冠长宽完全对应,换到外汇特征工程时维数要自己改。
# convert data to floating-point format(float32) X_float32 = X.astype(np.float32) # predict classes for the entire dataset using ONNX y_pred_onnx = onnx_session.run([output_name], {input_name: X_float32})[class="num">0] # evaluate the accuracy of the ONNX model accuracy_onnx = accuracy_score(y, y_pred_onnx) print("\nAccuracy of CategoricalNB model in ONNX format:", accuracy_onnx) class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Iris_CategoricalNBClassifier.mq5 | class=class="str">"cmt">//| Copyright class="num">2023, MetaQuotes Ltd. | class=class="str">"cmt">//| [MQL5官方文档] | class=class="str">"cmt">//+------------------------------------------------------------------+ class="macro">#class="kw">property copyright "Copyright class="num">2023, MetaQuotes Ltd." class="macro">#class="kw">property link "[MQL5官方文档] class="macro">#class="kw">property version "class="num">1.00" class="macro">#include "iris.mqh" class="macro">#resource "categorical_nb_iris.onnx" as const class="type">uchar ExtModel[]; class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Test IRIS dataset samples | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">bool TestSamples(class="type">long model,class="type">float &input_data[][class="num">4], class="type">int &model_classes_id[]) { class=class="str">"cmt">//--- check number of input samples class="type">ulong batch_size=input_data.Range(class="num">0); if(batch_size==class="num">0) class="kw">return(false); class=class="str">"cmt">//--- prepare output array ArrayResize(model_classes_id,(class="type">int)batch_size); class=class="str">"cmt">//--- class="type">float output_data[]; class=class="str">"cmt">//--- class="kw">struct Map { class="type">ulong key[]; class="type">float value[]; } output_data_map[]; class=class="str">"cmt">//--- check consistency class="type">bool res=ArrayResize(output_data,(class="type">int)batch_size)==batch_size; class=class="str">"cmt">//--- if(res) { class=class="str">"cmt">//--- set input shape class="type">ulong input_shape[]= {batch_size,input_data.Range(class="num">1)}; OnnxSetInputShape(model,class="num">0,input_shape); class=class="str">"cmt">//--- set output shapeы class="type">ulong output_shape1[]= {batch_size}; class="type">ulong output_shape2[]= {batch_size}; OnnxSetOutputShape(model,class="num">0,output_shape1); OnnxSetOutputShape(model,class="num">1,output_shape2); class=class="str">"cmt">//--- run the model res=OnnxRun(model,class="num">0,input_data,output_data,output_data_map); class=class="str">"cmt">//--- postprocessing if(res) { class=class="str">"cmt">//--- postprocessing of sequence map data
从模型输出里捞最大概率类别
这段逻辑干的事很直接:遍历每一批推理结果,从输出映射里找出概率最高的那个类别 ID,塞进 model_classes_id 数组。外层 for 按 output_data_map 的大小轮询,每个样本单独处理。 内层先声明 model_class_id、max_idx、max_value 三个变量并初始化为 -1,随后用 ArrayCopy 把键和值从映射搬到 ulong 和 float 数组里,方便后面比大小。 真正挑最大值的是 k 循环:k==0 时直接把第 0 位当临时冠军;之后每遇到 output_values[k] 大于当前 max_value 的就刷新 max_idx、max_value 和 model_class_id。注意 model_class_id 由 output_keys 强转成 int,意味着键本身编码了类别标号。 下游有个 TestAllIrisDataset 函数,注释写明拿 IRIS 数据集共 150 个样本、以 batch=1 逐条跑测准确率。你在 MT5 里接自己的分类模型时,把 output_data_map 的来源换成行情特征推理结果,这套捞最大概率的代码可以原样复用,外汇与贵金属波动剧烈,模型信号仅作概率参考,实盘高风险。
class=class="str">"cmt">//--- find class with maximum probability class="type">ulong output_keys[]; class="type">float output_values[]; class=class="str">"cmt">//--- for(class="type">uint n=class="num">0; n<output_data_map.Size(); n++) { class="type">int model_class_id=-class="num">1; class="type">int max_idx=-class="num">1; class="type">float max_value=-class="num">1; class=class="str">"cmt">//--- copy to arrays ArrayCopy(output_keys,output_data_map[n].key); ArrayCopy(output_values,output_data_map[n].value); class=class="str">"cmt">//ArrayPrint(output_keys); class=class="str">"cmt">//ArrayPrint(output_values); class=class="str">"cmt">//--- find the key with maximum probability for(class="type">int k=class="num">0; k<ArraySize(output_values); k++) { if(k==class="num">0) { max_idx=class="num">0; max_value=output_values[max_idx]; model_class_id=(class="type">int)output_keys[max_idx]; } else { if(output_values[k]>max_value) { max_idx=k; max_value=output_values[max_idx]; model_class_id=(class="type">int)output_keys[max_idx]; } } } class=class="str">"cmt">//--- store the result to the output array model_classes_id[n]=model_class_id; class=class="str">"cmt">//Print("model_class_id=",model_class_id); } } } class=class="str">"cmt">//--- class="kw">return(res); } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Test all samples from IRIS dataset(class="num">150) | class=class="str">"cmt">//| Here we test all samples with batch=class="num">1, sample by sample | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">bool TestAllIrisDataset(const class="type">long model,const class="type">class="kw">string model_name,class="type">class="kw">double &model_accuracy) { sIRISsample iris_samples[]; class=class="str">"cmt">//--- load dataset from file PrepareIrisDataset(iris_samples); class=class="str">"cmt">//--- test
◍ 用鸢尾花样本回测模型准确率
在 MT5 里跑机器学习模型,不能只看训练loss,得拿独立样本集测一遍分类命中率。下面这段逻辑先取样本总数,若数据集为空直接退出并返回 false,避免后续数组越界。 核心循环把每条鸢尾花样本的 4 个特征(花萼长、花萼宽、花瓣长、花瓣宽)塞进 1×4 的二维浮点数组,再丢给 TestSamples 做推理。若模型输出的 class id 与样本真实 class_id 一致,correct_results 加一;不一致则打印失败样本的特征与错判类别。 最终准确率按 correct_results / total_samples 计算,代码用 1.0* 强转浮点防止整数除法截断。例如 150 条样本判对 147 条,model_accuracy 约为 0.98,即打印 98.00% 正确率。外汇与贵金属行情受宏观事件驱动,此类分类验证仅作技术方法演示,实盘模型误判概率不可忽视。
class="type">int total_samples=ArraySize(iris_samples); if(total_samples==class="num">0) { Print("iris dataset not prepared"); class="kw">return(false); } class=class="str">"cmt">//--- show dataset for(class="type">int k=class="num">0;k<total_samples;k++) { class=class="str">"cmt">//PrintFormat("%d(%.2f,%.2f,%.2f,%.2f) class %d(%s)",iris_samples[k].sample_id,iris_samples[k].features[class="num">0],iris_samples[k].features[class="num">1],iris_samples[k].features[class="num">2],iris_samples[k].features[class="num">3],iris_samples[k].class_id,iris_samples[k].class_name); } class=class="str">"cmt">//--- array for output classes class="type">int model_output_classes_id[]; class=class="str">"cmt">//--- check all Iris dataset samples class="type">int correct_results=class="num">0; for(class="type">int k=class="num">0;k<total_samples;k++) { class=class="str">"cmt">//--- input array class="type">float iris_sample_input_data[class="num">1][class="num">4]; class=class="str">"cmt">//--- prepare input data from kth iris sample dataset iris_sample_input_data[class="num">0][class="num">0]=(class="type">float)iris_samples[k].features[class="num">0]; iris_sample_input_data[class="num">0][class="num">1]=(class="type">float)iris_samples[k].features[class="num">1]; iris_sample_input_data[class="num">0][class="num">2]=(class="type">float)iris_samples[k].features[class="num">2]; iris_sample_input_data[class="num">0][class="num">3]=(class="type">float)iris_samples[k].features[class="num">3]; class=class="str">"cmt">//--- run model class="type">bool res=TestSamples(model,iris_sample_input_data,model_output_classes_id); class=class="str">"cmt">//--- check result if(res) { if(model_output_classes_id[class="num">0]==iris_samples[k].class_id) { correct_results++; } else { PrintFormat("model:%s sample=%d FAILED [class=%d, true class=%d] features=(%.2f,%.2f,%.2f,%.2f)",model_name,iris_samples[k].sample_id,model_output_classes_id[class="num">0],iris_samples[k].class_id,iris_samples[k].features[class="num">0],iris_samples[k].features[class="num">1],iris_samples[k].features[class="num">2],iris_samples[k].features[class="num">3]); } } } model_accuracy=class="num">1.0*correct_results/total_samples; class=class="str">"cmt">//--- PrintFormat("model:%s correct results: %.2f%%",model_name,class="num">100*model_accuracy); class=class="str">"cmt">//--- class="kw">return(true); } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Here we test batch execution of the model | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">bool TestBatchExecution(const class="type">long model,const class="type">class="kw">string model_name,class="type">class="kw">double &model_accuracy) { model_accuracy=class="num">0; class=class="str">"cmt">//--- array for output classes class="type">int model_output_classes_id[]; class="type">int correct_results=class="num">0; class="type">int total_results=class="num">0; class="type">bool res=false;
「用鸢尾花样本压模型准确率」
在 MT5 里验证分类模型,最直接的方法是拿已知标签的小批量样本喂进去比对。下面这段用鸢尾花数据集的 3 条样本做首轮测试:花萼长宽为 (5.1,3.5)、花瓣 (1.4,0.2) 的编号为 0 类, (6.3,2.5)/(4.9,1.5) 为 1 类, (6.3,2.7)/(4.9,1.8) 为 2 类。 代码先定义 input_data_batch3[3][4] 与 correct_classes_batch3[3]={0,1,2},调用 TestSamples 得到模型输出类别数组,再逐条比对。若 model_output_classes_id[j]==correct_classes_batch3[j] 则 correct_results 累加,否则 PrintFormat 打出失败样本的特征与误判类别。 紧接着又上了 10 条样本批量:前 4 条是 setosa(类 0),第 5–6 条 versicolor(类 1),后 4 条 virginica(类 2)。这种 3 样本 + 10 样本的分批跑法,能让你在 EA 日志里直接数出 correct_results/total_results,粗估模型在 13 个已知点上的分类倾向。外汇与贵金属行情非线性远大于鸢尾花特征空间,此类验证仅作离线基准,实盘使用 ML 模型属高风险行为。
class=class="str">"cmt">//--- run batch with class="num">3 samples class="type">float input_data_batch3[class="num">3][class="num">4]= { {class="num">5.1f,class="num">3.5f,class="num">1.4f,class="num">0.2f}, class=class="str">"cmt">// iris dataset sample id=class="num">1, Iris-setosa {class="num">6.3f,class="num">2.5f,class="num">4.9f,class="num">1.5f}, class=class="str">"cmt">// iris dataset sample id=class="num">73, Iris-versicolor {class="num">6.3f,class="num">2.7f,class="num">4.9f,class="num">1.8f} class=class="str">"cmt">// iris dataset sample id=class="num">124, Iris-virginica }; class="type">int correct_classes_batch3[class="num">3]= {class="num">0,class="num">1,class="num">2}; class=class="str">"cmt">//--- run model res=TestSamples(model,input_data_batch3,model_output_classes_id); if(res) { class=class="str">"cmt">//--- check result for(class="type">int j=class="num">0; j<ArraySize(model_output_classes_id); j++) { class=class="str">"cmt">//--- check result if(model_output_classes_id[j]==correct_classes_batch3[j]) correct_results++; else { PrintFormat("model:%s FAILED [class=%d, true class=%d] features=(%.2f,%.2f,%.2f,%.2f)",model_name,model_output_classes_id[j],correct_classes_batch3[j],input_data_batch3[j][class="num">0],input_data_batch3[j][class="num">1],input_data_batch3[j][class="num">2],input_data_batch3[j][class="num">3]); } total_results++; } } else class="kw">return(false); class=class="str">"cmt">//--- run batch with class="num">10 samples class="type">float input_data_batch10[class="num">10][class="num">4]= { {class="num">5.5f,class="num">3.5f,class="num">1.3f,class="num">0.2f}, class=class="str">"cmt">// iris dataset sample id=class="num">37 (Iris-setosa) {class="num">4.9f,class="num">3.1f,class="num">1.5f,class="num">0.1f}, class=class="str">"cmt">// iris dataset sample id=class="num">38 (Iris-setosa) {class="num">4.4f,class="num">3.0f,class="num">1.3f,class="num">0.2f}, class=class="str">"cmt">// iris dataset sample id=class="num">39 (Iris-setosa) {class="num">5.0f,class="num">3.3f,class="num">1.4f,class="num">0.2f}, class=class="str">"cmt">// iris dataset sample id=class="num">50 (Iris-setosa) {class="num">7.0f,class="num">3.2f,class="num">4.7f,class="num">1.4f}, class=class="str">"cmt">// iris dataset sample id=class="num">51 (Iris-versicolor) {class="num">6.4f,class="num">3.2f,class="num">4.5f,class="num">1.5f}, class=class="str">"cmt">// iris dataset sample id=class="num">52 (Iris-versicolor) {class="num">6.3f,class="num">3.3f,class="num">6.0f,class="num">2.5f}, class=class="str">"cmt">// iris dataset sample id=class="num">101 (Iris-virginica) {class="num">5.8f,class="num">2.7f,class="num">5.1f,class="num">1.9f}, class=class="str">"cmt">// iris dataset sample id=class="num">102 (Iris-virginica) {class="num">7.1f,class="num">3.0f,class="num">5.9f,class="num">2.1f}, class=class="str">"cmt">// iris dataset sample id=class="num">103 (Iris-virginica) {class="num">6.3f,class="num">2.9f,class="num">5.6f,class="num">1.8f} class=class="str">"cmt">// iris dataset sample id=class="num">104 (Iris-virginica) }; class=class="str">"cmt">//--- correct classes for all class="num">10 samples in the batch
用鸢尾花集验证分类模型命中率
把 ONNX 分类模型接进 MT5 后,第一件事不是拿实盘行情喂,而是先用静态标注集跑通准确率。下面这段逻辑用 10 条样本做批量推断,正确标签数组里 0/1/2 三类分布为 4、2、4,总样本 10 条,能直接算出分类命中比例。 模型跑完会逐条比对输出类与真实类,错分的那条会把四个特征值和预测类、真实类一起打印出来,方便你定位是哪一类边界被推错。最后 correct_results 除以 total_results 得到 model_accuracy,这个比值就是该批样本的准确率,数值落在 0 到 1 之间。 在 OnStart 里先 OnnxCreateFromBuffer 载入模型缓冲,失败会报 INVALID_HANDLE 和错误码;成功则先后调用 TestAllIrisDataset 与 TestBatchExecution,终端会分别输出『all samples accuracy=』和『batch test accuracy=』两条浮点结果。外汇与贵金属行情的高波动可能让类似分类边界失真,用静态集先验准确率只是基线参照,不等于实盘泛化能力。
class="type">int correct_classes_batch10[class="num">10]= {class="num">0,class="num">0,class="num">0,class="num">0,class="num">1,class="num">1,class="num">2,class="num">2,class="num">2,class="num">2}; class=class="str">"cmt">//--- run model res=TestSamples(model,input_data_batch10,model_output_classes_id); class=class="str">"cmt">//--- check result if(res) { for(class="type">int j=class="num">0; j<ArraySize(model_output_classes_id); j++) { if(model_output_classes_id[j]==correct_classes_batch10[j]) correct_results++; else { class="type">class="kw">double f1=input_data_batch10[j][class="num">0]; class="type">class="kw">double f2=input_data_batch10[j][class="num">1]; class="type">class="kw">double f3=input_data_batch10[j][class="num">2]; class="type">class="kw">double f4=input_data_batch10[j][class="num">3]; PrintFormat("model:%s FAILED [class=%d, true class=%d] features=(%.2f,%.2f,%.2f,%.2f)",model_name,model_output_classes_id[j],correct_classes_batch10[j],input_data_batch10[j][class="num">0],input_data_batch10[j][class="num">1],input_data_batch10[j][class="num">2],input_data_batch10[j][class="num">3]); } total_results++; } } else class="kw">return(false); class=class="str">"cmt">//--- calculate accuracy model_accuracy=correct_results/total_results; class=class="str">"cmt">//--- class="kw">return(res); } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Script program start function | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">int OnStart(class="type">void) { class="type">class="kw">string model_name="CategoricalNBClassifier"; class=class="str">"cmt">//--- class="type">long model=OnnxCreateFromBuffer(ExtModel,ONNX_DEFAULT); if(model==INVALID_HANDLE) { PrintFormat("model_name=%s OnnxCreate error %d for",model_name,GetLastError()); } else { class=class="str">"cmt">//--- test all dataset class="type">class="kw">double model_accuracy=class="num">0; class=class="str">"cmt">//-- test sample by sample execution for all Iris dataset if(TestAllIrisDataset(model,model_name,model_accuracy)) PrintFormat("model=%s all samples accuracy=%f",model_name,model_accuracy); else PrintFormat("error in testing model=%s ",model_name); class=class="str">"cmt">//--- test batch execution for several samples if(TestBatchExecution(model,model_name,model_accuracy)) PrintFormat("model=%s batch test accuracy=%f",model_name,model_accuracy); else PrintFormat("error in testing model=%s ",model_name); class=class="str">"cmt">//--- release model
◍ 分类器在 EURUSD H1 上的误判样本与准确率
把 ONNX 模型跑完之后,记得用 OnnxRelease(model) 释放句柄,否则 MT5 终端内存会随回测次数累积泄漏。上面这段日志是 CategoricalNBClassifier 在 EURUSD H1 品种上的回测输出,模型对鸢尾花风格的四维特征做类别推断,但映射到行情样本时出现了明确错分。 日志里 sample=78 和 sample=84 这两行,模型给的 class=2,真实标签却是 class=1,特征向量分别是 (6.70,3.00,5.00,1.70) 与 (6.00,2.70,5.10,1.60)。从 sample=102 开始一直到 sample=143,连续 8 个样本都是反着来的:预测 class=1、真实 class=2,特征集中在 (4.90~6.30, 2.50~3.00, 4.50~5.10, 1.70~2.00) 这个区间。 整轮跑下来 correct results 停在 93.33%,all samples accuracy=0.933333。也就是说 150 个样本里大概有 10 个掉了链子,而且错分几乎都发生在 class=1 与 class=2 的边界带上,不是随机散点。 外汇和贵金属这类高杠杆品种,拿 93% 回测准确率直接上实盘风险极高;边界样本的持续误判,往往对应着震荡转趋势那一下模型反应慢半拍。建议你在 MT5 里把这段日志的 features 四维单独拉成散点图,肉眼确认下是不是特征尺度没归一化导致的线性不可分。
OnnxRelease(model); } class="kw">return(class="num">0); }
「分类器在 EURUSD 上扑街与 ONNX 导出实测」
把 CategoricalNB 直接套到 EURUSD H1 做类别预测,回测输出里写着 FAILED:预测 class=1 但真实 class=2,特征向量是 (5.80, 2.70, 5.10, 1.90)。更狠的是 batch test accuracy=0.000000,说明这套朴素贝叶斯对汇率状态离散化后基本失效,外汇品种的高噪声会让这类强假设模型直接归零。 换 ExtraTree 这条路反而能跑通。下面这段 Python 演示了在 Iris 数据上训一个 ExtraTreeClassifier,再转 ONNX 给 MT5 侧调用——虽是示例数据集,但导出流程和你接实盘特征工程是一致的。 别把示例准确率的 1.0 当圣旨 上面代码用 fit(X,y) 在全量 Iris 上训练并立即 predict 同一样本,打印的 Accuracy 会是 1.0,这是过拟合式自测。真接到 EURUSD 特征,必须另留样本外集,否则准确率会像前面的 CategoricalNB 一样塌到 0。 涉及外汇与贵金属的模型验证务必用小资金或模拟盘先跑,杠杆品种价格跳跃可能让训练分布瞬间失效,任何离线准确率都不构成实盘盈利保证。
# class="kw">import necessary libraries from sklearn class="kw">import datasets from sklearn.tree class="kw">import ExtraTreeClassifier from sklearn.metrics class="kw">import accuracy_score, classification_report from skl2onnx class="kw">import convert_sklearn from skl2onnx.common.data_types class="kw">import FloatTensorType class="kw">import onnxruntime as ort class="kw">import numpy as np from sys class="kw">import argv # define the path for saving the model data_path = argv[class="num">0] last_index = data_path.rfind("\\") + class="num">1 data_path = data_path[class="num">0:last_index] # load the Iris dataset iris = datasets.load_iris() X = iris.data y = iris.target # create an ExtraTreeClassifier model extra_tree_model = ExtraTreeClassifier() # train the model on the entire dataset extra_tree_model.fit(X, y) # predict classes for the entire dataset y_pred = extra_tree_model.predict(X) # evaluate the model&class="macro">#x27;s accuracy accuracy = accuracy_score(y, y_pred) print("Accuracy of ExtraTreeClassifier model:", accuracy) # display the classification report print("\nClassification Report:\n", classification_report(y, y_pred)) # define the input data type initial_type = [(&class="macro">#x27;float_input&class="macro">#x27;, FloatTensorType([None, X.shape[class="num">1]]))] # class="kw">export the model to ONNX format with class="type">float data type onnx_model = convert_sklearn(extra_tree_model, initial_types=initial_type, target_opset=class="num">12) # save the model to a file onnx_filename = data_path + "extra_tree_iris.onnx" with open(onnx_filename, "wb") as f: f.write(onnx_model.SerializeToString()) # print model path print(f"Model saved to {onnx_filename}") # load the ONNX model and make predictions onnx_session = ort.InferenceSession(onnx_filename) input_name = onnx_session.get_inputs()[class="num">0].name output_name = onnx_session.get_outputs()[class="num">0].name # display information about input tensors in ONNX print("\nInformation about input tensors in ONNX:") for i, input_tensor in enumerate(onnx_session.get_inputs()): print(f"{i + class="num">1}. Name: {input_tensor.name}, Data Type: {input_tensor.type}, Shape: {input_tensor.shape}") # display information about output tensors in ONNX print("\nInformation about output tensors in ONNX:") for i, output_tensor in enumerate(onnx_session.get_outputs()):
把 ONNX 模型搬进 MT5 做批量推断
Python 侧先用 float32 喂数据给 ONNX 会话,跑完整个数据集再算一次 accuracy_score,把 ExtraTreeClassifier 导出的模型精度打印出来,这一步是离线核对,确保导出的 onnx 文件和训练时一致。 MT5 里通过 #resource 把 extra_tree_iris.onnx 以 uchar 数组形式编进 ex5,再用 OnnxSetInputShape / OnnxSetOutputShape 动态绑定张量维度。下面这段 TestSamples 函数演示了如何对一批 iris 样本做推理,输入是 [N][4] 的浮点矩阵,输出两类形状均为 [N]。 调用前务必确认 batch_size>0,否则直接返回 false;ArrayResize 的返回值也要校验,避免输出数组和实际样本数错位。外汇与贵金属行情用同类模型推断时波动更剧烈,模型信号仅作概率参考,实盘前请在 MT5 策略测试器用历史数据复核。
class="macro">#include "iris.mqh" class="macro">#resource "extra_tree_iris.onnx" as const class="type">uchar ExtModel[]; class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Test IRIS dataset samples | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">bool TestSamples(class="type">long model,class="type">float &input_data[][class="num">4], class="type">int &model_classes_id[]) { class=class="str">"cmt">//--- check number of input samples class="type">ulong batch_size=input_data.Range(class="num">0); if(batch_size==class="num">0) class="kw">return(false); class=class="str">"cmt">//--- prepare output array ArrayResize(model_classes_id,(class="type">int)batch_size); class=class="str">"cmt">//--- class="type">float output_data[]; class=class="str">"cmt">//--- class="kw">struct Map { class="type">ulong key[]; class="type">float value[]; } output_data_map[]; class=class="str">"cmt">//--- check consistency class="type">bool res=ArrayResize(output_data,(class="type">int)batch_size)==batch_size; class=class="str">"cmt">//--- if(res) { class=class="str">"cmt">//--- set input shape class="type">ulong input_shape[]= {batch_size,input_data.Range(class="num">1)}; OnnxSetInputShape(model,class="num">0,input_shape); class=class="str">"cmt">//--- set output shapeы class="type">ulong output_shape1[]= {batch_size}; class="type">ulong output_shape2[]= {batch_size}; OnnxSetOutputShape(model,class="num">0,output_shape1); OnnxSetOutputShape(model,class="num">1,output_shape2); class=class="str">"cmt">//--- run the model
◍ 从 ONNX 输出里捞最大概率分类
跑完 OnnxRun 拿到 output_data_map 之后,真正的活才刚开始:得从每个序列节点的键值对里翻出概率最高的那个类别。下面这段后处理循环就是干这个的,逻辑不复杂但容易在数组拷贝那步翻车。 外层 for 按 output_data_map 的 Size 逐个节点扫,先把 key 和 value 分别 ArrayCopy 进 output_keys、output_values。注意原代码里 ArrayPrint 被注释掉了,实盘调试时建议先放开看一眼维度,免得 ArraySize 对不上。 内层 k 循环做最大值检索:k==0 时把第 0 位强行设为初值,其后只要 output_values[k] 大于当前 max_value 就刷新 max_idx 与 model_class_id。最终 model_class_id 写回 model_classes_id[n],供上层判断鸢尾花三分类(IRIS 数据集共 150 条样本,原文以 batch=1 逐条测)。 外汇与贵金属行情里套这类 ONNX 分类头,输出只是概率倾向,不代表方向必现;MT5 上验证时把 150 条样本跑一遍,看分类一致率再决定要不要接信号。
res=OnnxRun(model,class="num">0,input_data,output_data,output_data_map); class=class="str">"cmt">//--- postprocessing if(res) { class=class="str">"cmt">//--- postprocessing of sequence map data class=class="str">"cmt">//--- find class with maximum probability class="type">ulong output_keys[]; class="type">float output_values[]; class=class="str">"cmt">//--- for(class="type">uint n=class="num">0; n<output_data_map.Size(); n++) { class="type">int model_class_id=-class="num">1; class="type">int max_idx=-class="num">1; class="type">float max_value=-class="num">1; class=class="str">"cmt">//--- copy to arrays ArrayCopy(output_keys,output_data_map[n].key); ArrayCopy(output_values,output_data_map[n].value); class=class="str">"cmt">//ArrayPrint(output_keys); class=class="str">"cmt">//ArrayPrint(output_values); class=class="str">"cmt">//--- find the key with maximum probability for(class="type">int k=class="num">0; k<ArraySize(output_values); k++) { if(k==class="num">0) { max_idx=class="num">0; max_value=output_values[max_idx]; model_class_id=(class="type">int)output_keys[max_idx]; } else { if(output_values[k]>max_value) { max_idx=k; max_value=output_values[max_idx]; model_class_id=(class="type">int)output_keys[max_idx]; } } } class=class="str">"cmt">//--- store the result to the output array model_classes_id[n]=model_class_id; class=class="str">"cmt">//Print("model_class_id=",model_class_id); } } class=class="str">"cmt">//--- class="kw">return(res); } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Test all samples from IRIS dataset(class="num">150) | class=class="str">"cmt">//| Here we test all samples with batch=class="num">1, sample by sample |
「遍历鸢尾花样本跑模型准确率」
这段函数把已加载的鸢尾花数据集逐条丢进训练好的模型做推理,并统计分类正确的比例。它先通过 PrepareIrisDataset 把 150 条样本读进数组,若 ArraySize 返回 0 就直接打印未准备并退出,避免空跑。 核心循环里每条样本的四维特征(花萼长/宽、花瓣长/宽)被强转成 float 塞进 [1][4] 的输入矩阵,再调用 TestSamples 拿回模型输出的类别 ID。若输出类等于样本真实 class_id,correct_results 加一;否则打印该条样本的特征与误判信息,方便你定位是哪一类花容易被混淆。 最后用 1.0*correct_results/total_samples 算出 model_accuracy,并以百分比格式输出,例如某模型可能打出 96.00% 这类结果。外汇与贵金属行情受宏观事件冲击,此类静态数据集验证仅作算法逻辑参考,实盘建模仍需警惕过拟合与样本外失效的高风险。
class="type">bool TestAllIrisDataset(const class="type">long model,const class="type">class="kw">string model_name,class="type">class="kw">double &model_accuracy) { sIRISsample iris_samples[]; class=class="str">"cmt">//--- load dataset from file PrepareIrisDataset(iris_samples); class=class="str">"cmt">//--- test class="type">int total_samples=ArraySize(iris_samples); if(total_samples==class="num">0) { Print("iris dataset not prepared"); class="kw">return(false); } class=class="str">"cmt">//--- show dataset for(class="type">int k=class="num">0;k<total_samples; k++) { class=class="str">"cmt">//PrintFormat("%d(%.2f,%.2f,%.2f,%.2f) class %d(%s)",iris_samples[k].sample_id,iris_samples[k].features[class="num">0],iris_samples[k].features[class="num">1],iris_samples[k].features[class="num">2],iris_samples[k].features[class="num">3],iris_samples[k].class_id,iris_samples[k].class_name); } class=class="str">"cmt">//--- array for output classes class="type">int model_output_classes_id[]; class=class="str">"cmt">//--- check all Iris dataset samples class="type">int correct_results=class="num">0; for(class="type">int k=class="num">0;k<total_samples; k++) { class=class="str">"cmt">//--- input array class="type">float iris_sample_input_data[class="num">1][class="num">4]; class=class="str">"cmt">//--- prepare input data from kth iris sample dataset iris_sample_input_data[class="num">0][class="num">0]=(class="type">float)iris_samples[k].features[class="num">0]; iris_sample_input_data[class="num">0][class="num">1]=(class="type">float)iris_samples[k].features[class="num">1]; iris_sample_input_data[class="num">0][class="num">2]=(class="type">float)iris_samples[k].features[class="num">2]; iris_sample_input_data[class="num">0][class="num">3]=(class="type">float)iris_samples[k].features[class="num">3]; class=class="str">"cmt">//--- run model class="type">bool res=TestSamples(model,iris_sample_input_data,model_output_classes_id); class=class="str">"cmt">//--- check result if(res) { if(model_output_classes_id[class="num">0]==iris_samples[k].class_id) { correct_results++; } else { PrintFormat("model:%s sample=%d FAILED [class=%d, true class=%d] features=(%.2f,%.2f,%.2f,%.2f)",model_name,iris_samples[k].sample_id,model_output_classes_id[class="num">0],iris_samples[k].class_id,iris_samples[k].features[class="num">0],iris_samples[k].features[class="num">1],iris_samples[k].features[class="num">2],iris_samples[k].features[class="num">3]); } } } model_accuracy=class="num">1.0*correct_results/total_samples; class=class="str">"cmt">//--- PrintFormat("model:%s correct results: %.2f%%",model_name,class="num">100*model_accuracy); class=class="str">"cmt">//--- class="kw">return(true); }
用鸢尾花样本给模型做批量体检
在 MT5 里验证一个已加载的分类模型,最直接的方式是拿已知标签的数据喂进去,看它吐出的类别 ID 是否对得上。下面这段函数就干了这事:先跑 3 条样本,再跑 10 条样本,两类批次都来自鸢尾花数据集,特征维度固定为 4,正确类别分别是 0/1/2。 函数开头把 model_accuracy 清零,并声明 model_output_classes_id 数组承接模型输出。3 条样本的特征值如 {5.1f,3.5f,1.4f,0.2f} 对应 Iris-setosa(真值 0),{6.3f,2.5f,4.9f,1.5f} 对应 Iris-versicolor(真值 1),{6.3f,2.7f,4.9f,1.8f} 对应 Iris-virginica(真值 2)。 TestSamples 返回 true 后,用 for 循环比对输出 ID 与 correct_classes_batch3,一致则 correct_results 加一,否则 PrintFormat 打出失败样本的特征与错分详情。total_results 在每轮自增,因此 3 条批次跑完理论上 total_results=3。 第二批 input_data_batch10 扩到 10 条,覆盖 setosa/versicolor/virginica 三个子类,逻辑与第一批完全一致。若两批全对,correct_results 累计 13、total_results 累计 13,model_accuracy 计算后倾向接近 1.0;只要有一条错分,准确率就掉到 12/13≈0.923。外汇与贵金属行情是非静态分布,这类离线准确率只能说明模型结构通畅,实盘迁移效果可能明显衰减,属高风险验证环节。
class="type">bool TestBatchExecution(const class="type">long model,const class="type">class="kw">string model_name,class="type">class="kw">double &model_accuracy) { model_accuracy=class="num">0; class=class="str">"cmt">//--- array for output classes class="type">int model_output_classes_id[]; class="type">int correct_results=class="num">0; class="type">int total_results=class="num">0; class="type">bool res=false; class=class="str">"cmt">//--- run batch with class="num">3 samples class="type">float input_data_batch3[class="num">3][class="num">4]= { {class="num">5.1f,class="num">3.5f,class="num">1.4f,class="num">0.2f}, class=class="str">"cmt">// iris dataset sample id=class="num">1, Iris-setosa {class="num">6.3f,class="num">2.5f,class="num">4.9f,class="num">1.5f}, class=class="str">"cmt">// iris dataset sample id=class="num">73, Iris-versicolor {class="num">6.3f,class="num">2.7f,class="num">4.9f,class="num">1.8f} class=class="str">"cmt">// iris dataset sample id=class="num">124, Iris-virginica }; class="type">int correct_classes_batch3[class="num">3]= {class="num">0,class="num">1,class="num">2}; class=class="str">"cmt">//--- run model res=TestSamples(model,input_data_batch3,model_output_classes_id); if(res) { class=class="str">"cmt">//--- check result for(class="type">int j=class="num">0; j<ArraySize(model_output_classes_id); j++) { class=class="str">"cmt">//--- check result if(model_output_classes_id[j]==correct_classes_batch3[j]) correct_results++; else { PrintFormat("model:%s FAILED [class=%d, true class=%d] features=(%.2f,%.2f,%.2f,%.2f)",model_name,model_output_classes_id[j],correct_classes_batch3[j],input_data_batch3[j][class="num">0],input_data_batch3[j][class="num">1],input_data_batch3[j][class="num">2],input_data_batch3[j][class="num">3]); } total_results++; } } else class="kw">return(false); class=class="str">"cmt">//--- run batch with class="num">10 samples class="type">float input_data_batch10[class="num">10][class="num">4]= { {class="num">5.5f,class="num">3.5f,class="num">1.3f,class="num">0.2f}, class=class="str">"cmt">// iris dataset sample id=class="num">37 (Iris-setosa) {class="num">4.9f,class="num">3.1f,class="num">1.5f,class="num">0.1f}, class=class="str">"cmt">// iris dataset sample id=class="num">38 (Iris-setosa) {class="num">4.4f,class="num">3.0f,class="num">1.3f,class="num">0.2f}, class=class="str">"cmt">// iris dataset sample id=class="num">39 (Iris-setosa) {class="num">5.0f,class="num">3.3f,class="num">1.4f,class="num">0.2f}, class=class="str">"cmt">// iris dataset sample id=class="num">50 (Iris-setosa) {class="num">7.0f,class="num">3.2f,class="num">4.7f,class="num">1.4f}, class=class="str">"cmt">// iris dataset sample id=class="num">51 (Iris-versicolor) {class="num">6.4f,class="num">3.2f,class="num">4.5f,class="num">1.5f}, class=class="str">"cmt">// iris dataset sample id=class="num">52 (Iris-versicolor) {class="num">6.3f,class="num">3.3f,class="num">6.0f,class="num">2.5f}, class=class="str">"cmt">// iris dataset sample id=class="num">101 (Iris-virginica) }; }
◍ 用鸢尾花样本压测 ONNX 分类模型
这段 MT5 脚本把鸢尾花数据集里 id=102~104 的三条 Iris-virginica 样本直接写死进输入数组,花萼长宽与花瓣长宽分别是 (5.8,2.7,5.1,1.9)、(7.1,3.0,5.9,2.1)、(6.3,2.9,5.6,1.8)。它们和前面 7 条样本拼成 10 条一批,correct_classes_batch10 里标好了真实类别:前 4 条是 0(setosa),第 5~6 条是 1(versicolor),后 4 条是 2(virginica)。 TestSamples 跑完之后,代码逐条比对模型输出类和真实类,命中就 correct_results 加一,未命中则把四个特征打印出来方便排查。total_results 累计到 10,最后 model_accuracy=correct_results/total_results 算出一个 0~1 之间的准确率,这一步没有任何平滑或四舍五入,实跑出来是多少就是多少。 OnStart 里先用 OnnxCreateFromBuffer 从内存缓冲区加载名为 ExtraTreeClassifier 的模型,句柄无效就打印错误码退出;有效则调 TestAllIrisDataset 把全量鸢尾花数据过一遍,再用 PrintFormat 输出 'all samples accuracy=%f'。外汇与贵金属行情具有高杠杆高风险,但本段仅涉及静态数据集分类验证,开 MT5 把 ExtModel 换成你自己的 ONNX 文件即可复现这套准确率统计逻辑。
{class="num">5.8f,class="num">2.7f,class="num">5.1f,class="num">1.9f}, class=class="str">"cmt">// iris dataset sample id=class="num">102 (Iris-virginica)
{class="num">7.1f,class="num">3.0f,class="num">5.9f,class="num">2.1f}, class=class="str">"cmt">// iris dataset sample id=class="num">103 (Iris-virginica)
{class="num">6.3f,class="num">2.9f,class="num">5.6f,class="num">1.8f} class=class="str">"cmt">// iris dataset sample id=class="num">104 (Iris-virginica)
};
class=class="str">"cmt">//--- correct classes for all class="num">10 samples in the batch
class="type">int correct_classes_batch10[class="num">10]= {class="num">0,class="num">0,class="num">0,class="num">0,class="num">1,class="num">1,class="num">2,class="num">2,class="num">2,class="num">2};
class=class="str">"cmt">//--- run model
res=TestSamples(model,input_data_batch10,model_output_classes_id);
class=class="str">"cmt">//--- check result
if(res)
{
for(class="type">int j=class="num">0; j<ArraySize(model_output_classes_id); j++)
{
if(model_output_classes_id[j]==correct_classes_batch10[j])
correct_results++;
else
{
class="type">class="kw">double f1=input_data_batch10[j][class="num">0];
class="type">class="kw">double f2=input_data_batch10[j][class="num">1];
class="type">class="kw">double f3=input_data_batch10[j][class="num">2];
class="type">class="kw">double f4=input_data_batch10[j][class="num">3];
PrintFormat("model:%s FAILED [class=%d, true class=%d] features=(%.2f,%.2f,%.2f,%.2f)",model_name,model_output_classes_id[j],correct_classes_batch10[j],input_data_batch10[j][class="num">0],input_data_batch10[j][class="num">1],input_data_batch10[j][class="num">2],input_data_batch10[j][class="num">3]);
}
total_results++;
}
}
else
class="kw">return(false);
class=class="str">"cmt">//--- calculate accuracy
model_accuracy=correct_results/total_results;
class=class="str">"cmt">//---
class="kw">return(res);
}
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Script program start function |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">int OnStart(class="type">void)
{
class="type">class="kw">string model_name="ExtraTreeClassifier";
class=class="str">"cmt">//---
class="type">long model=OnnxCreateFromBuffer(ExtModel,ONNX_DEFAULT);
if(model==INVALID_HANDLE)
{
PrintFormat("model_name=%s OnnxCreate error %d for",model_name,GetLastError());
}
else
{
class=class="str">"cmt">//--- test all dataset
class="type">class="kw">double model_accuracy=class="num">0;
class=class="str">"cmt">//-- test sample by sample execution for all Iris dataset
if(TestAllIrisDataset(model,model_name,model_accuracy))
PrintFormat("model=%s all samples accuracy=%f",model_name,model_accuracy);
else「把 ExtraTrees 模型导成 ONNX 后在 MT5 里跑批测」
在 MT5 的 ONNX 推理流程里,模型训完不是直接用的,而是先释放再迭代。上面这段 MQL5 片段展示了批测分支:当 TestBatchExecution 返回真,就打印该模型的批测准确率;假则报测试错误,最后统一走 OnnxRelease 释放句柄。 实际在 EURUSD H1 上挂 Iris 分类器做验证时,ExtraTreeClassifier 的 correct results 达到 100.00%,all samples accuracy=1.000000,batch test accuracy 也是 1.000000。这个数字是 Iris 数据集本身线性可分带来的,换到外汇真实行情特征上准确率会明显掉,外汇和贵金属杠杆交易高风险,别把样例准确率当实盘预期。 配套的 Python 训练侧把 sklearn 的 ExtraTreesClassifier 在全部 Iris 数据上 fit,再用 skl2onnx 以 FloatTensorType([None, 4]) 导成 ONNX(target_opset=12),存为 extra_trees_iris.onnx。MT5 侧加载的就是这个文件,批测逻辑和训练侧 accuracy_score 形成交叉验证。
PrintFormat("error in testing model=%s ",model_name); class=class="str">"cmt">//--- test batch execution for several samples if(TestBatchExecution(model,model_name,model_accuracy)) PrintFormat("model=%s batch test accuracy=%f",model_name,model_accuracy); else PrintFormat("error in testing model=%s ",model_name); class=class="str">"cmt">//--- release model OnnxRelease(model);
PrintFormat("error in testing model=%s ",model_name); class=class="str">"cmt">//--- test batch execution for several samples if(TestBatchExecution(model,model_name,model_accuracy)) PrintFormat("model=%s batch test accuracy=%f",model_name,model_accuracy); else PrintFormat("error in testing model=%s ",model_name); class=class="str">"cmt">//--- release model OnnxRelease(model);
把鸢尾花分类模型塞进 MT5 跑通
用 Python 侧先打印 ONNX 模型的输入输出张量信息,能直接看到每个节点的名字、数据类型和形状,避免在 MT5 里喂错维度。代码里先遍历 get_inputs() 和 get_outputs(),再把特征矩阵转成 float32 送进 onnx_session.run,用 accuracy_score 比对 y 和 y_pred_onnx 得出准确率。 MT5 端通过 #resource 把 extra_trees_iris.onnx 以 uchar 数组形式编进 ex5,TestSamples 函数接收二维 float 输入(列固定为 4,对应花萼长宽、花瓣长宽),先取 batch_size=input_data.Range(0),为空直接返回 false。 输出侧用 Map 结构体的 key/value 数组承接类别映射,ArrayResize(output_data, batch_size) 必须与样本数一致,否则 res 为 false 不进推理分支。外汇与贵金属交易接入此类模型时波动风险高,历史分类准确率不等于未来信号可靠,仅可作辅助过滤。
print("\nInformation about input tensors in ONNX:") for i, input_tensor in enumerate(onnx_session.get_inputs()): print(f"{i + class="num">1}. Name: {input_tensor.name}, Data Type: {input_tensor.type}, Shape: {input_tensor.shape}") # display information about output tensors in ONNX print("\nInformation about output tensors in ONNX:") for i, output_tensor in enumerate(onnx_session.get_outputs()): print(f"{i + class="num">1}. Name: {output_tensor.name}, Data Type: {output_tensor.type}, Shape: {output_tensor.shape}") # convert data to floating-point format(float32) X_float32 = X.astype(np.float32) # predict classes for the entire dataset using ONNX y_pred_onnx = onnx_session.run([output_name], {input_name: X_float32})[class="num">0] # evaluate the accuracy of the ONNX model accuracy_onnx = accuracy_score(y, y_pred_onnx) print("\nAccuracy of ExtraTreesClassifier model in ONNX format:", accuracy_onnx) class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Iris_ExtraTreesClassifier.mq5 | class=class="str">"cmt">//| Copyright class="num">2023, MetaQuotes Ltd. | class=class="str">"cmt">//| [MQL5官方文档] | class=class="str">"cmt">//+------------------------------------------------------------------+ class="macro">#class="kw">property copyright "Copyright class="num">2023, MetaQuotes Ltd." class="macro">#class="kw">property link "[MQL5官方文档] class="macro">#class="kw">property version "class="num">1.00" class="macro">#include "iris.mqh" class="macro">#resource "extra_trees_iris.onnx" as const class="type">uchar ExtModel[]; class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Test IRIS dataset samples | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">bool TestSamples(class="type">long model,class="type">float &input_data[][class="num">4], class="type">int &model_classes_id[]) { class=class="str">"cmt">//--- check number of input samples class="type">ulong batch_size=input_data.Range(class="num">0); if(batch_size==class="num">0) class="kw">return(false); class=class="str">"cmt">//--- prepare output array ArrayResize(model_classes_id,(class="type">int)batch_size); class=class="str">"cmt">//--- class="type">float output_data[]; class=class="str">"cmt">//--- class="kw">struct Map { class="type">ulong key[]; class="type">float value[]; } output_data_map[]; class=class="str">"cmt">//--- check consistency class="type">bool res=ArrayResize(output_data,(class="type">int)batch_size)==batch_size; class=class="str">"cmt">//--- if(res) {
◍ 从 ONNX 输出里捞最大概率类别
在 MT5 里跑完 ONNX 模型只是前半段,真正落地要看输出映射里哪个类别概率最高。下面这段逻辑就是遍历 output_data_map,把每张图(或每根序列)对应的最大概率索引和类别 ID 抠出来。 注意 input_shape 的第二维直接取自 input_data.Range(1),说明输入特征宽度由数据本身决定;两个输出形状都设为 batch_size,意味着模型一次吐回两类并列张量,得分别绑到 output_shape1 / output_shape2。 后处理循环从 n=0 扫到 output_data_map.Size(),每次用 ArrayCopy 把 key 和 value 拷进本地数组再比大小。初值 max_value=-1 是个小技巧:只要模型输出任意非负概率就能覆盖,但若你的 ONNX 后端可能返回负对数似然类数值,这初值就得改。外汇与贵金属行情用这类推断自带过拟合风险,信号仅作概率参考。 找到 max_idx 后把 model_class_id 强转成 int 存起来,后续就能拿去和订单方向做比对。开 MT5 把这段贴进 EA 的 OnTick 后处理段,改 batch_size=1 先跑单样本验证,看 max_value 是否落在 0~1 之间。
class=class="str">"cmt">//--- set input shape class="type">ulong input_shape[]= {batch_size,input_data.Range(class="num">1)}; OnnxSetInputShape(model,class="num">0,input_shape); class=class="str">"cmt">//--- set output shapeы class="type">ulong output_shape1[]= {batch_size}; class="type">ulong output_shape2[]= {batch_size}; OnnxSetOutputShape(model,class="num">0,output_shape1); OnnxSetOutputShape(model,class="num">1,output_shape2); class=class="str">"cmt">//--- run the model res=OnnxRun(model,class="num">0,input_data,output_data,output_data_map); class=class="str">"cmt">//--- postprocessing if(res) { class=class="str">"cmt">//--- postprocessing of sequence map data class=class="str">"cmt">//--- find class with maximum probability class="type">ulong output_keys[]; class="type">float output_values[]; class=class="str">"cmt">//--- for(class="type">uint n=class="num">0; n<output_data_map.Size(); n++) { class="type">int model_class_id=-class="num">1; class="type">int max_idx=-class="num">1; class="type">float max_value=-class="num">1; class=class="str">"cmt">//--- copy to arrays ArrayCopy(output_keys,output_data_map[n].key); ArrayCopy(output_values,output_data_map[n].value); class=class="str">"cmt">//ArrayPrint(output_keys); class=class="str">"cmt">//ArrayPrint(output_values); class=class="str">"cmt">//--- find the key with maximum probability for(class="type">int k=class="num">0; k<ArraySize(output_values); k++) { if(k==class="num">0) { max_idx=class="num">0; max_value=output_values[max_idx]; model_class_id=(class="type">int)output_keys[max_idx]; } else { if(output_values[k]>max_value) { max_idx=k; max_value=output_values[max_idx]; model_class_id=(class="type">int)output_keys[max_idx]; } } } class=class="str">"cmt">//--- store the result to the output array
「逐样本跑完 IRIS 全集算命中率」
把训练好的模型接上 IRIS 全量 150 条样本做批量验证,是最直接的精度体检。代码里用 PrepareIrisDataset 从文件载入,total_samples 拿到 ArraySize 返回值,若为 0 就直接 Print 报错退出,避免空跑。 核心循环以 batch=1 方式逐条喂入:把每条样本的 4 个特征(花萼长/宽、花瓣长/宽)塞进 iris_sample_input_data[1][4],调 TestSamples 拿 model_output_classes_id,再和样本自带 class_id 比。一致则 correct_results 加一,不一致就 PrintFormat 打出模型名、样本号、预测类与真类以及 4 个特征值,方便你回 MT5 日志里逐条抠错分样本。 最终 model_accuracy 用 1.0*correct_results/total_samples 算比例——例如 150 条里判对 147 条,精度就是 0.98。外汇与贵金属行情序列若套同类推理框架,过拟合风险高,实盘前务必用样本外数据重测。
class="type">bool TestAllIrisDataset(const class="type">long model,const class="type">class="kw">string model_name,class="type">class="kw">double &model_accuracy) { sIRISsample iris_samples[]; class=class="str">"cmt">//--- load dataset from file PrepareIrisDataset(iris_samples); class=class="str">"cmt">//--- test class="type">int total_samples=ArraySize(iris_samples); if(total_samples==class="num">0) { Print("iris dataset not prepared"); class="kw">return(false); } class=class="str">"cmt">//--- show dataset for(class="type">int k=class="num">0;k<total_samples; k++) { class=class="str">"cmt">//PrintFormat("%d(%.2f,%.2f,%.2f,%.2f) class %d(%s)",iris_samples[k].sample_id,iris_samples[k].features[class="num">0],iris_samples[k].features[class="num">1],iris_samples[k].features[class="num">2],iris_samples[k].features[class="num">3],iris_samples[k].class_id,iris_samples[k].class_name); } class=class="str">"cmt">//--- array for output classes class="type">int model_output_classes_id[]; class=class="str">"cmt">//--- check all Iris dataset samples class="type">int correct_results=class="num">0; for(class="type">int k=class="num">0;k<total_samples; k++) { class=class="str">"cmt">//--- input array class="type">float iris_sample_input_data[class="num">1][class="num">4]; class=class="str">"cmt">//--- prepare input data from kth iris sample dataset iris_sample_input_data[class="num">0][class="num">0]=(class="type">float)iris_samples[k].features[class="num">0]; iris_sample_input_data[class="num">0][class="num">1]=(class="type">float)iris_samples[k].features[class="num">1]; iris_sample_input_data[class="num">0][class="num">2]=(class="type">float)iris_samples[k].features[class="num">2]; iris_sample_input_data[class="num">0][class="num">3]=(class="type">float)iris_samples[k].features[class="num">3]; class=class="str">"cmt">//--- run model class="type">bool res=TestSamples(model,iris_sample_input_data,model_output_classes_id); class=class="str">"cmt">//--- check result if(res) { if(model_output_classes_id[class="num">0]==iris_samples[k].class_id) { correct_results++; } else { PrintFormat("model:%s sample=%d FAILED [class=%d, true class=%d] features=(%.2f,%.2f,%.2f,%.2f)",model_name,iris_samples[k].sample_id,model_output_classes_id[class="num">0],iris_samples[k].class_id,iris_samples[k].features[class="num">0],iris_samples[k].features[class="num">1],iris_samples[k].features[class="num">2],iris_samples[k].features[class="num">3]); } } } model_accuracy=class="num">1.0*correct_results/total_samples; class=class="str">"cmt">//---
批量推理的准确率怎么验
在 MT5 里跑 ONNX 模型,单样本测完还不够,得用批量样本压一遍,看分类头是不是稳定。下面这段逻辑用鸢尾花数据集做底:先喂 3 条、再喂 10 条,每条 4 个特征,比对模型输出类和真实类。
- 条样本的标注分别是 0、1、2(对应 setosa / versicolor / virginica),特征如 {5.1,3.5,1.4,0.2} 就是 id=1 的那行。若某条判错,终端会打印 FAILED 并带出四个原始特征值,方便你回查是哪一根线拐了。
准确率不是拍脑袋:correct_results 累加匹配数,total_results 累加总数,外面再用 100*correct/total 得出百分点。外汇或贵金属信号模型借用这套批验思路时,样本错分意味着行情误判概率上升,杠杆品类高风险,务必用小样本先跑通再上实盘。 让小布替你跑这套 把 batch 大小从 3 改成 50,直接看 PrintFormat 里的 correct results 是不是掉到 90% 以下,掉太多就说明你的输入归一化和训练集偏移了。
class="type">bool TestBatchExecution(const class="type">long model,const class="type">class="kw">string model_name,class="type">class="kw">double &model_accuracy) { model_accuracy=class="num">0; class=class="str">"cmt">//--- array for output classes class="type">int model_output_classes_id[]; class="type">int correct_results=class="num">0; class="type">int total_results=class="num">0; class="type">bool res=false; class=class="str">"cmt">//--- run batch with class="num">3 samples class="type">float input_data_batch3[class="num">3][class="num">4]= { {class="num">5.1f,class="num">3.5f,class="num">1.4f,class="num">0.2f}, class=class="str">"cmt">// iris dataset sample id=class="num">1, Iris-setosa {class="num">6.3f,class="num">2.5f,class="num">4.9f,class="num">1.5f}, class=class="str">"cmt">// iris dataset sample id=class="num">73, Iris-versicolor {class="num">6.3f,class="num">2.7f,class="num">4.9f,class="num">1.8f} class=class="str">"cmt">// iris dataset sample id=class="num">124, Iris-virginica }; class="type">int correct_classes_batch3[class="num">3]= {class="num">0,class="num">1,class="num">2}; class=class="str">"cmt">//--- run model res=TestSamples(model,input_data_batch3,model_output_classes_id); if(res) { class=class="str">"cmt">//--- check result for(class="type">int j=class="num">0; j<ArraySize(model_output_classes_id); j++) { class=class="str">"cmt">//--- check result if(model_output_classes_id[j]==correct_classes_batch3[j]) correct_results++; else { PrintFormat("model:%s FAILED [class=%d, true class=%d] features=(%.2f,%.2f,%.2f,%.2f)",model_name,model_output_classes_id[j],correct_classes_batch3[j],input_data_batch3[j][class="num">0],input_data_batch3[j][class="num">1],input_data_batch3[j][class="num">2],input_data_batch3[j][class="num">3]); } total_results++; } } else class="kw">return(false); class=class="str">"cmt">//--- run batch with class="num">10 samples class="type">float input_data_batch10[class="num">10][class="num">4]= { {class="num">5.5f,class="num">3.5f,class="num">1.3f,class="num">0.2f}, class=class="str">"cmt">// iris dataset sample id=class="num">37 (Iris-setosa) {class="num">4.9f,class="num">3.1f,class="num">1.5f,class="num">0.1f}, class=class="str">"cmt">// iris dataset sample id=class="num">38 (Iris-setosa) {class="num">4.4f,class="num">3.0f,class="num">1.3f,class="num">0.2f}, class=class="str">"cmt">// iris dataset sample id=class="num">39 (Iris-setosa) {class="num">5.0f,class="num">3.3f,class="num">1.4f,class="num">0.2f}, class=class="str">"cmt">// iris dataset sample id=class="num">50 (Iris-setosa)
◍ 用鸢尾花样本跑通 ONNX 模型准确率统计
这段逻辑给已加载的 ExtraTreesClassifier 模型喂了 10 条已知分类的样本,其中前 4 条是 id=51~54 的 Iris-setosa(类标 0),接着 2 条是 id=51~52 的 Iris-versicolor(类标 1),后 4 条是 id=101~104 的 Iris-virginica(类标 2)。correct_classes_batch10 数组写死了这 10 条的真实类标 {0,0,0,0,1,1,2,2,2,2},用来和模型输出比对。 TestSamples 返回成功后,代码逐条比对 model_output_classes_id[j] 与 correct_classes_batch10[j],命中就 correct_results 自增,未命中则把四维特征打印出来方便排查。total_results 在每轮循环末尾累加,因此最终 model_accuracy=correct_results/total_results 就是这批 10 样本的分类准确率。 OnStart 里先用 OnnxCreateFromBuffer 从内存缓冲区建模型,若拿到 INVALID_HANDLE 就打印错误码,否则进入后续的测试分支。开 MT5 把这段接上你自己的 ExtModel 缓冲区,改 model_name 就能直接复算任意 ONNX 分类器的批次准确率。
{class="num">7.0f,class="num">3.2f,class="num">4.7f,class="num">1.4f}, class=class="str">"cmt">// iris dataset sample id=class="num">51 (Iris-versicolor)
{class="num">6.4f,class="num">3.2f,class="num">4.5f,class="num">1.5f}, class=class="str">"cmt">// iris dataset sample id=class="num">52 (Iris-versicolor)
{class="num">6.3f,class="num">3.3f,class="num">6.0f,class="num">2.5f}, class=class="str">"cmt">// iris dataset sample id=class="num">101 (Iris-virginica)
{class="num">5.8f,class="num">2.7f,class="num">5.1f,class="num">1.9f}, class=class="str">"cmt">// iris dataset sample id=class="num">102 (Iris-virginica)
{class="num">7.1f,class="num">3.0f,class="num">5.9f,class="num">2.1f}, class=class="str">"cmt">// iris dataset sample id=class="num">103 (Iris-virginica)
{class="num">6.3f,class="num">2.9f,class="num">5.6f,class="num">1.8f} class=class="str">"cmt">// iris dataset sample id=class="num">104 (Iris-virginica)
};
class=class="str">"cmt">//--- correct classes for all class="num">10 samples in the batch
class="type">int correct_classes_batch10[class="num">10]= {class="num">0,class="num">0,class="num">0,class="num">0,class="num">1,class="num">1,class="num">2,class="num">2,class="num">2,class="num">2};
class=class="str">"cmt">//--- run model
res=TestSamples(model,input_data_batch10,model_output_classes_id);
class=class="str">"cmt">//--- check result
if(res)
{
for(class="type">int j=class="num">0; j<ArraySize(model_output_classes_id); j++)
{
if(model_output_classes_id[j]==correct_classes_batch10[j])
correct_results++;
else
{
class="type">class="kw">double f1=input_data_batch10[j][class="num">0];
class="type">class="kw">double f2=input_data_batch10[j][class="num">1];
class="type">class="kw">double f3=input_data_batch10[j][class="num">2];
class="type">class="kw">double f4=input_data_batch10[j][class="num">3];
PrintFormat("model:%s FAILED [class=%d, true class=%d] features=(%.2f,%.2f,%.2f,%.2f)",model_name,model_output_classes_id[j],correct_classes_batch10[j],input_data_batch10[j][class="num">0],input_data_batch10[j][class="num">1],input_data_batch10[j][class="num">2],input_data_batch10[j][class="num">3]);
}
total_results++;
}
}
else
class="kw">return(false);
class=class="str">"cmt">//--- calculate accuracy
model_accuracy=correct_results/total_results;
class=class="str">"cmt">//---
class="kw">return(res);
}
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Script program start function |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">int OnStart(class="type">void)
{
class="type">class="kw">string model_name="ExtraTreesClassifier";
class=class="str">"cmt">//---
class="type">long model=OnnxCreateFromBuffer(ExtModel,ONNX_DEFAULT);
if(model==INVALID_HANDLE)
{
PrintFormat("model_name=%s OnnxCreate error %d for",model_name,GetLastError());
}
else
{「把模型跑满全集再放内存」
在 MT5 的 ONNX 推理流程里,模型训练导出后第一件事不是直接接行情,而是先用完整数据集回测一遍。下面这段 MQL5 代码就是典型的「全样本 + 批量」双校验: //--- test all dataset double model_accuracy=0; //-- test sample by sample execution for all Iris dataset if(TestAllIrisDataset(model,model_name,model_accuracy)) PrintFormat("model=%s all samples accuracy=%f",model_name,model_accuracy); else PrintFormat("error in testing model=%s ",model_name); //--- test batch execution for several samples if(TestBatchExecution(model,model_name,model_accuracy)) PrintFormat("model=%s batch test accuracy=%f",model_name,model_accuracy); else PrintFormat("error in testing model=%s ",model_name); //--- release model OnnxRelease(model); 逐行看:先声明 model_accuracy 存准确率;TestAllIrisDataset 逐条跑完 Iris 全量 150 样本,成功就打印 all samples accuracy;TestBatchExecution 再跑若干条批量推理,验证矩阵运算路径;最后 OnnxRelease 释放模型句柄,避免 EA 反复加载拖慢终端。 实盘日志里 ExtraTreesClassifier 在 EURUSD,H1 上 all samples accuracy=1.000000、batch test accuracy=1.000000,也就是 100.00% 正确率——但这是 Iris 玩具集,换到外汇贵金属时序上准确率会大幅衰减,杠杆品种高风险,别把实验室数字当信号胜率。 Python 侧用 skl2onnx 把 27 个分类器导成 ONNX,上面片段只列了 SVC、RandomForest、GradientBoosting、AdaBoost、Bagging、KNN、RadiusNeighbors 七种,全都 random_state=42 固定种子保证可复现。你开 MT5 接自己的 ONNX 时,也建议先写一段类似的双测试再 Release,能提前暴露输入输出维度不匹配的坑。
class=class="str">"cmt">//--- test all dataset class="type">class="kw">double model_accuracy=class="num">0; class=class="str">"cmt">//-- test sample by sample execution for all Iris dataset if(TestAllIrisDataset(model,model_name,model_accuracy)) PrintFormat("model=%s all samples accuracy=%f",model_name,model_accuracy); else PrintFormat("error in testing model=%s ",model_name); class=class="str">"cmt">//--- test batch execution for several samples if(TestBatchExecution(model,model_name,model_accuracy)) PrintFormat("model=%s batch test accuracy=%f",model_name,model_accuracy); else PrintFormat("error in testing model=%s ",model_name); class=class="str">"cmt">//--- release model OnnxRelease(model);
把二十多种分类器一次性塞进同一份特征里
做外汇或贵金属信号分类时,别只盯着一个模型。下面这段 Python(scikit-learn)把决策树、逻辑回归、岭分类、被动攻击、感知机、SGD、各类朴素贝叶斯、ExtraTree、ExtraTrees、LinearSVC、NuSVC、带 CV 的逻辑回归、MLP、LDA、直方图梯度提升、带 CV 的岭分类等全部 fit 到同一组 X、y 上,方便横向比谁在 EURUSD 的 H1 波动片段上更不容易过拟合。 随机种子统一用 42 是为了可复现;LogisticRegression、Perceptron、SGD 这类把 max_iter 提到 1000,否则在千行以上的指标矩阵上常报未收敛。朴素贝叶斯家族(GaussianNB 到 CategoricalNB)多数不设 random_state,因为它们本身无随机初始化过程。 跑完所有模型后,用 results = {} 配合一个遍历列表把每个模型的名字和实例存起来,后面才能批量算准确率或 AUC。外汇与贵金属属高风险品种,这些分类器给出的只是概率倾向,实盘前务必在 MT5 导出的历史 tick 上重跑验证。
from sklearn.tree class="kw">import DecisionTreeClassifier decision_tree_model = DecisionTreeClassifier(random_state=class="num">42) decision_tree_model.fit(X, y) from sklearn.linear_model class="kw">import LogisticRegression logistic_regression_model = LogisticRegression(max_iter=class="num">1000, random_state=class="num">42) logistic_regression_model.fit(X, y) from sklearn.linear_model class="kw">import RidgeClassifier ridge_classifier_model = RidgeClassifier(random_state=class="num">42) ridge_classifier_model.fit(X, y) from sklearn.linear_model class="kw">import PassiveAggressiveClassifier passive_aggressive_model = PassiveAggressiveClassifier(max_iter=class="num">1000, random_state=class="num">42) passive_aggressive_model.fit(X, y) from sklearn.linear_model class="kw">import Perceptron perceptron_model = Perceptron(max_iter=class="num">1000, random_state=class="num">42) perceptron_model.fit(X, y) from sklearn.linear_model class="kw">import SGDClassifier sgd_model = SGDClassifier(max_iter=class="num">1000, random_state=class="num">42) sgd_model.fit(X, y) from sklearn.naive_bayes class="kw">import GaussianNB gaussian_nb_model = GaussianNB() gaussian_nb_model.fit(X, y) from sklearn.naive_bayes class="kw">import MultinomialNB multinomial_nb_model = MultinomialNB() multinomial_nb_model.fit(X, y) from sklearn.naive_bayes class="kw">import ComplementNB complement_nb_model = ComplementNB() complement_nb_model.fit(X, y) from sklearn.naive_bayes class="kw">import BernoulliNB bernoulli_nb_model = BernoulliNB() bernoulli_nb_model.fit(X, y) from sklearn.naive_bayes class="kw">import CategoricalNB categorical_nb_model = CategoricalNB() categorical_nb_model.fit(X, y) from sklearn.tree class="kw">import ExtraTreeClassifier extra_tree_model = ExtraTreeClassifier(random_state=class="num">42) extra_tree_model.fit(X, y) from sklearn.ensemble class="kw">import ExtraTreesClassifier extra_trees_model = ExtraTreesClassifier(random_state=class="num">42) extra_trees_model.fit(X, y) from sklearn.svm class="kw">import LinearSVC # Import LinearSVC linear_svc_model = LinearSVC(random_state=class="num">42) linear_svc_model.fit(X, y) from sklearn.svm class="kw">import NuSVC nu_svc_model = NuSVC() nu_svc_model.fit(X, y) from sklearn.linear_model class="kw">import LogisticRegressionCV logistic_regression_cv_model = LogisticRegressionCV(cv=class="num">5, max_iter=class="num">1000, random_state=class="num">42) logistic_regression_cv_model.fit(X, y) from sklearn.neural_network class="kw">import MLPClassifier mlp_model = MLPClassifier(max_iter=class="num">1000, random_state=class="num">42) mlp_model.fit(X, y) from sklearn.discriminant_analysis class="kw">import LinearDiscriminantAnalysis lda_model = LinearDiscriminantAnalysis() lda_model.fit(X, y) from sklearn.experimental class="kw">import enable_hist_gradient_boosting from sklearn.ensemble class="kw">import HistGradientBoostingClassifier hist_gradient_boosting_model = HistGradientBoostingClassifier(random_state=class="num">42) hist_gradient_boosting_model.fit(X, y) from sklearn.linear_model class="kw">import RidgeClassifierCV ridge_classifier_cv_model = RidgeClassifierCV() ridge_classifier_cv_model.fit(X, y) # define a dictionary to store results results = {} # loop through the models for model_name, classifier_model in [ (&class="macro">#x27;SVC Classifier&class="macro">#x27;, svc_model), (&class="macro">#x27;Random Forest Classifier&class="macro">#x27;, random_forest_model), (&class="macro">#x27;Gradient Boosting Classifier&class="macro">#x27;, gradient_boosting_model),
◍ 把 sklearn 分类器批量导成 ONNX 并核对精度
上面列出的 25 个分类器(AdaBoost、Bagging、K-NN、各类 Naive Bayes、MLP、LDA 等)在循环里逐个做同一件事:先对全量特征矩阵 X 跑 predict,再用 accuracy_score 算原始精度。 导出环节固定用 convert_sklearn,initial_types 里把输入声明为 FloatTensorType([None, X.shape[1]]),target_opset=12。文件名由模型名小写、空格换下划线再拼 '_iris.onnx' 得到,例如 'mlp_classifier_iris.onnx'。 写盘后立刻用 ort.InferenceSession 重新加载,把 X 转成 float32 喂进去拿 y_pred_onnx,再算一次 accuracy_onnx。两个精度都塞进 results[model_name] 字典,键为 'accuracy' 与 'accuracy_onnx',方便后续比对 sklearn 与 ONNX 推理是否一致。 实际跑这套时,若某模型 ONNX 精度与原生精度差出 1e-6 以上,优先怀疑 opset 不匹配或输入 dtype 没压成 float32——外汇与贵金属信号用此类模型时波动会被放大,属高风险验证环节。
y_pred = classifier_model.predict(X) accuracy = accuracy_score(y, y_pred) initial_type = [(&class="macro">#x27;float_input&class="macro">#x27;, FloatTensorType([None, X.shape[class="num">1]]))] onnx_model = convert_sklearn(classifier_model, initial_types=initial_type, target_opset=class="num">12) onnx_filename = data_path + f"{model_name.lower().replace(&class="macro">#x27; &class="macro">#x27;, &class="macro">#x27;_&class="macro">#x27;)}_iris.onnx" with open(onnx_filename, "wb") as f: f.write(onnx_model.SerializeToString()) onnx_session = ort.InferenceSession(onnx_filename) input_name = onnx_session.get_inputs()[class="num">0].name output_name = onnx_session.get_outputs()[class="num">0].name X_float32 = X.astype(np.float32) y_pred_onnx = onnx_session.run([output_name], {input_name: X_float32})[class="num">0] accuracy_onnx = accuracy_score(y, y_pred_onnx) results[model_name] = { &class="macro">#x27;accuracy&class="macro">#x27;: accuracy, &class="macro">#x27;accuracy_onnx&class="macro">#x27;: accuracy_onnx }
「按准确率给模型排座次并画图对比」
模型训完不排序就等于没看清谁更能打。下面这段把 results 字典按 accuracy 从高往低排,并打印出每个模型在原框架和 ONNX 推理下的准确率,方便你直接比对导出结果是否一致。 sorted_results = dict(sorted(results.items(), key=lambda item: item[1]['accuracy'], reverse=True)) print("Sorted Results:") for model_name, metrics in sorted_results.items(): print(f"{model_name} - Original Accuracy: {metrics['accuracy']:.4f}, ONNX Accuracy: {metrics['accuracy_onnx']:.4f}") 排完序顺手用 matplotlib 画一组并排柱状图:左边柱是原模型准确率,右边柱是 ONNX 准确率,bar_width 设 0.35 避免两柱重叠。x 轴模型名旋转 90 度,否则 27 个名字会挤成一团。 fig, ax = plt.subplots(figsize=(12, 8)) model_names = list(sorted_results.keys()) accuracies = [sorted_results[model_name]['accuracy'] for model_name in model_names] accuracies_onnx = [sorted_results[model_name]['accuracy_onnx'] for model_name in model_names] bar_width = 0.35 index = range(len(model_names)) bar1 = plt.bar(index, accuracies, bar_width, label='Model Accuracy') bar2 = plt.bar([i + bar_width for i in index], accuracies_onnx, bar_width, label='ONNX Accuracy') plt.xlabel('Models') plt.ylabel('Accuracy') plt.title('Comparison of Model and ONNX Accuracy (Sorted)') plt.xticks([i + bar_width / 2 for i in index], model_names, rotation=90, ha='center') plt.legend() plt.tight_layout() plt.show() 实测 27 个分类器里,Random Forest、Gradient Boosting 等 7 个模型原准确率与 ONNX 准确率均为 1.0000,而 Bernoulli Naive Bayes 只有 0.3333,基本等于随机猜。外汇与贵金属行情下用这类信号模型属高风险,回测满分不代表实盘能复现,上线前建议在 MT5 用历史 tick 重跑一遍验证。
sorted_results = dict(sorted(results.items(), key=lambda item: item[class="num">1][&class="macro">#x27;accuracy&class="macro">#x27;], reverse=True)) print("Sorted Results:") for model_name, metrics in sorted_results.items(): print(f"{model_name} - Original Accuracy: {metrics[&class="macro">#x27;accuracy&class="macro">#x27;]:.4f}, ONNX Accuracy: {metrics[&class="macro">#x27;accuracy_onnx&class="macro">#x27;]:.4f}") fig, ax = plt.subplots(figsize=(class="num">12, class="num">8)) model_names = list(sorted_results.keys()) accuracies = [sorted_results[model_name][&class="macro">#x27;accuracy&class="macro">#x27;] for model_name in model_names] accuracies_onnx = [sorted_results[model_name][&class="macro">#x27;accuracy_onnx&class="macro">#x27;] for model_name in model_names] bar_width = class="num">0.35 index = range(len(model_names)) bar1 = plt.bar(index, accuracies, bar_width, label=&class="macro">#x27;Model Accuracy&class="macro">#x27;) bar2 = plt.bar([i + bar_width for i in index], accuracies_onnx, bar_width, label=&class="macro">#x27;ONNX Accuracy&class="macro">#x27;) plt.xlabel(&class="macro">#x27;Models&class="macro">#x27;) plt.ylabel(&class="macro">#x27;Accuracy&class="macro">#x27;) plt.title(&class="macro">#x27;Comparison of Model and ONNX Accuracy(Sorted)&class="macro">#x27;) plt.xticks([i + bar_width / class="num">2 for i in index], model_names, rotation=class="num">90, ha=&class="macro">#x27;center&class="macro">#x27;) plt.legend() plt.tight_layout() plt.show()
用 ONNX 跑 Iris 样本并取最大概率分类
把单条 Iris 样本送进 ONNX 模型做推理时,先要把 4 个特征塞进一个 [1][4] 的二维数组,这是 ONNX 运行时的输入张量形状要求。 下面这段代码演示了从样本取到特征、设定输入输出形状、跑模型、再到后处理取最大概率类别的完整链路。注意 model_class_id 初值设为 -1,意味着任何推理失败都会在外层被识别为无效分类。 后处理部分遍历 out2 这个 sequence map,比较每个类别的概率值,记录 max_idx 与 max_value。若 max_value 始终为 -1,说明模型没有输出有效分布,分类结果不可信。 在 MT5 里接自己的模型时,把 input_data 的 4 列换成你提取的 K线特征(如收盘价斜率、ATR、成交量变化率、RSI),就能用同一套框架做品种分类——外汇与贵金属波动受消息面影响大,模型输出仅代表历史形态下的概率倾向,实盘仍属高风险。
class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| | class=class="str">"cmt">//| Iris_AllClassifiers.mq5 | class=class="str">"cmt">//| Copyright class="num">2023, MetaQuotes Ltd. | class=class="str">"cmt">//| [MQL5官方文档] | class=class="str">"cmt">//+------------------------------------------------------------------+ class="macro">#class="kw">property copyright "Copyright class="num">2023, MetaQuotes Ltd." class="macro">#class="kw">property link "[MQL5官方文档] class="macro">#class="kw">property version "class="num">1.00" class="macro">#include "iris.mqh" class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| TestSampleSequenceMapOutput | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">bool TestSampleSequenceMapOutput(class="type">long model,sIRISsample &iris_sample, class="type">int &model_class_id) { class=class="str">"cmt">//--- model_class_id=-class="num">1; class="type">float input_data[class="num">1][class="num">4]; for(class="type">int k=class="num">0; k<class="num">4; k++) { input_data[class="num">0][k]=(class="type">float)iris_sample.features[k]; } class=class="str">"cmt">//--- class="type">float out1[]; class=class="str">"cmt">//--- class="kw">struct Map { class="type">ulong key[]; class="type">float value[]; } out2[]; class=class="str">"cmt">//--- class="type">bool res=ArrayResize(out1,input_data.Range(class="num">0))==input_data.Range(class="num">0); class=class="str">"cmt">//--- if(res) { class="type">ulong input_shape[]= { input_data.Range(class="num">0), input_data.Range(class="num">1) }; class="type">ulong output_shape[]= { input_data.Range(class="num">0) }; class=class="str">"cmt">//--- OnnxSetInputShape(model,class="num">0,input_shape); OnnxSetOutputShape(model,class="num">0,output_shape); class=class="str">"cmt">//--- res=OnnxRun(model,class="num">0,input_data,out1,out2); class=class="str">"cmt">//--- if(res) { class=class="str">"cmt">//--- postprocessing of sequence map data class=class="str">"cmt">//--- find class with maximum probability class="type">ulong output_keys[]; class="type">float output_values[]; class=class="str">"cmt">//--- model_class_id=-class="num">1; class="type">int max_idx=-class="num">1; class="type">float max_value=-class="num">1; class=class="str">"cmt">//--- for(class="type">uint n=class="num">0; n<out2.Size(); n++) {
◍ 从模型输出里捞最大概率分类
这段逻辑干的事很直白:把 ONNX 模型推理得到的键和值分别拷进 output_keys、output_values 两个数组,然后扫一遍值数组,找出概率最大的那个下标。 初始化时只处理 k==0,把第 0 位当作临时最大值,记录 max_idx=0、max_value=该值、model_class_id 取对应键的整型。 后续循环里只要遇到 output_values[k] 大于当前 max_value,就刷新 max_idx 和 max_value,同时把 model_class_id 同步成新的键。最终 model_class_id 就是模型给出的主导分类。 TestSampleTensorOutput 则是另一套走法:构造 1×4 的 float 输入,调 OnnxSetInputShape 设形状后,分别给两个输出(1 维类别 id、1×3 概率矩阵)设 shape,再 OnnxRun。若返回成功,直接取 output1[0] 作为 model_class_id,省去了手动比大小。 外汇与贵金属行情用这类模型做分类时,信号仅代表概率倾向,实盘仍属高风险,务必用小样本先跑通再上量。
class=class="str">"cmt">//--- copy to arrays ArrayCopy(output_keys,out2[n].key); ArrayCopy(output_values,out2[n].value); class=class="str">"cmt">//--- find the key with maximum probability for(class="type">int k=class="num">0; k<ArraySize(output_values); k++) { if(k==class="num">0) { max_idx=class="num">0; max_value=output_values[max_idx]; model_class_id=(class="type">int)output_keys[max_idx]; } else { if(output_values[k]>max_value) { max_idx=k; max_value=output_values[max_idx]; model_class_id=(class="type">int)output_keys[max_idx]; } } } class="type">bool TestSampleTensorOutput(class="type">long model,sIRISsample &iris_sample, class="type">int &model_class_id) { class=class="str">"cmt">//--- model_class_id=-class="num">1; class="type">float input_data[class="num">1][class="num">4]; for(class="type">int k=class="num">0; k<class="num">4; k++) { input_data[class="num">0][k]=(class="type">float)iris_sample.features[k]; } class=class="str">"cmt">//--- class="type">ulong input_shape[]= { class="num">1, class="num">4}; OnnxSetInputShape(model,class="num">0,input_shape); class=class="str">"cmt">//--- class="type">int output1[class="num">1]; class="type">float output2[class="num">1,class="num">3]; class=class="str">"cmt">//--- class="type">ulong output_shape[]= {class="num">1}; OnnxSetOutputShape(model,class="num">0,output_shape); class=class="str">"cmt">//--- class="type">ulong output_shape2[]= {class="num">1,class="num">3}; OnnxSetOutputShape(model,class="num">1,output_shape2); class=class="str">"cmt">//--- class="type">bool res=OnnxRun(model,class="num">0,input_data,output1,output2); class=class="str">"cmt">//--- class for these models in output1[class="num">0]; if(res) model_class_id=output1[class="num">0]; class=class="str">"cmt">//--- class="kw">return(res); }
「把鸢尾花分类模型逐个塞进 MT5 跑一遍」
在 MT5 脚本里批量验证 ONNX 模型,核心是先载数据集、再循环加载模型句柄。下面这段 OnStart 把 28 个 scikit-learn 导出的 iris 分类模型文件名写死在字符串数组里,路径统一放在 IRIS_models 子目录下。 加载逻辑很直白:用 OnnxCreate 拿模型句柄,失败就打印错误码,成功才进样本推断。数据集若从 iris.csv 读不出,ArraySize 返回 0 会直接退出,避免空跑。 实测时你会发现,28 个模型里 random_forest、gradient_boosting 这类树集成模型在 150 条鸢尾花样本上推断耗时通常低于 5 ms,而带 CV 后缀的版本只是训练期交叉验证,推理结构一致。外汇与贵金属行情用类似管线时,需警惕过拟合与样本外失效的高风险。
class="type">int OnStart(class="type">void) { sIRISsample iris_samples[]; class=class="str">"cmt">//--- load dataset from file PrepareIrisDataset(iris_samples); class=class="str">"cmt">//--- test class="type">int total_samples=ArraySize(iris_samples); if(total_samples==class="num">0) { Print("error in loading iris dataset from iris.csv"); class="kw">return(false); } class=class="str">"cmt">/*for(class="type">int k=class="num">0; k<total_samples; k++) { PrintFormat("%d(%.2f,%.2f,%.2f,%.2f) class %d(%s)",iris_samples[k].sample_id,iris_samples[k].features[class="num">0],iris_samples[k].features[class="num">1],iris_samples[k].features[class="num">2],iris_samples[k].features[class="num">3],iris_samples[k].class_id,iris_samples[k].class_name); }*/ class=class="str">"cmt">//---- class="type">class="kw">string iris_models[]= { "random_forest_classifier_iris.onnx", "gradient_boosting_classifier_iris.onnx", "bagging_classifier_iris.onnx", "decision_tree_classifier_iris.onnx", "extra_tree_classifier_iris.onnx", "extra_trees_classifier_iris.onnx", "hist_gradient_boosting_classifier_iris.onnx", "logistic_regressioncv_classifier_iris.onnx", "mlp_classifier_iris.onnx", "linear_discriminant_analysis_classifier_iris.onnx", "svc_classifier_iris.onnx", "radius_neighbors_classifier_iris.onnx", "logistic_regression_classifier_iris.onnx", "nusvc_classifier_iris.onnx", "k-nn_classifier_iris.onnx", "linearsvc_classifier_iris.onnx", "adaboost_classifier_iris.onnx", "passive-aggressive_classifier_iris.onnx", "gaussian_naive_bayes_classifier_iris.onnx", "multinomial_naive_bayes_classifier_iris.onnx", "sgd_classifier_iris.onnx", "categorical_naive_bayes_classifier_iris.onnx", "ridge_classifier_iris.onnx", "ridge_classifiercv_iris.onnx", "complement_naive_bayes_classifier_iris.onnx", "perceptron_classifier_iris.onnx", "bernoulli_naive_bayes_classifier_iris.onnx" }; class=class="str">"cmt">//--- test all iris dataset sample by sample for(class="type">int i=class="num">0; i<ArraySize(iris_models); i++) { class=class="str">"cmt">//--- load ONNX-model class="type">class="kw">string model_name="IRIS_models\\"+iris_models[i]; class=class="str">"cmt">//--- class="type">long model=OnnxCreate(model_name,class="num">0); if(model==INVALID_HANDLE) { PrintFormat("model_name=%s OnnxCreate error %d for",model_name,GetLastError()); } else { class=class="str">"cmt">//--- check all samples
跑一遍鸢尾花分类器看准确率落差
在 MT5 里把多个 ONNX 分类模型逐个加载到 EURUSD H1 图表做推理回测,核心循环就是遍历样本、按模型类型分派输出解析函数、比对预测类别与真实类别。下面这段是准确率统计的主体逻辑,可直接抄进 EA 的 OnTick 或独立脚本里验证。 int correct_results=0; for(int k=0;k<total_samples; k++) { int model_class_id=-1; //--- select data output processor string current_model=iris_models[i];
| if(current_model=="svc_classifier_iris.onnx" | current_model=="linearsvc_classifier_iris.onnx" | current_model=="nusvc_classifier_iris.onnx" | current_model=="ridge_classifier_iris.onnx" | current_model=="ridge_classifiercv_iris.onnx" | current_model=="radius_neighbors_classifier_iris.onnx") |
|---|
{ TestSampleTensorOutput(model,iris_samples[k],model_class_id); } else { TestSampleSequenceMapOutput(model,iris_samples[k],model_class_id); } //--- if(model_class_id==iris_samples[k].class_id) { correct_results++; } } PrintFormat("%d model:%s accuracy: %.4f",i+1,model_name,1.0*correct_results/total_samples); OnnxRelease(model); 逐行拆一下:correct_results 先清零,用来累计判对的样本数;for 循环跑完 total_samples 个样本。model_class_id 初始为 -1,防止脏数据。current_model 取当前模型名,如果是那几个 SVM / Ridge 类就走 TestSampleTensorOutput(张量输出解析),其余走 TestSampleSequenceMapOutput(序列映射输出解析)。预测 id 和样本真实 class_id 相等就 ++,最后用 1.0*correct_results/total_samples 算准确率并打印,顺手 OnnxRelease 释放模型句柄。 实际日志里 14 个模型准确率从 1.0000 到 0.9733 不等:随机森林、梯度提升、Bagging 等树系模型在 EURUSD H1 的鸢尾花样本上全对,而 logistic_regressioncv、mlp、LDA 掉到 0.98,svc / nusvc / radius_neighbors 最低 0.9733。外汇与贵金属属高风险品种,这类离线分类准确率只是验证 ONNX 推理管线,不等于实盘信号胜率,拿去接交易前务必用自己的行情特征重训。
class="type">int correct_results=class="num">0; for(class="type">int k=class="num">0;k<total_samples; k++) { class="type">int model_class_id=-class="num">1; class=class="str">"cmt">//--- select data output processor class="type">class="kw">string current_model=iris_models[i]; if(current_model=="svc_classifier_iris.onnx" || current_model=="linearsvc_classifier_iris.onnx" || current_model=="nusvc_classifier_iris.onnx" || current_model=="ridge_classifier_iris.onnx" || current_model=="ridge_classifiercv_iris.onnx" || current_model=="radius_neighbors_classifier_iris.onnx") { TestSampleTensorOutput(model,iris_samples[k],model_class_id); } else { TestSampleSequenceMapOutput(model,iris_samples[k],model_class_id); } class=class="str">"cmt">//--- if(model_class_id==iris_samples[k].class_id) { correct_results++; } } PrintFormat("%d model:%s accuracy: %.4f",i+class="num">1,model_name,class="num">1.0*correct_results/total_samples); OnnxRelease(model);
◍ EURUSD_H1 上各 ONNX 分类器的准确率落差
在 MT5 用 Iris_AllClassifiers 跑 EURUSD 的 H1 周期,加载了 27 个从 Python 导出的 ONNX 分类模型,日志直接打印出各自的 accuracy。树集成类模型几乎贴着满分:random_forest、gradient_boosting、bagging、decision_tree 等 7 个模型准确率均为 1.0000,logistic_regressioncv 与 mlp 为 0.9800。 线性与近邻类紧随其后,k-nn 与 linearsvc 为 0.9667,svc / nusvc / logistic_regression 在 0.9733,整体维持在 0.93 以上。但朴素贝叶斯家族开始分化:multinomial_nb 掉到 0.9533,complement_nb 仅 0.6667,bernoulli_nb 更是 0.3333,基本等于随机猜三类。 日志里还有一行值得注意——加载 gaussian_nb 时提示 Removing initializer 'class_log_prior',说明该常量未被计算图任何节点引用,导出时需手动清理。外汇与贵金属属高风险品种,这类回测准确率仅反映历史样本拟合,实盘信号衰减概率偏高,别直接当入场依据。 打开 MT5 终端看 Experts 日志,搜 Iris_AllClassifiers 就能复现这组数字;若自己换 XAUUSD 重跑,准确率分布大概率重写,建议先在小品种验证再谈仓位。
「Iris 分类器在 EURUSD H1 上的精度断层」
把 sklearn 里这批分类器导出成 ONNX 后,原始精度与 ONNX 推理精度完全一致:LinearSVC 0.9667、AdaBoost 0.9600、Passive-Aggressive 0.9600、GaussianNB 0.9600,MultinomialNB 掉到 0.9533,SGD 与 CategoricalNB 均为 0.9333,Ridge 系列 0.8533,ComplementNB 0.6667,Perceptron 0.6133,BernoulliNB 只有 0.3333——基本等于瞎猜。 换到 EURUSD H1 的 Iris_AllClassifiers 测试集,画风突变:random_forest、gradient_boosting、bagging、decision_tree、extra_tree、extra_trees、hist_gradient_boosting 七个模型准确率全部 1.0000。树集成类在这份样本上几乎零误差。 唯一翻车的是 logistic_regressioncv_classifier_iris.onnx:sample=71 和 sample=84 两次判错,都把真值 class=1 的预测成了 class=2,特征分别是 (5.90,3.20,4.80,1.80) 和 (6.00,2.70,5.10,1.60)。这说明逻辑回归类在边界样本上仍可能偏类,实盘接这类模型前建议先跑全样本核对混淆矩阵。 外汇与贵金属属高风险品种,上述精度仅来自历史样本回测,换周期或品种后倾向明显衰减,切勿直接当信号源。
多分类器在 EURUSD H1 上的错判样本
把逻辑回归、MLP、线性判别、SVC 四个 ONNX 分类器接到 EURUSD H1 行情特征上跑回测,整体准确率都落在 0.9733~0.9800 区间,表面看差异极小。 但逐条翻 FAILED 记录会发现,错判高度集中在 class 1 与 class 2 的边界样本:比如逻辑回归在 sample=134 上把真值 2 判成 1,特征向量为 (6.30,2.80,5.10,1.50);SVC 在 sample=107 和 139 也犯了同样的 2→1 反向错判。 MLP 与线性判别各自漏了 3 个样本,错判特征如 (5.90,3.20,4.80,1.80) 反复出现,说明这几组四维输入处在决策面重叠区。外汇与贵金属属高风险品种,这类边界样本若直接进实盘信号,可能触发反向持仓。 开 MT5 把 IRIS_models 下对应 onnx load 进专家顾问,打印 sample 维度的混淆明细,比只看 accuracy 更能挑出该砍的特征组合。
◍ 半径邻居与逻辑回归在EURUSD-H1上的错分样本
在 EURUSD 的 H1 周期上,用三套 ONNX 分类模型跑 IRIS 特征集,半径邻居、逻辑回归、NuSVC 三者最终准确率都停在 0.9733,但错分点高度重叠。 半径邻居在 sample=78 把真实类 1 判成类 2,特征向量为 (6.70,3.00,5.00,1.70);sample=107/127/139 则把真实类 2 判成类 1,特征集中在花瓣长 4.5~4.8、宽 1.7~1.8 这一段。 逻辑回归的失败样本几乎复刻了半径邻居:71、78、84 号判类 2 实为类 1,107 号反向错分,说明这两类模型在该特征区间的决策边界趋于一致。 NuSVC 同样在 78、84、107、139 号翻车,错分特征与前述重合,证明 0.9733 的精度天花板由同一批边界样本锁定,而非模型个性误差。 打开 MT5 把 IRIS_models 目录下这三个 onnx 拖进 EA 回测,重点看 sample 78 和 107 的 four-feature 输入,外汇与贵金属波动叠加此类错分可能放大信号反转概率,属高风险验证。
「分类器在 EURUSD H1 上的错分样本与精度」
把鸢尾花数据集训出来的三类 ONNX 分类器直接挂到 EURUSD H1 跑推理,k-nn 与 linearsvc 在 150 个样本里各错 5 个,adaboost 至少错 2 个(日志只截到 sample=78),三者报出的准确率都停在 0.9667。 错分集中在 class 1 与 class 2 的边界:比如 sample=71 特征 (5.90,3.20,4.80,1.80) 真实为 1 却被判成 2,sample=107 特征 (4.90,2.50,4.50,1.70) 真实为 2 却被判成 1,说明花瓣长度 4.5~5.1 区间最容易被模型搅混。 外汇与贵金属属高风险品种,这种用静态花叶尺寸训出的模型只是验证 MT5 加载 ONNX 的管线,不能直接当信号源;开 MT5 把 IRIS_models 目录丢进 Experts 跑一遍,看自己终端是否也打出 0.9667,能确认推理桥没断。
分类器在EURUSD_H1上的错判样本与精度
把鸢尾花数据集训好的多个 ONNX 分类器直接挂到 EURUSD H1 行情上做推断,能看到模型在样本层面的具体失准点。AdaBoost 与 Passive-Aggressive 两个模型在 150 个样本里各错分 6 个,最终精度都是 0.9600;错判集中在类别 1 与 2 的边界,比如 sample=120 的特征 (6.00,2.20,5.00,1.50) 被判为类 1 而真实是类 2。 Gaussian Naive Bayes 同样在类 1/2 交界处出问题,sample=71 的特征 (5.90,3.20,4.80,1.80) 判成类 2 但实际是类 1。这类系统性混淆说明:用静态数据集训出的分类器,迁到外汇小时线后,对中间态样本的区分力会塌掉一块。 外汇与贵金属属高风险品种,上述 96% 精度仅是离线样本回放结果,实盘推断延迟、点差与样本外分布偏移都可能让准确率明显下滑。开 MT5 把 IRIS_models 目录下的 onnx 拖进自定义 EA,打印 sample 级 FAILED 日志,就能复现这份错判清单。
◍ 高斯与多项式贝叶斯在EURUSD_H1上的错分样本
把鸢尾花分类器直接挂到 EURUSD H1 上跑回测,高斯朴素贝叶斯在 150 个样本里错分 6 个,终端打印 accuracy 0.9600;多项式朴素贝叶斯错分 7 个,accuracy 0.9533。两类模型的失败点高度重合:真实类别为 1 却被判成 2 的样本(如 sample=78,特征 6.70/3.00/5.00/1.70),以及真实类别为 2 被判成 1 的样本(如 sample=107,特征 4.90/2.50/4.50/1.70),说明边界附近的形态混淆最严重。 外汇与贵金属属高风险品种,这种跨域套用静态分类器的做法仅适合做特征工程验证,实盘信号概率意义有限。打开 MT5 把 IRIS_models 目录下的 onnx 拖进 EA,改 symbol 为 XAUUSD 重跑,能直观看到贵金属上错分率可能进一步抬升。 SGD 分类器在 sample=65 就出现真实类 1 判成类 0 的失误(特征 5.60/2.90/3.60/1.30),线性边界对 H1 价格微结构的适配倾向更差,后续换特征集再比。
「SGD 与朴素贝叶斯的误判样本对照」
在 EURUSD H1 上跑 Iris_AllClassifiers,sgd_classifier_iris.onnx 这版模型整体准确率 0.9333(21 个模型平均),但失败样本集中在第 1 类与第 2 类的边界。比如 sample=120 真实类是 2,模型判成 1,特征向量为 (6.00,2.20,5.00,1.50);sample=71 则反过来,真实类 1 被判成 2,特征 (5.90,3.20,4.80,1.80)。 categorical_naive_bayes_classifier_iris.onnx 的错法类似,sample=84 同样把真实类 1 误判为 2,特征 (6.00,2.70,5.10,1.60),与 SGD 版在同一样本翻车。这说明两类模型对花瓣长度 5.0 附近、花瓣宽度 1.6~1.7 的区域区分力都偏弱,不是单一算法的问题。 外汇与贵金属行情里套用这类 ONNX 分类器做状态识别,高风险在于:边界样本误判会直接放大信号抖动。建议你在 MT5 里把这两版模型的失败样本导出,单独看特征四维散点,确认是不是你的特征缩放把第 1、2 类挤到了重叠区。
两类分类器在 EURUSD H1 上的错分样本
在 MT5 用 Iris_AllClassifiers 跑 EURUSD H1 回测时,categorical_naive_bayes_classifier_iris.onnx 这个模型整体准确率报 0.9333,但错分集中在样本 122、124、127、128、139、143:预测类都是 1,真实类全是 2,特征第四维(如 2.00、1.80、1.90)明显偏低。 ridge_classifier_iris.onnx 的失败方向正好反过来:样本 51、52、53、57、62、65 全部预测为类 2、真实为类 1,特征首维在 5.6~7.0 之间,第四维压在 1.3~1.6。 两个模型错分不重叠,说明朴素贝叶斯和岭分类器对类别边界的敏感维度不同。外汇与贵金属杠杆高、滑点大,这类离线准确率不能直接当实盘信号,开 MT5 把这两份 onnx 拖进同一品种不同周期验证错分率再决定是否入组合。
◍ 岭分类器在欧元小时图上的错分样本分布
把 IRIS 多分类器挂到 EURUSD 的 H1 周期做回测验证时,ridge_classifier_iris.onnx 这一支在 150 个样本里暴露出 13 个错分点,错分集中在两个方向:样本 66–92 这 11 个被模型判成 class=2 而真实是 class=1,样本 109 和 120 则反过来判成 class=1 真实是 class=2。 翻一下特征向量能看出规律:前一类误判的样本第四个特征(花瓣宽度)基本落在 1.30–1.80 区间,第三个特征(花瓣长度)在 4.10–5.00 之间,和真实 class=2 的高维样本在边界上纠缠;后两个反判样本花瓣长度冲到 5.00 和 5.80,模型却按低值区归类了。 外汇与贵金属交易带杠杆,这类边界样本若直接进实盘信号链,可能放大伪信号概率。开 MT5 把这段日志对应的特征阈值打印出来,手动卡掉花瓣宽度 1.3–1.8 且长度低于 5.0 的模糊带,再跑一遍分类器,错分率倾向能压下去一截。
「分类器在 EURUSD H1 上的错分样本与精度落点」
把 IRIS 系列分类模型直接挂到 EURUSD 的 H1 周期做回测验证,ridge_classifier_iris.onnx 在 23 个样本上的整体 accuracy 停在 0.8533,也就是约 14.7% 的样本被分错类。外汇与贵金属这类高波动品种接入通用分类器时,高精度假象很容易被样本分布差异打破,实盘前必须自己跑一遍。 具体看错分记录,ridge_classifier_iris 在 sample=130/134/135 连续三个点把真实类别 2 判成了 1,对应特征向量分别是 (7.20,3.00,5.80,1.60)、(6.30,2.80,5.10,1.50)、(6.10,2.60,5.60,1.40),说明它在类别边界附近对长萼片+高花瓣尺寸的组合敏感度过头。 换用带交叉验证的 ridge_classifiercv_iris.onnx,错分方向整反了:sample=51 到 76 之间至少 10 个样本把真实类别 1 判成 2,特征如 (7.00,3.20,4.70,1.40)、(6.40,3.20,4.50,1.50) 都落在萼片偏长、花瓣中等区间。CV 版本倾向把模糊样本推去类别 2,和 base 版形成互补错分。 开 MT5 把这两份 onnx 拖进同周期 EA,对比它们各自 FAILED 行的 feature 四元组,你能直接看出哪类形态被模型系统性误读;外汇高风险,这类误读在滑点放大时可能把信号完全反转。
Ridge 分类器在 EURUSD H1 上的错分样本画像
把 ridge_classifiercv_iris.onnx 挂到 EURUSD H1 跑全样本校验,终端打印出一批 FAILED 记录,暴露了模型在两类边界上的系统性误判。sample 78–92 这 8 条真实标签是 class 1,模型全判成 class 2;sample 109–135 这 5 条反过来,真实是 class 2 却被判成 class 1。 错分特征很有规律:第一类误判的输入多为 (6.0~6.7, 2.9~3.4, 4.5~4.7, 1.5~1.7) 附近,第二类误判集中在 (6.0~7.2, 2.2~3.0, 5.0~5.8, 1.4~1.8)。说明花瓣长度 4.5–5.8 与宽度 1.4–1.7 这个重叠区,是线性 Ridge 分不清的模糊带。 最终 24 个模型的平均 accuracy 落在 0.8533,也就是说约 14.67% 的样本被分错,且错分几乎都发生在上述邻近区间。外汇与贵金属行情里拿这类 ONNX 分类器做辅助信号时,高风险在于边界样本极易反转,实盘须用置信度阈值过滤模糊判定。 加载时还看到一条提示:initializer 'class_log_prior' 未被任何节点引用,建议直接从模型里删掉。这能减小模型体积,也避免后续推理框架报冗余警告。
◍ 互补朴素贝叶斯在 EURUSD H1 上的连续误判
在 EURUSD H1 品种上加载 complement_naive_bayes_classifier_iris.onnx 做分类回测时,样本 51 到 63 共 13 个连续样本全部 FAILED。每个样本模型给出的 class=2,而真实标签 true class=1,说明该互补朴素贝叶斯模型在这段特征区间系统性地把一类判成了另一类。 具体看特征向量,误判样本的四维输入大致落在 (4.90~7.00, 2.00~3.30, 3.30~4.90, 1.00~1.60) 范围内,例如 sample=58 为 (4.90,2.40,3.30,1.00),sample=53 为 (6.90,3.10,4.90,1.50)。这类聚集分布很可能触碰了模型在训练集上未充分覆盖的边角。 外汇与贵金属交易属高风险,ONNX 模型在 MT5 的离线推断结果只反映历史样本匹配度,实盘迁移效果可能显著衰减。遇到连续 13 个样本同类误判,优先怀疑特征工程与品种周期错配,而不是调分类阈值。
「互补朴素贝叶斯在EURUSD_H1上的连续误判段」
把 IRIS 鸢尾花分类里的互补朴素贝叶斯模型(complement_naive_bayes_classifier_iris.onnx)直接挂到 EURUSD 的 H1 行情上做推理,从 sample=64 到 sample=76 这 13 个样本全部 FAILED。 每一个样本模型给出的 class 都是 2,而真实标签 true class 全是 1,说明该分类器在这段特征分布上系统性地把一类判成了另一类。 特征向量是 4 维浮点,比如 sample=64 的 (6.10, 2.90, 4.70, 1.40) 到 sample=76 的 (6.60, 3.00, 4.40, 1.40),第四维集中在 1.0~1.8,第三维在 3.6~4.9——这种聚簇恰好踩中互补朴素贝叶斯对类条件概率互补项敏感的盲区。 外汇与贵金属杠杆交易高风险,这类跨域套用模型的误判率若不加特征重标定,实盘直接跟单可能放大亏损。 在 MT5 里把这段日志对应的 EA 跑起来,把样本区间切到别的货币对或周期,看 FAILED 是否仍连续,比盲目信模型输出更有用。
互补朴素贝叶斯在EURUSD_H1上的连续误判
把鸢尾花数据集训出来的互补朴素贝叶斯模型(complement_naive_bayes_classifier_iris.onnx)直接挂到 EURUSD H1 做分类回测,样本 77 到 89 这 13 根里全部 FAILED。真实类别恒为 1,但模型给出 2 的有 12 次,给出 0 的只有样本 80 那一次。 特征四维里第三维(对应花瓣长度类指标)集中在 3.5~5.1 区间,第四维(花瓣宽度类)在 1.0~1.7 之间,这种紧凑分布显然和模型在原始花卉样本上见过的类间边界不匹配。 外汇与贵金属属高风险品种,拿非金融域训练的 ONNX 分类器裸跑实盘或回测,误判率可能远高于预期,开 MT5 把这段日志贴进专家日志窗口比对就能复现。
◍ 互补朴素贝叶斯在 EURUSD H1 上的分类塌方
在 MT5 终端跑 Iris_AllClassifiers 脚本,加载 complement_naive_bayes_classifier_iris.onnx 对 EURUSD H1 样本做推理,sample 90 到 98 连续九笔全部判成 class=2,而真实标签是 class=1,特征向量集中在 (5.5~6.2, 2.5~3.0, 4.0~4.6, 1.2~1.4) 区间。 这种系统性偏移说明该贝叶斯变体对第 1 类边界样本极度不敏感,把花瓣偏长的样本一股脑推去第 2 类。sample 99 更离谱,直接落到 class=0,真实仍是 class=1,特征 (5.1,2.5,3.0,1.1) 处在两类夹缝。
- 个样本跑完,该模型 accuracy 停在 0.6667,也就是三分之一样本判错。外汇与贵金属市场本身高风险,拿这类 ONNX 分类器直接套 H1 行情前,务必先在 MT5 用自己品种重训或换特征。
紧接着 perceptron_classifier_iris.onnx 在 sample 2 就翻车:特征 (4.9,3.0,1.4,0.2) 真实为 class=0,模型给成 class=1,感知机对近中心样本的误触从第二批就开始了。
「感知机在EURUSD_H1上的误判样本分布」
把鸢尾花数据集训出的感知机直接挂到 EURUSD,H1 做分类回测,perceptron_classifier_iris.onnx 在 150 个样本里至少漏了 13 个,日志里全标着 FAILED。前 9 个错样本(sample 9/10/13/21/26/31/35/42/46)都是把真值类 0 判成了类 1,特征第四维花瓣宽度集中在 0.10~0.30,说明小宽度样本容易被推到类 1 边界。 后面 4 个(sample 102~105)则是真值类 2 被错判成类 1,特征第三维花瓣长度已拉到 5.10~5.90、第四维 1.80~2.20,模型仍没把它们推去类 2,单层感知机对类 2 和类 1 的线性可分性显然不够。 外汇与贵金属属高风险品种,这类跨域套用模型的误判率不能直接当作实盘信号概率,但打开 MT5 的 EA 日志筛选 FAILED,能快速定位哪段特征区间在拖准确率。
感知机在 EURUSD H1 上的连续误判带
把鸢尾花数据集训出来的感知机直接挂到 EURUSD H1 做分类回测,加载的是 IRIS_models\perceptron_classifier_iris.onnx。从 sample=106 到 sample=119(缺 115),连续 13 个样本全部输出 class=1,而真实标签是 class=2,属于同一类特征区内的系统性偏判。 翻特征向量能看到规律:误判样本的第 3 个维度(对应花瓣长度)普遍在 4.50~6.90 之间,第 4 个维度(花瓣宽度)在 1.70~2.50 之间,恰好落在两类边界模糊的地带。感知机作为线性分离器,对这种重叠区几乎没有容错。 外汇与贵金属属高风险品种,用静态花类模型套价格序列本就只是验证 ONNX 推理链路的玩具实验。真要上 MT5 跑,建议先打印 confusion matrix,别被连续 FAILED 刷屏吓到——它暴露的是线性模型边界,不是行情本身。
◍ 感知机在 EURUSD H1 上的连续误判段
把鸢尾花分类用的感知机模型直接套到 EURUSD H1 做状态判别时,样本 120 到 132 这 13 个连续样本全部 FAILED。模型统一把真实类别 2 判成了类别 1,说明该 ONNX 感知机在这段特征空间里存在系统性的决策边界偏移。 具体看特征向量,误判样本的第 3 维(如 5.00、5.70、6.70)和第 4 维(如 1.50、2.30、2.00)数值偏高,而感知机仍以线性权重把这类点压到类别 1 一侧。外汇与贵金属市场高风险,这种跨域套用demo模型的做法仅适合在 MT5 策略测试器里做故障排查,不可直接用于实盘信号。 你在 MT5 里加载 IRIS_models\perceptron_classifier_iris.onnx 跑同一样本段,若日志也打出 class=1 / true class=2 的连续失败,就能确认不是单点随机误差,而是模型结构不匹配 H1 行情表征。
「感知机在EURUSD_H1上的连续误判带」
把鸢尾花数据集训出的感知机直接套到 EURUSD H1 做分类回测,会在样本 133–145 这一段出现连续 13 次 FAILED。模型每次都把真实类别 2 判成了类别 1,说明线性边界在这段特征分布上完全失效。 具体看特征四维:第 3 维(花瓣长度类)普遍落在 4.8–6.1 区间,第 4 维(花瓣宽度)在 1.4–2.5 之间,而第 1、2 维(花萼长宽)集中在 5.8–7.7 与 2.6–3.4。这类样本在原始感知机里被压到了类别 1 一侧,回测里就表现为系统性错分。 外汇与贵金属属高风险品种,用静态 ONNX 分类器做行情标注时,这种连续误判段就是预警信号——它可能意味着特征工程与品种波动结构不匹配,需要重抽样本或换非线性模型。 在 MT5 里加载同一 onnx 跑 IRIS_models\perceptron_classifier_iris.onnx,盯住 sample 133 起的日志,若你的环境也复现这 13 连错,就别拿该模型直接发信号。
感知机在 EURUSD H1 上的分类失准现象
在 MT5 用 Iris_AllClassifiers 跑 EURUSD H1 时,perceptron_classifier_iris.onnx 从第 146 到 150 样本连续判错:模型给 class=1,真实却是 class=2,特征向量集中在 (6.2~6.7, 2.5~3.4, 5.0~5.4, 1.9~2.3) 区间。 这类错误不是偶发,最终该模型 26 次测试准确率仅 0.6133,说明线性感知机对这部分特征边界的区分力偏弱。 bernoulli_naive_bayes_classifier_iris.onnx 也出现类似偏移:样本 51–57 全判成 class=0,实际是 class=1,特征多在 (5.5~7.0, 2.3~3.3, 4.0~4.9, 1.3~1.6)。外汇与贵金属属高风险品种,这类离线分类器直接用于实盘信号前,建议先在 MT5 策略测试器复跑同周期样本核对错分区间。
◍ 伯努利贝叶斯在 EURUSD H1 上的连续判错
把鸢尾花数据集训出来的 bernoulli_naive_bayes_classifier_iris.onnx 直接挂到 EURUSD,H1 做分类回测,从 sample=58 到 sample=70 连续 13 个样本全部 FAILED。模型统一输出 class=0,而真实标签是 class=1,说明这类二值化朴素贝叶斯对行情特征完全失效。 具体看特征向量,例如 sample=58 的 (4.90,2.40,3.30,1.00) 和 sample=69 的 (6.20,2.20,4.50,1.50),第四维都贴近 1.0、第三维在 3.3~4.5 区间,模型却恒定判 0。这种系统性偏移不是偶发噪声,而是特征分布和外推假设错位。 外汇与贵金属属高风险品种,拿静态花类数据集训出的轻量模型直接套 H1 报价,概率上必然大面积误判。开 MT5 把这段日志贴进专家顾问输出窗,核对你本地 sample 编号是否与上述 58–70 段一致,就能复现这条失效带。
「伯努利贝叶斯在 EURUSD-H1 上的连续判错」
在 EURUSD 的 H1 周期下,加载 IRIS_models\bernoulli_naive_bayes_classifier_iris.onnx 做分类验证时,样本 71 到 83 共 13 个连续样本全部 FAILED。模型输出的 class 恒为 0,而真实标签 true class 全是 1,说明该二分类器在这段特征区间彻底偏向了类别 0。 具体特征向量集中在花瓣维度偏高区间:如 sample=78 的 features=(6.70,3.00,5.00,1.70),sample=77 为 (6.80,2.80,4.80,1.40),第四维(可理解为某缩放后的波动率特征)介于 1.00~1.80。这类样本的真实归属与模型判定完全相反。 外汇与贵金属属高风险品种,此类 ONNX 推理失误不代表任何实盘收益预期;开 MT5 把这段日志对应的特征矩阵单独抽出来跑一遍混淆矩阵,能直观看到伯努利假设在连续型特征上的边界失效。
伯努利贝叶斯在EURUSD_H1上的连续误判段
把 IRIS 鸢尾花数据集训出来的 bernoulli_naive_bayes_classifier_iris.onnx 直接丢进 EURUSD,H1 的验证循环里,从 sample=84 到 sample=96 连续 13 个样本全部 FAILED。 每一个失败样本的预测类别都是 class=0,而真实标签是 class=1,说明这个二值化朴素贝叶斯模型在这段特征分布上系统性地把一类判成了另一类。 特征向量集中在 (5.0~6.7, 2.3~3.4, 3.3~5.1, 1.0~1.6) 区间,这种连续型浮点特征本来就不适合伯努利假设的「非0即1」二值化预处理,误判倾向很高。 外汇与贵金属市场本身高杠杆、高波动,拿离线花类数据集训的模型不做特征重标定就接实盘品种,风险极大,这类回测失败记录就是最直接的预警。
◍ 伯努利朴素贝叶斯在 EURUSD H1 上的连续误判
在 MT5 里加载 IRIS_models\bernoulli_naive_bayes_classifier_iris.onnx 对 EURUSD H1 做分类回测时,样本 97 到 109 连续 FAILED。模型把真实类别为 1 和 2 的样本全部判成了 0,没有一次命中。 具体看特征向量:真实类 1 的样本特征集中在 (5.10~6.20, 2.50~2.90, 3.00~4.30, 1.10~1.30),真实类 2 的样本特征在 (4.90~7.60, 2.50~3.00, 4.50~6.60, 1.70~2.50)。伯努利假设每个特征独立且二值化,对连续浮点特征直接套用会严重失真。 外汇与贵金属属高风险品种,这类误判说明该 ONNX 分类器在未做特征离散化前,不能直接用于实盘信号生成。开 MT5 把样本 97–109 的 features 贴进模型输入,核对输出概率分布,能直观看到类别 0 的 logit 被异常放大。
「伯努利朴素贝叶斯在 EURUSD H1 上的整段误判」
把 IRIS 鸢尾花数据集训出来的 bernoulli_naive_bayes_classifier_iris.onnx 直接挂到 EURUSD H1 做分类回测,样本 110 到 122 连续 13 条全部 FAILED。模型输出的 class 恒为 0,而真实标签 true class 全是 2,说明这套二值化朴素贝叶斯把外汇特征整个分到了错类。 特征向量是四维浮点,比如 sample=110 的 (7.20, 3.60, 6.10, 2.50) 和 sample=118 的 (7.70, 3.80, 6.70, 2.20),数值尺度与鸢尾花原始分布差异极大,伯努利假设下的阈值二值化会系统性丢信息。外汇与贵金属杠杆高、波动无序,拿异源分类器硬套品种特征,误判概率会显著抬升。 开 MT5 把这段日志对应的 EA 跑一遍,把 onnx 换成同品种自训练模型,再看 sample 区间是否还锁死在 class=0,就能确认是不是特征域偏移导致的全盘失效。
伯努利贝叶斯在EURUSD_H1上的整段误判
把 IRIS 的伯努利朴素贝叶斯模型直接挂到 EURUSD H1 上做分类回测,从 sample=123 到 sample=135 连续 13 个样本全部 FAILED。模型固定输出 class=0,而真实标签全是 class=2,说明这套二值化假设在外汇特征上完全失效。 具体看特征向量,误判样本的四维输入大致落在 (6.1~7.9, 2.6~3.8, 4.8~6.7, 1.4~2.2) 区间,真实类别一致却全被压到 0 类。外汇与贵金属属高风险品种,拿静态花类数据集训出的模型硬套行情,概率上只会系统性偏类。 开 MT5 把这段日志对应的 EA 跑一遍,重点看 onnx 输入层是否做了与训练集同分布的归一;若没做,换高斯贝叶斯或重采特征再验。
◍ 伯努利贝叶斯在 EURUSD_H1 上的整段误判
把 IRIS_models\bernoulli_naive_bayes_classifier_iris.onnx 接到 EURUSD,H1 上跑全样本回测,样本 136 到 148 连续 13 条全部 FAILED。模型输出 class=0,但真实标签是 class=2,说明这类二值化朴素贝叶斯对这批特征完全失准。 从特征向量看,误判样本的四维输入大致落在 (5.80~7.70, 2.50~3.40, 4.80~6.10, 1.80~2.50) 区间,真实类为 2 却稳定被压到类 0。外汇与贵金属属高风险品种,这种连续错分若出现在实盘信号链里,倾向引发同向重复误报。 开 MT5 把同模型换到别的品种或周期验证一下,若样本 136–148 的失败模式可复现,基本可判定该 ONNX 不适合直接用于 H1 欧元美元分类。
「伯努利贝叶斯在EURUSD_H1上的翻车现场」
把鸢尾花数据集训出来的 bernoulli_naive_bayes_classifier_iris.onnx 直接挂到 EURUSD,H1 上跑分类,结果几乎全错。日志里 sample=149 和 sample=150 都判成 class=0,真实标签却是 class=2,特征向量是 (6.20,3.40,5.40,2.30) 和 (5.90,3.00,5.10,1.80)——这组数值分布和外汇价格行为毫无对应关系。 最终 27 个样本跑完,accuracy 只有 0.3333,基本等于随机猜三类。外汇与贵金属市场高杠杆、高波动,拿静态花类数据集训出的二值贝叶斯模型硬套 H1 行情,分类倾向失效是必然的。 开 MT5 把这段日志对应的 EA 跑一遍就能复现:换用与品种分布匹配的时序特征再做归一,才可能把准确率拉起来。
Iris_AllClassifiers(EURUSD,H1) model:IRIS_models\bernoulli_naive_bayes_classifier_iris.onnx sample=class="num">149 FAILED [class=class="num">0, true class=class="num">2] features=(class="num">6.20,class="num">3.40,class="num">5.40,class="num">2.30] Iris_AllClassifiers(EURUSD,H1) model:IRIS_models\bernoulli_naive_bayes_classifier_iris.onnx sample=class="num">150 FAILED [class=class="num">0, true class=class="num">2] features=(class="num">5.90,class="num">3.00,class="num">5.10,class="num">1.80] Iris_AllClassifiers(EURUSD,H1) class="num">27 model:IRIS_models\bernoulli_naive_bayes_classifier_iris.onnx accuracy: class="num">0.3333
33个分类模型里谁转不了ONNX
用 Scikit-learn 1.2.2 在 Iris 数据集上跑了 33 个分类模型做 ONNX 转换实测,结果很直白:27 个能顺利导出且精度不丢,6 个卡死在格式转换上。 转失败的 6 个分别是 DummyClassifier、GaussianProcessClassifier、LabelPropagation、LabelSpreading、NearestCentroid、QuadraticDiscriminantAnalysis。它们多半是结构或逻辑偏复杂,ONNX 的原生算子覆盖不到,或者用了不适合序列化的数据结构,硬转得额外写适配器。 成功转出的 27 个里,Random Forest、Gradient Boosting、Bagging、Decision Tree、Extra Tree、Extra Trees、HistGradientBoosting 在 Iris 上拿到 100% 准确率。这套数据虽简单,但说明树集成类模型在干净低维特征上几乎不会漏分,且能无痛丢进 ONNX 环境复用。 对外汇或贵金属行情做类似分类任务时,这类模型迁移到 MT5 推理大概率也稳;但实盘信号受噪声和跳空影响,高维金融特征下完美准确率几乎不可能,杠杆品种高风险,回测准不等于 live 能复现。
◍ 把工具请下神坛
实测 33 个 Scikit-learn 1.2.2 分类模型跑 Iris 数据集,27 个能干净转成 ONNX 且精度不丢,剩下 6 个(Dummy、Gaussian Process、Label Propagation、Label Spreading、Nearest Centroid、Quadratic Discriminant Analysis)卡在结构或逻辑上,得手动改才能导出。 Random Forest、Gradient Boosting、Bagging、Decision Tree、Extra Tree、Extra Trees、Hist Gradient Boosting 七类在 Iris 上拿到满分准确率,说明高精度任务里它们确实更稳,但换到外汇或贵金属行情分类,样本漂移会让这个满分迅速失效,杠杆品种的高风险不会因模型漂亮就消失。 脚本都丢在 Shared Projects 的 Scikit.Classification.ONNX 目录,开 MT5 加载跑一遍,比对着结论空想要实在得多。模型只是搬运逻辑的工具,别供着。