利用CatBoost机器学习模型作为趋势跟踪策略的过滤器·综合运用
(3/3)·把训练好的机器学习模型接回MT5 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 不会串样本。外汇与贵金属数据受滑点跳空影响,这类清洗后仍可能含噪声,实盘前须小批量验证。
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 的「文件」→「打开数据文件夹」核对路径一致性。
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 补一个整数就能在本地复现。
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 里复现上面的过滤实验。
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 都跑一遍,关掉过滤器看基准、打开看过滤后,样本外年份的差异自己会说真话。