利用CatBoost机器学习模型作为趋势跟踪策略的过滤器·综合运用
🤖

利用CatBoost机器学习模型作为趋势跟踪策略的过滤器·综合运用

(3/3)·把训练好的机器学习模型接回MT5 EA,用统计测试看清它到底滤掉了多少噪音

新手友好 第 3/3 篇
很多交易者把模型当黑箱直接丢进EA,结果样本不足、标签错位,回测漂亮实盘崩。先确认核心策略本身能赚钱且信号够多,机器学习才只是锦上添花的过滤器。

把回测和特征脏数据收拾干净

MT5 导出的回测报告常是堆满格式的 xlsx,做机器学习只需每笔交易盈亏的 0/1 标签。先按实际起始行跳过表头,比如示例代码里 skiprows=10757,把交易结果段抽出来存 csv,再用奇偶索引过滤拿到纯盈亏序列:盈利记 1、亏损记 0。 若跑完发现 processed_bin.csv 里全是 0,八成是起始行奇偶没对齐。回测明细里 Profit 列每隔一行才是真实成交,过滤时用 index % 2 == 0 取偶数位,否则标签全反。 CFileCSV 落地的特征文件常挤成一列字符串,用 sep=';' 读入后先 dropna、去重,再把 object 列用 cat.codes 做标签编码转数值。Cleaned.csv 生成后,和 processed_bin.csv 按索引 inner merge,得到 merged_data.csv 统一结构。 验证合并没错位很简单:打开三个 csv 看末尾索引是否一致。一致就说明样本对齐,后面直接喂 CatBoost 不会串样本。外汇与贵金属数据受滑点跳空影响,这类清洗后仍可能含噪声,实盘前须小批量验证。

