基于暴力算法的 CatBoost 模型高级重采样与选择·进阶篇
(2/3)·简单随机抽样正在偷偷喂给你的模型错误偏见,进阶重采样把类重叠和自相关一次挤干
「用高斯混合模型重采样训练集」
k-means 把每个簇硬框成一个由最外点决定半径的圆或超球体,真实簇常是椭圆甚至不规则形,强行套圆会产生簇间重叠、丢失结构。它非概率化,只给硬标签,在需要评估“属于某类置信度”的场景里基本废掉。 高斯混合模型(GMM)找的是多个多元高斯分布的叠加,covariance_type 决定协方差形态:full 允许任意朝向的椭圆,tied 共享协方差,diag 只保留轴对齐缩放,spherical 退化为各向同性球。每给一个样本,模型吐出它落入各个分量的概率,而不是非此即彼。 GMM 本质不是在聚类,而是在估概率密度。估完密度后能从每个分量里重新抽样本,拼出一套“像原数据但有更小相关性和更少异常值”的新训练集。实盘里你可以拿这套重采样集去喂 CatBoost 做分类,外汇与贵金属波动聚簇常被宏观事件压扁成狭长椭圆,GMM 的 full 协方差比 k-means 更贴真实分布,但过拟合风险随分量数上升而放大,属高风险建模操作,须用 MT5 导出的 tick 或分钟序列亲自回测验证。
◍ GMM重采样叠加CatBoost暴力选模
对原始特征矩阵做高斯混合聚类,核心参数只盯 n_components,经验值直接给 75 个簇。聚类后从多元分布里抽 5000 条人工样本,标签按 0.5 阈值二值化,再用 pca_plot 看投影——和原 GIGO 集比,样本环消失了,特征与标签的相关性走低,模型划分边界可能更少。 光抽一次不够,写了个暴力函数:每次从 GMM 重采 10000 条,喂给 CatBoost(iterations=500, depth=6, learning_rate=0.1),训练集来自 2020 年初,测试集用 2015 年初起的历史外推。循环跑 50 次,把每次的 R^2 存进列表并升序排,末尾即最优。 排序后倒数第二个模型在五年回测窗口(训练仅用一年)拿到 R^2 = 0.9576,说明重采样后的泛化倾向稳定。模型对象嵌在嵌套列表索引 2,导出时取 res[-2] 转 MQH,丢进 MT5 策略测试器跑——自定义点差比实盘小,曲线形状大致一致但绝对值有偏移。外汇与贵金属属高风险品种,此类回测不代表实盘概率。
# perform GMM clustering over dataset from sklearn class="kw">import mixture pr_c = pr.copy() X = pr_c[pr_c.columns[class="num">1:]] gmm = mixture.GaussianMixture(n_components=class="num">75, covariance_type=&class="macro">#x27;full&class="macro">#x27;).fit(X) # plot resampled components generated = gmm.sample(class="num">5000) gen = pd.DataFrame(generated[class="num">0]) gen.rename(columns={ gen.columns[-class="num">1]: "labels" }, inplace = True) gen.loc[gen[&class="macro">#x27;labels&class="macro">#x27;] >= class="num">0.5, &class="macro">#x27;labels&class="macro">#x27;] = class="num">1 gen.loc[gen[&class="macro">#x27;labels&class="macro">#x27;] < class="num">0.5, &class="macro">#x27;labels&class="macro">#x27;] = class="num">0 pca_plot(gen) # brute force loop def brute_force(samples = class="num">5000): # sample new dataset generated = gmm.sample(samples) # make labels gen = pd.DataFrame(generated[class="num">0]) gen.rename(columns={ gen.columns[-class="num">1]: "labels" }, inplace = True) gen.loc[gen[&class="macro">#x27;labels&class="macro">#x27;] >= class="num">0.5, &class="macro">#x27;labels&class="macro">#x27;] = class="num">1 gen.loc[gen[&class="macro">#x27;labels&class="macro">#x27;] < class="num">0.5, &class="macro">#x27;labels&class="macro">#x27;] = class="num">0 X = gen[gen.columns[:-class="num">1]] y = gen[gen.columns[-class="num">1]] # train\test split train_X, test_X, train_y, test_y = train_test_split(X, y, train_size = class="num">0.5, test_size = class="num">0.5, shuffle=True) class="macro">#learn with train and validation subsets model = CatBoostClassifier(iterations=class="num">500, depth=class="num">6, learning_rate=class="num">0.1, custom_loss=[&class="macro">#x27;Accuracy&class="macro">#x27;], eval_metric=&class="macro">#x27;Accuracy&class="macro">#x27;, verbose=False, use_best_model=True, task_type=&class="macro">#x27;CPU&class="macro">#x27;) model.fit(train_X, train_y, eval_set = (test_X, test_y), early_stopping_rounds=class="num">25, plot=False) # test on new data pr_tst = get_prices(TSTART_DATE, START_DATE) X = pr_tst[pr_tst.columns[class="num">1:]] X.columns = [&class="macro">#x27;&class="macro">#x27;] * len(X.columns) class="macro">#test the learned model p = model.predict_proba(X) p2 = [x[class="num">0]<class="num">0.5 for x in p] pr2 = pr_tst.iloc[:len(p2)].copy() pr2[&class="macro">#x27;labels&class="macro">#x27;] = p2 R2 = tester(pr2, MARKUP, plot=False) class="kw">return [R2, samples, model] res = [] for i in range(class="num">50): res.append(brute_force(class="num">10000)) print(&class="macro">#x27;Iteration: &class="macro">#x27;, i, &class="macro">#x27;R^class="num">2: &class="macro">#x27;, res[-class="num">1][class="num">0]) res.sort() test_model(res[-class="num">1]) test_model(res[-class="num">2]) >>> res[-class="num">2][class="num">0] class="num">0.9576444017048906 # class="kw">export best model to mql
把训练好的模型直接吐成 MQL5 代码
在 Python 侧完成模型训练后,可以用 export_model_to_MQL_code 把倒数第二折的交叉验证结果里的模型对象导出为 MT5 可用的代码。 传入的 res[-2][2] 指向交叉验证结果列表中索引为 -2 的那一项、其内部索引为 2 的子元素,通常对应某一折训练得到的模型实例。 实际落地时,先在 Python 环境跑通 res 的结构打印,确认 [-2][2] 确实是模型而非指标矩阵,再执行导出,避免把错误对象写进 EA。
export_model_to_MQL_code(res[-class="num">2][class="num">2])
「重跑与调参打破随机性困局」
这套流程里随机源不止一处:交易样本随机抽样、GMM 自身训练带随机、后验分布再抽样、CatBoost 也非 deterministic。同份数据跑一次和跑五次,出来的模型质量可能差出一截。 最直接的做法是把整段程序连续重启 3~5 次,挑验证集表现最稳的那版留用,而不是跑完就当定稿。 若多次重跑仍拿不到稳定模型,先动 LOOK_BACK 长度,再调移动平均的周期数与个数;样本层面可改从 GMM 抽出的样本量,以及训练/测试窗口的切分比例。外汇与贵金属波动率高,模型漂移快,上述改动都建议在 MT5 用历史数据复跑确认。