数据科学和机器学习(第 36 部分):与偏颇的金融市场打交道·综合运用
(3/3)·USDJPY 千根日线里 59.2% 看涨,直接训模型等于默认它永远涨,这篇收尾给出解法
◍ 三类欠抽样在 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],并非完全平衡。
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 策略测试器跨多个品种验证。
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 侧做不了重抽样,但你能拿它比对特征维度是否匹配。
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 测试),重跑一遍再信模型,比直接抄参数更靠谱。