基于暴力算法的 CatBoost 模型高级重采样与选择(基础篇)
📘

基于暴力算法的 CatBoost 模型高级重采样与选择(基础篇)

第 1/3 篇

◍ 用暴力重采样榨干 CatBoost 的泛化边界

在 MT5 里做 CatBoost 信号模型,最容易被忽略的是样本结构的稳定性。单纯一次 train/test 切分,在外汇与贵金属这种非平稳序列上,回测漂亮、实盘崩坏的概率偏高,属于典型的高风险陷阱。 暴力重采样思路很直接:对同一段行情窗口,做 N 次有放回 / 无放回的随机抽取,每次都训一个 CatBoost,再横向比对各模型的 AUC 与回撤分布。2021 年 2 月一篇实盘向文章里,作者用 2 762 次社区浏览验证了这个方向的关注度,但关键不在浏览量,而在重采样次数到 200 轮后,模型间预测分歧才趋于收敛。 落地时建议先在 EURUSD H1 上跑 50 轮轻量重采样,观察 CatBoost 的 Feature Importance 排名是否每次大幅跳动;若跳动剧烈,说明当前窗口样本信噪比偏低,倾向需要换周期或加波动率过滤。

为什么简单随机抽样会拖垮模型

把机器学习交易系统直接套在原始行情上做简单随机抽样,往往会埋下三个隐蔽坑。第一是类别不平衡:若训练窗口里行情以涨为主,随机抽样产出的买入标签就会明显多于卖出标签,模型在新数据上可能倾向高频预测买入,而对下跌段失效。 第二是特征与标签的自相关。随机抽出来的样本常常同类标签连片,特征增量变化极小,回归模型残差会出现自相关——这等同于把模型过度拟合到训练期的波动属性上,忽略了其他依赖。对比看,残差方差均匀的模型通常覆盖了更多信息。 第三是类重叠。在二维特征空间里,随机抽样可能让类0和类1的样本交叉,同类点被推远、异类点被拉近,逼出大量分割边界;特征微偏就让预测在类间跳变,新数据稳定性直接崩掉。理想状态是类标签在特征空间里不相交、能用线性或简单边界分开,这样泛化才站得住。 外汇与贵金属市场高杠杆、高波动,这类数据缺陷会被放大,动手改采样前建议先开 MT5 用历史数据跑一遍标签分布统计。

「PCA 降维后 EURUSD 特征里的环形噪声」

原始特征维度是 look_back 乘 ma_periods 的长度,本例里 LOOK_BACK=5、MA_PERIODS 有 4 个周期,直接就是 20 维,平面根本画不出来。用 PCA 压到 5 个主成分,信息损失最小,但特征空间结构一眼就能看穿问题。 散点图里类 0 和类 1 的点成对形成环状,而不是普通云团。这是价格序列自相关带出来的伪结构——把采样线画细一点,环就散了。两类重叠极强,分类器要想分准,只能堆很复杂的边界,本质上就是 GIGO。 数据用的 EURUSD H1,区间 2020-01-01 到 2021-01-01,标签用 10~25 点波动带生成、未加噪声。外汇和贵金属这种高杠杆品种,原始 OHLC 派生特征自带自相关,直接喂模型大概率学到的是镜像假规律,风险很高。 下面这段就是把 20 维压成 5 维并画 PairGrid 的实操代码,开 MT5 取数后直接跑就能复现环状图。

MQL5 / C++
LOOK_BACK = class="num">5
MA_PERIODS = [class="num">15, class="num">55, class="num">150, class="num">250]
SYMBOL = &class="macro">#x27;EURUSD&class="macro">#x27;
MARKUP = class="num">0.00010
TIMEFRAME = mt5.TIMEFRAME_H1
START_DATE = class="type">class="kw">datetime(class="num">2020, class="num">1, class="num">1)
TSTART_DATE = class="type">class="kw">datetime(class="num">2015, class="num">1, class="num">1)
STOP_DATE = class="type">class="kw">datetime(class="num">2021, class="num">1, class="num">1)
# make dataset
pr = get_prices(START_DATE, STOP_DATE)
pr = add_labels(pr, min=class="num">10, max=class="num">25, add_noize=class="num">0)
res = tester(pr, plot=True)
pca_plot(pr)
def pca_plot(data):
    from sklearn.decomposition class="kw">import PCA
    pca = PCA(n_components = class="num">5)
    components = pd.DataFrame(pca.fit_transform(data[data.columns[class="num">1:-class="num">1]]))
    components[&class="macro">#x27;labels&class="macro">#x27;] = data[&class="macro">#x27;labels&class="macro">#x27;].reset_index(drop = True)
    class="kw">import seaborn as sns
    g = sns.PairGrid(components, hue="labels", height=class="num">1.2)
    g.map_diag(sns.histplot)
    g.map_offdiag(sns.scatterplot)
    g.add_legend()
    plt.show()

◍ 在干净数据和垃圾数据之间找平衡

用 K-means 把特征空间切成两簇,能直观看到所谓「理想分割」长什么样。上面这段代码把价格矩阵去掉首列后丢进 KMeans(n_clusters=2),预测出的标签直接写回数据框再画 PCA,五个主成分里前两个就能把两类分得挺开。 但别忘了,簇标签 0 和 1 只是数学上的分类,跟能不能赚钱半点关系没有。这个例子只是证明:比起纯 GIGO 数据集,构造出的特征空间至少看起来像回事。 外汇和贵金属杠杆高、滑点狠,这种「看起来理想」的空间一旦接实盘,大概率会露馅。所以下一步不是追求完美划分,而是在可解释性和数据质量之间做折衷——太干净的特征往往脱离行情,太脏的特征只会喂出垃圾信号。

MQL5 / C++
# perform K-means clustering over dataset
from sklearn.cluster class="kw">import KMeans
pr = get_prices(look_back=LOOK_BACK)
X = pr[pr.columns[class="num">1:]]
kmeans = KMeans(n_clusters=class="num">2).fit(X)
y_kmeans = kmeans.predict(X)
pr[&class="macro">#x27;labels&class="macro">#x27;] = y_kmeans
pca_plot(pr)

常见问题

简单随机抽样容易让训练集和验证集分布偏移,拖垮泛化;用暴力重采样多轮切分能暴露这种偏差,建议至少跑 50 轮以上看波动。
不一定是废了,环形噪声常来自冗余周期叠加;可先剔除解释方差低于 1% 的主成分,再观察边界是否收敛。
可以,小布能按你设定的重采样轮数和特征集自动出泛化边界报告,你只需导入品种和参数,不用自己写循环。
从 8:2 干净:噪声起步,逐步加噪到 5:5 看准确率拐点;外汇贵金属高风险,实盘前务必用样本外数据复核。
会,百轮以上在普通机可能几小时;可用早停阈值,连续 10 轮泛化指标无提升就终止,省下的时间用于调特征。