基于暴力算法的 CatBoost 模型高级重采样与选择(基础篇)
◍ 用暴力重采样榨干 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 取数后直接跑就能复现环状图。
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 数据集,构造出的特征空间至少看起来像回事。 外汇和贵金属杠杆高、滑点狠,这种「看起来理想」的空间一旦接实盘,大概率会露馅。所以下一步不是追求完美划分,而是在可解释性和数据质量之间做折衷——太干净的特征往往脱离行情,太脏的特征只会喂出垃圾信号。
# 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)