数据科学和机器学习(第 36 部分):与偏颇的金融市场打交道·综合运用
⚖️

数据科学和机器学习(第 36 部分):与偏颇的金融市场打交道·综合运用

(3/3)·USDJPY 千根日线里 59.2% 看涨,直接训模型等于默认它永远涨,这篇收尾给出解法

含代码示例 第 3/3 篇
拿历史涨跌比当预测基准是常见坑:USDJPY 千根日线 59.2% 收阳,无脑判涨就有近六成正确率,但实盘里这等于赌过去重演。类失衡不处理,ML 模型会悄悄偏向主类,把次要方向漏个干净。

◍ 三类欠抽样在 MT5 回测里的真实表现

处理类不平衡时,随机欠抽样会直接砍掉主类样本去凑平比例。它简单但危险:删掉的往往是有信息量的主类观测,训练集代表性下降,模型可能欠拟合。原文在策略测试器同配置下跑出的结果是 282 笔空头和 237 笔多头,模型偏空但比生料训练的偏颇模型更能双向盈利。 托梅克链接只删不同类里互为最近邻的主类点,用来清理决策边界。它限二元分类,重叠高时效果差。同配置回测开了 303 笔空头和 216 笔多头,偏空却依然有获利能力。 聚簇质心用 K-均值质心替换主类大多数样本,平衡后准确率仅 0.73,暗示过拟合较低。回测 519 笔中 343 笔盈利,准确率 66.09%,多头仓位成功率高达 75.97%——这是三种里实盘信号最稳的一项。外汇与贵金属杠杆高,这类重采样模型仅降低偏差,不预示收益,请先在 MT5 策略测试器复跑确认。 下方代码是 Python 端 imblearn 的抽样片段,可对照看每类技术对 y_train 计数的改变:随机欠抽样把 [304,395] 压成 [304,304];托梅克链接变成 [304,283],并非完全平衡。

MQL5 / C++
from imblearn.under_sampling class="kw">import RandomUnderSampler
print("b4 Target: ",np.unique(y_train, return_counts=True))
rus = RandomUnderSampler(random_state=class="num">42)
X_resampled, y_resampled = rus.fit_resample(X_train, y_train)
print("After Target: ",np.unique(y_resampled, return_counts=True))
b4 Target:  (array([class="num">0, class="num">1]), array([class="num">304, class="num">395]))
After Target:  (array([class="num">0, class="num">1]), array([class="num">304, class="num">304]))
Train Classification report
              precision    recall  f1-score   support
           class="num">0       class="num">0.76      class="num">0.90      class="num">0.82       class="num">158
           class="num">1       class="num">0.91      class="num">0.78      class="num">0.84       class="num">204
    accuracy                           class="num">0.83       class="num">362
   macro avg       class="num">0.83      class="num">0.84      class="num">0.83       class="num">362