MQL5 / C++
class="kw">import pandas as pd
# Replace &class="macro">#x27;your_file.xlsx&class="macro">#x27; with the path to your file
input_file = &class="macro">#x27;ML2.xlsx&class="macro">#x27;
# Load the Excel file and skip the first {skiprows} rows
df = pd.read_excel(input_file, skiprows=class="num">10757)
# Save the extracted content to a CSV file
output_file = &class="macro">#x27;extracted_content.csv&class="macro">#x27;
df.to_csv(output_file, index=False)
print(f"Content has been saved to {output_file}.")
class="kw">import pandas as pd
# Load the CSV file
file_path = &class="macro">#x27;extracted_content.csv&class="macro">#x27;  # Update with the correct file path if needed
data = pd.read_csv(file_path)
# Select the &class="macro">#x27;profit&class="macro">#x27; column(assumed to be &class="macro">#x27;Unnamed: class="num">10&class="macro">#x27;) and filter rows as per your instructions
profit_data = data["Profit"][class="num">1:-class="num">1]
profit_data = profit_data[profit_data.index % class="num">2 == class="num">0]  # Filter for rows with odd indices
profit_data = profit_data.reset_index(drop=True)  # Reset index
# Convert to class="type">class="kw">float, then apply the condition to set values to class="num">1 if > class="num">0, otherwise to class="num">0
profit_data = pd.to_numeric(profit_data, errors=&class="macro">#x27;coerce&class="macro">#x27;).fillna(class="num">0)  # Convert to class="type">class="kw">float, replacing NaN with class="num">0
profit_data = profit_data.apply(lambda x: class="num">1 if x > class="num">0 else class="num">0)  # Apply condition
# Save the processed data to a new CSV file with index
output_csv_path = &class="macro">#x27;processed_bin.csv&class="macro">#x27;
profit_data.to_csv(output_csv_path, index=True, header=[&class="macro">#x27;bin&class="macro">#x27;])
print(f"Processed data saved to {output_csv_path}")
class="kw">import pandas as pd
# Load the CSV file with semicolon separator
file_path = &class="macro">#x27;ML.csv&class="macro">#x27;
data = pd.read_csv(file_path, sep=&class="macro">#x27;;&class="macro">#x27;)
# Drop rows with any missing or incomplete values
data.dropna(inplace=True)
# Drop any duplicate rows if present
data.drop_duplicates(inplace=True)
# Convert non-numeric columns to numerical format
for col in data.columns:
    if data[col].dtype == &class="macro">#x27;object&class="macro">#x27;:
        # Convert categorical to numerical using label encoding
        data[col] = data[col].astype(&class="macro">#x27;category&class="macro">#x27;).cat.codes
# Ensure all remaining columns are numeric and cleanly formatted for CatBoost
data = data.apply(pd.to_numeric, errors=&class="macro">#x27;coerce&class="macro">#x27;)
data.dropna(inplace=True)  # Drop any rows that might still contain NaNs after conversion
# Save the cleaned data to a new file in CatBoost-friendly format
output_file_path = &class="macro">#x27;Cleaned.csv&class="macro">#x27;
data.to_csv(output_file_path, index=False)
print(f"Data cleaned and saved to {output_file_path}")
class="kw">import pandas as pd
# Load the two CSV files
file1_path = &class="macro">#x27;processed_bin.csv&class="macro">#x27;  # Update with the correct file path if needed
file2_path = &class="macro">#x27;Cleaned.csv&class="macro">#x27;  # Update with the correct file path if needed
data1 = pd.read_csv(file1_path, index_col=class="num">0)  # Load first file with index
data2 = pd.read_csv(file2_path, index_col=class="num">0)  # Load second file with index
# Merge the two DataFrames on the index
merged_data = pd.merge(data1, data2, left_index=True, right_index=True, how=&class="macro">#x27;inner&class="macro">#x27;)
# Save the merged data to a new CSV file
output_csv_path = &class="macro">#x27;merged_data.csv&class="macro">#x27;
merged_data.to_csv(output_csv_path)

「把合并结果落盘到指定 CSV」

在 MQL5 之外的数据预处理环节,常需用 Python 把多源行情并成一张表再喂给回测。上面这行代码负责在合并完成后,向日志确认文件写入位置。 输出路径由变量 output_csv_path 决定,跑之前务必检查该路径在 MT5 数据目录或你指定的工作区中具备写权限,否则合并好的数据只会留在内存里。 验证方式很简单:执行脚本后去对应目录看是否生成了 CSV,再用 MT5 的「文件」→「打开数据文件夹」核对路径一致性。

MQL5 / C++
print(f"Merged data saved to {output_csv_path}")

◍ 把 CatBoost 模型训出来再转成 ONNX

做机器学习交易分类,不必从头啃算法理论,但数据切分和模型落地这两步必须自己跑通。先读入合并好的 CSV,把 bin 列当标签 y,其余列当特征 X,再按 80% 训练、20% 测试拆开。 CatBoost 分类器里有个细节:class_weights=[10, 1] 是因为样本里某一类正确信号太少,靠权重拉平偏向。迭代 20000 次、学习率 0.02、树深 5,early_stopping_rounds=50 能在验证集 50 轮无提升时停手,省算力。 训完先存一份 .cbm,但 MT5 只认 ONNX。用 convert_sklearn 把模型转成 ONNX 并写盘,target_opset 里 ai.onnx.ml 设 2 基本能覆盖 CatBoost 导出的算子。外汇与贵金属波动受事件驱动,模型在历史数据上表现好只代表概率优势,实盘仍属高风险。 下面这段是可直接改路径跑的通路:读数据、训 CatBoost、存双格式。把 merged_data.csv 换成你自己的特征表,RANDOM_STATE 补一个整数就能在本地复现。

MQL5 / C++
data = pd.read_csv("merged_data.csv",index_col=class="num">0)
XX = data.drop(columns=[&class="macro">#x27;bin&class="macro">#x27;])
yy = data[&class="macro">#x27;bin&class="macro">#x27;]
y = yy.values
X = XX.values
from catboost class="kw">import CatBoostClassifier
from sklearn.ensemble class="kw">import BaggingClassifier
# Define the CatBoost model with initial parameters
catboost_clf = CatBoostClassifier(
    class_weights=[class="num">10, class="num">1],   class="macro">#more weights to class="num">1 class cuz there&class="macro">#x27;s less correct cases
    iterations=class="num">20000,                # Number of trees(similar to n_estimators)
    learning_rate=class="num">0.02,              # Learning rate
    depth=class="num">5,                        # Depth of each tree
    l2_leaf_reg=class="num">5,
    bagging_temperature=class="num">1,
    early_stopping_rounds=class="num">50,
    loss_function=&class="macro">#x27;Logloss&class="macro">#x27;,        # Use &class="macro">#x27;MultiClass&class="macro">#x27; if it&class="macro">#x27;s a multi-class problem
    random_seed=RANDOM_STATE,
    verbose=class="num">1000,                   # Suppress output(set to a positive number if you want to see training progress)
)
fit = catboost_clf.fit(X_train, y_train)
catboost_clf.save_model(&class="macro">#x27;catboost_test.cbm&class="macro">#x27;)
model_onnx = convert_sklearn(
    model,
    "catboost",
    [("input", FloatTensorType([None, X.shape[class="num">1]]))],
    target_opset={"": class="num">12, "ai.onnx.ml": class="num">2},
)
# And save.
with open("CatBoost_test.onnx", "wb") as f:
    f.write(model_onnx.SerializeToString())

用阈值卡掉烂信号后的样本外表现

把 .onnx 模型丢进 MQL5/Files 后,直接在原来取数据的 EA 上改入场逻辑:调用 getData() 把特征向量塞进 xx,再让模型吐出交易成功概率。趋势跟踪本身胜率低、赔率高,所以模型给的概率常年在 0.5 以下,这点在打印日志里能直接看到分布。 样本按 8:2 切,外推那段覆盖 2021.1.1–2024.11.1。先拿 0.05 阈值跑样本内确认没训歪,再无阈值跑样本外当基线;之后逐级拉阈值看盈利模式怎么变。 阈值 0.05 时砍掉约一半原始信号,盈利反而掉,说明预测器大概率过拟合了训练集里的局部pattern。等阈值提到 0.1,盈利因子爬回基线之上;到 0.2 时过滤掉约 70% 交易,剩下单子质量明显抬高,盈利远超裸信号。统计上这段区间盈利能力和阈值正相关——模型越有把握,整体表现越能打。外汇/贵金属波动剧烈,这种相关也只是样本内倾向,实盘仍属高风险。 Python 里十折交叉验证的得分彼此差很小,说明准确率在不同切分下稳得住;平均对数损失约 -1,算中等水平。想继续往上抠,三条路:做特征重要性图剔弱特征(基于树的模型吃固定阈值规则,特征得先平稳化);写网格遍历调分类器超参;换模型——ML 测价格不灵但测波动挺准,隐马尔可夫抓隐藏趋势也常被用作趋势跟踪的过滤器。 别把正态当圣经 过拟合在金融 ML 里是常态,0.05 阈值盈利下滑就是预警,别因为样本内漂亮就直接上实盘。 让小布替你跑这套 附的代码里 prob[1] 是模型给的做多概率,min/max 就是阈值边界,改这两个数就能在 MT5 里复现上面的过滤实验。

MQL5 / C++
    if (maFast[class="num">1]>maSlow[class="num">1]&&maFast[class="num">0]<maSlow[class="num">0]&&sellpos == buypos){  
        xx= getData();
        prob = cat_boost.predict_proba(xx);
        if (prob[class="num">1]<max&&prob[class="num">1]>min)executeBuy(); 
    }
    if(maFast[class="num">1]<maSlow[class="num">1]&&maFast[class="num">0]>maSlow[class="num">0]&&sellpos == buypos){
        xx= getData();
        prob = cat_boost.predict_proba(xx);
        Print(prob);
        if(prob[class="num">1]<max&&prob[class="num">1]>min)executeSell();
      }
{&class="macro">#x27;score&class="macro">#x27;: array([-class="num">0.97148655, -class="num">1.25263677, -class="num">1.02043177, -class="num">1.06770248, -class="num">0.97339545, -class="num">0.88611439, -class="num">0.83877111, -class="num">0.95682533, -class="num">1.02443847, -class="num">1.1385681 ])}

「画得少,看得清」

整套流程走完,核心就落在附件那几个文件上:ML-Momentum.mq5 是最终执行 EA,CB2.ipynb 管 CatBoost 训练,OnnxConvert.ipynb 把 .cbm 转成 .onnx 丢进 MT5 跑。特征清洗、标签合并全用 Python 脚本链起来,不在 MT5 里硬算。 评论区有个细节值得记:有用户用 40–50 个特征、分钟级数据(每年约 25 万行)训练,2024 年样本内回测收益超 300%,其他年份却崩了,即便迭代降到 1k 也救不回来。作者回得直接——MT5 回测算了点差佣金,C++ 没算,所以两边对不上;更关键的是 ML 本质就在过拟合,样本内永远比样本外好看。 他这套用的是金属标签法,预测目标只绑自己的策略信号,噪声比直接猜收益率小,但可学样本少、复杂度得卡死。外汇和贵金属这种高波动品种,ML 过滤器只能当概率筛子,别当圣杯。 真要验证,就下 ZIP 把 Classic Trend Following.mq5 和 ML-Momentum.mq5 都跑一遍,关掉过滤器看基准、打开看过滤后,样本外年份的差异自己会说真话。

把模型回测交给小布盯盘
这些诊断小布盯盘的 AIGC 已内置,打开对应品种页即可看到样本量与盈利因子的实时估算,你只需专注特征设计和阈值调参。

常见问题

通常从 MetaTrader 5 用历史缓存导出 OHLC、均线与波动率等静态特征到 Python 训练,再将模型权重或概率输出集成回 EA 的 OnTick 逻辑中判断开仓。
可以,小布盯盘的品种页已内置 AIGC 诊断,能呈现过滤前后样本量与盈利因子变化,省去你手算大数定律显著性的重复劳动。
过高盈利因子往往伴随过拟合或样本极少,模型难以学到稳定规律;1 至 1.15 区间更利于模型在样本外给出略优于随机的微弱边缘。
可能倾向无法体现大数定律,过滤掉部分交易后剩余样本更少,结论的统计显著性会明显弱化,实盘失效概率上升。