weighted avg       class="num">0.84      class="num">0.83      class="num">0.83       class="num">362
from imblearn.under_sampling class="kw">import TomekLinks
tl = TomekLinks()
X_resampled, y_resampled = tl.fit_resample(X_train, y_train)
print(f"Before --> y(unique): {np.unique(y_train, return_counts=True)}
After  --> y(unique): {np.unique(y_resampled, return_counts=True)}")
Before --> y(unique): (array([class="num">0, class="num">1]), array([class="num">304, class="num">395]))
After  --> y(unique): (array([class="num">0, class="num">1]), array([class="num">304, class="num">283]))
Train Classification report
              precision    recall  f1-score   support
           class="num">0       class="num">0.69      class="num">0.94      class="num">0.80       class="num">158
           class="num">1       class="num">0.93      class="num">0.68      class="num">0.78       class="num">204
    accuracy                           class="num">0.79       class="num">362

「用聚类中心重采样抹平类别倾斜」

做二分类信号模型时,EURUSD 这类品种的涨跌样本天然不均。原始训练集里类别 0 有 158 条、类别 1 有 204 条,直接喂模型会让少数类召回率塌掉。 用 ClusterCentroids 做欠采样,把两类都压到 225 条:随机种子锁 42,fit_resample 后类别分布从 (158,204) 变成 (225,225),代码跑完直接 print 前后计数就能核对。 重采样后训练集分类报告显示:类别 0 的 precision 0.64 / recall 0.86,类别 1 的 precision 0.85 / recall 0.62,整体 accuracy 0.73,macro f1 0.73。相比采样前 macro avg 的 0.81/0.81/0.79,准确率掉了一点但两类更均衡,后续用于 MT5 信号过滤时误杀概率可能更可控。 外汇与贵金属杠杆高、波动剧烈,任何重采样带来的指标改善都只是历史样本上的倾向,实盘前务必用 MT5 策略测试器跨多个品种验证。

MQL5 / C++
from imblearn.under_sampling class="kw">import ClusterCentroids
cc = ClusterCentroids(random_state=class="num">42)
X_resampled, y_resampled = cc.fit_resample(X, y)
print(f"Before --> y(unique): {np.unique(y_train, return_counts=True)}
After  --> y(unique): {np.unique(y_resampled, return_counts=True)}")
Before --> y(unique): (array([class="num">0, class="num">1]), array([class="num">158, class="num">204]))
After  --> y(unique): (array([class="num">0, class="num">1]), array([class="num">225, class="num">225]))
Train Classification report
              precision   recall  f1-score   support
           class="num">0       class="num">0.64      class="num">0.86      class="num">0.73       class="num">158
           class="num">1       class="num">0.85      class="num">0.62      class="num">0.72       class="num">204
   accuracy                           class="num">0.73       class="num">362
   macro avg       class="num">0.75      class="num">0.74      class="num">0.73       class="num">362
weighted avg       class="num">0.76      class="num">0.73      class="num">0.73       class="num">362

SMOTE 叠加清洗策略的实盘反差

先跑 SMOTE 再过托梅克链接,思路是用合成样本补平类别缺口,随后删掉跨类边界的噪声对。重抽样后训练集从 362 条变为两类各 159 条,分类报告里 0 类 precision 0.74、1 类 0.79,整体 accuracy 0.77。 挂到行情上回测,机器人开出 220 笔空头和 299 笔多头,合计 519 笔,分布没有明显畸形,这种组合在样本量尚可时倾向给出能用的信号。 换成 SMOTE + ENN 就走了另一条路:合成之后用编辑最近邻把被误判的样本清掉,结果训练集被砍到只剩 61 条。分类报告直接垮掉,accuracy 掉到 0.51,1 类 recall 仅 0.32。 实盘里它开了 519 笔交易,其中 180 笔买、339 笔卖,偏卖倾向很明显,不仅没帮忙还令结果更糟。这不是说 ENN 本身烂,只是在当前数据规模下它不是合适的解法,外汇与贵金属杠杆高,这类偏颇样本容易放大回撤。 下面这段 Python 是两套组合的直接调用与前后类别计数,MT5 侧做不了重抽样,但你能拿它比对特征维度是否匹配。

MQL5 / C++
from imblearn.combine class="kw">import SMOTETomek
smt = SMOTETomek(random_state=class="num">42)
X_resampled, y_resampled = smt.fit_resample(X_train, y_train)
print(f"Before --> y(unique): {np.unique(y_train, return_counts=True)}
After  --> y(unique): {np.unique(y_resampled, return_counts=True)}")
Before --> y(unique): (array([class="num">0, class="num">1]), array([class="num">158, class="num">204]))
After  --> y(unique): (array([class="num">0, class="num">1]), array([class="num">159, class="num">159]))
Train Classification report
              precision    recall  f1-score   support
           class="num">0       class="num">0.74      class="num">0.73      class="num">0.73       class="num">158
           class="num">1       class="num">0.79      class="num">0.80      class="num">0.80       class="num">204
    accuracy                           class="num">0.77       class="num">362
   macro avg       class="num">0.77      class="num">0.77      class="num">0.77       class="num">362
weighted avg       class="num">0.77      class="num">0.77      class="num">0.77       class="num">362
from imblearn.combine class="kw">import SMOTEENN
sme = SMOTEENN(random_state=class="num">42)
X_resampled, y_resampled = sme.fit_resample(X_train, y_train)
print(f"Before --> y(unique): {np.unique(y_train, return_counts=True)}
After  --> y(unique): {np.unique(y_resampled, return_counts=True)}")
Before --> y(unique): (array([class="num">0, class="num">1]), array([class="num">158, class="num">204]))
After  --> y(unique): (array([class="num">0, class="num">1]), array([class="num">37, class="num">24]))
Train Classification report
              precision    recall  f1-score   support
           class="num">0       class="num">0.46      class="num">0.76      class="num">0.58       class="num">158
           class="num">1       class="num">0.63      class="num">0.32      class="num">0.42       class="num">204
    accuracy                           class="num">0.51       class="num">362
   macro avg       class="num">0.55      class="num">0.54      class="num">0.50       class="num">362
weighted avg       class="num">0.56      class="num">0.51      class="num">0.49       class="num">362

◍ 一点提醒

市场不会按教科书重演,历史形态只能当参考,不能当决策依据。重抽样技术(过抽样、欠抽样)在 MQL5 里用 ONNX 模型跑外汇贵金属时,过度拟合和丢信息的风险始终存在,这是高频交易者必须盯紧的权衡点。 附件里 Experts\Test Resampling Techniques.mq5 这款 EA 直接加载 Common 下的 USDJPY.PERIOD_D1.randomundersampling.onnx 就能在 MT5 里验证;但社区已有实测反馈:若训练集占 80% 且未按时间顺序严格切分,2024.07.06–2025.01.01 的回测其实混入了训练数据,测试段表现会虚高、后续实盘倾向亏损。 外汇与贵金属属高风险品种,任何机器学习系统都只是概率工具。开 MT5 把训练/测试按时间断开(如 2023 整年训练、2024 测试),重跑一遍再信模型,比直接抄参数更靠谱。

把重采样跑批交给小布
这些诊断和小布盯盘的 AIGC 已内置,打开对应品种页即可看到类分布与重采样后的回测摘要,你专注挑特征而不是手搓抽样脚本。

常见问题

日线样本里看涨烛条占比偏高,模型优化整体准确率时会偏好主类,看跌作为次要类被忽视,预测倾向一边倒。
欠抽样丢信息但快,过抽样补少数类但可能过拟合;MT5 小样本常先用混合方法,具体看品种偏移幅度。
原文含代码示例,用 ArrayResize 控少数类副本数、Shuffle 打乱索引,再并回主类子集交给 CNet 训练。
可以,品种页的 AIGC 诊断会算多空烛条比并提示训练前是否需重采样,省去手动数柱线。
主类拉高准确率却掩盖次要类烂表现,需用 F1、召回率或混淆矩阵看各类真实命中。