梯度提升(CatBoost)在交易系统开发中的应用. 初级的方法·综合运用
(3/3)·初级模型只是起点,真正能在 MT5 跑起来的机器学习交易系统还差这几步
从模型头文件抽取树结构片段
这段 Python 片段干的事很直接:打开编译好的 catmodel.h,把 CatBoost 模型里的树深度、分裂节点和边界参数抠出来,拼进要生成的 MQL5 函数体。 它用 data.find 定位到 "unsigned int TreeDepth" 起点,截到 "double Scale = 1;" 之前,这一段正好包含树集成所需的整型与浮点表。末尾补一个 ApplyCatboostModel 调用并把右括号收掉,相当于自动把训练端的模型骨架搬进 EA 的推理函数。 你在本地跑通这套抽取逻辑后,改一下文件路径就能复用;若头文件里 Scale 定义位置有变动,find 的终止串也要跟着改,否则会截到多余或缺失内容。
with open(&class="macro">#x27;catmodel.h&class="macro">#x27;, &class="macro">#x27;r&class="macro">#x27;) as file: data = file.read() code += data[data.find("unsigned class="type">int TreeDepth"):data.find("class="type">class="kw">double Scale = class="num">1;")] code +=&class="macro">#x27;\n\n&class="macro">#x27; code+= &class="macro">#x27;class="kw">return &class="macro">#x27; + &class="macro">#x27;ApplyCatboostModel(features, TreeDepth, TreeSplits , BorderCounts, Borders, LeafValues); } \n\n&class="macro">#x27;
◍ 把 CatBoost 模型接进 MT5 的 EA 骨架
模型训完存成 cat_model.mqh 之后,EA 这边要先把三个头文件挂上:MT4Orders.mqh 负责下单兼容,AccountInfo.mqh 管账户信息,cat_model.mqh 就是刚才导出的模型。注意 look_back 和 MA_period 这两个输入,必须和 Python 训练时一模一样——原文给的是 50 和 15,错一个就会让特征维度对不上,推理直接报错。 每根 K 线只跑一次逻辑,用 isNewBar() 拦住重复触发。核心是把最近 look_back 根收盘价和同周期 MA 的差值排成向量喂给 catboost_model(),返回 sig 落在 0~1 之间。 信号阈值卡在 0.5:sig>0.5 倾向做空,sig<0.5 倾向做多,和训练时的标签定义要一致。下单手数走 LotsOptimized(),止损写死 500 点(stoploss*_Point)。外汇和贵金属杠杆高,这套逻辑回测和实盘偏差可能很大,上 MT5 前先用策略测试器跑几周 tick 数据。 下面这段是 EA 主体里最关键的特征构造和下单片段,逐行拆一下: #include <MT4Orders.mqh> // 引入兼容 MT4 下单方式的封装 #include <Trade\AccountInfo.mqh> // 引入账户信息类 #include <cat_model.mqh> // 引入导出的 CatBoost 模型 sinput int look_back = 50; // 回看窗口,须与训练一致 sinput int MA_period = 15; // 均线周期,须与训练一致 sinput int OrderMagic = 666; // 订单魔术码,用于识别本 EA 订单 sinput double MaximumRisk=0.01; // 最大风险比例 sinput double CustomLot=0; // 自定义手数,0 表示由函数计算 input int stoploss = 500; // 止损点数 static datetime last_time=0; // 上次处理时间 #define Ask SymbolInfoDouble(_Symbol, SYMBOL_ASK) // 买价宏 #define Bid SymbolInfoDouble(_Symbol, SYMBOL_BID) // 卖价宏 int hnd; // MA 指标句柄(原文未展示创建,实盘需 iMA 赋值) if(!isNewBar()) return; // 非新柱直接退出 double ma[]; // 存均线序列 double pr[]; // 存收盘价序列 double ret[]; // 存差值特征 ArrayResize(ret, look_back); // 给特征数组定长 CopyBuffer(hnd, 0, 1, look_back, ma); // 拷 look_back 个均线值 CopyClose(NULL,PERIOD_CURRENT,1,look_back,pr); // 拷 look_back 个收盘价 for(int i=0; i<look_back; i++) // 逐根算差 ret[i] = pr[i] - ma[i]; // 价格减均线作为模型输入 ArraySetAsSeries(ret, true); // 改为时间序列方向 double sig = catboost_model(ret); // 模型推理得信号 for(int b = OrdersTotal() - 1; b >= 0; b--) // 倒序遍历订单 if(OrderSelect(b, SELECT_BY_POS) == true) { // 选中订单 if(OrderType() == 0 && OrderSymbol() == _Symbol && OrderMagicNumber() == OrderMagic && sig > 0.5) // 多单且信号翻空 if(OrderClose(OrderTicket(), OrderLots(), OrderClosePrice(), 0, Red)) {} // 平多 if(OrderType() == 1 && OrderSymbol() == _Symbol && OrderMagicNumber() == OrderMagic && sig < 0.5) // 空单且信号翻多 if(OrderClose(OrderTicket(), OrderLots(), OrderClosePrice(), 0, Red)) {} // 平空 } if(countOrders(0) == 0 && countOrders(1) == 0) { // 无持仓才开仓 if(sig < 0.5) // 信号偏多 OrderSend(Symbol(),OP_BUY,LotsOptimized(), Ask, 0, Bid-stoploss*_Point, 0, NULL, OrderMagic); // 开多带止损 else if(sig > 0.5) // 信号偏空 OrderSend(Symbol(),OP_SELL,LotsOptimized(), Bid, 0, Ask+stoploss*_Point, 0, NULL, OrderMagic); // 开空带止损 return; }
class="macro">#include <MT4Orders.mqh> class="macro">#include <Trade\AccountInfo.mqh> class="macro">#include <cat_model.mqh> sinput class="type">int look_back = class="num">50; sinput class="type">int MA_period = class="num">15; sinput class="type">int OrderMagic = class="num">666; class=class="str">"cmt">//Orders magic sinput class="type">class="kw">double MaximumRisk=class="num">0.01; class=class="str">"cmt">//Maximum risk sinput class="type">class="kw">double CustomLot=class="num">0; class=class="str">"cmt">//Custom lot input class="type">int stoploss = class="num">500; class="kw">static class="type">class="kw">datetime last_time=class="num">0; class="macro">#define Ask SymbolInfoDouble(_Symbol, SYMBOL_ASK) class="macro">#define Bid SymbolInfoDouble(_Symbol, SYMBOL_BID) class="type">int hnd; if(!isNewBar()) class="kw">return; class="type">class="kw">double ma[]; class="type">class="kw">double pr[]; class="type">class="kw">double ret[]; ArrayResize(ret, look_back); CopyBuffer(hnd, class="num">0, class="num">1, look_back, ma); CopyClose(NULL,PERIOD_CURRENT,class="num">1,look_back,pr); for(class="type">int i=class="num">0; i<look_back; i++) ret[i] = pr[i] - ma[i]; ArraySetAsSeries(ret, true); class="type">class="kw">double sig = catboost_model(ret); for(class="type">int b = OrdersTotal() - class="num">1; b >= class="num">0; b--) if(OrderSelect(b, SELECT_BY_POS) == true) { if(OrderType() == class="num">0 && OrderSymbol() == _Symbol && OrderMagicNumber() == OrderMagic && sig > class="num">0.5) if(OrderClose(OrderTicket(), OrderLots(), OrderClosePrice(), class="num">0, Red)) { } if(OrderType() == class="num">1 && OrderSymbol() == _Symbol && OrderMagicNumber() == OrderMagic && sig < class="num">0.5) if(OrderClose(OrderTicket(), OrderLots(), OrderClosePrice(), class="num">0, Red)) { } } if(countOrders(class="num">0) == class="num">0 && countOrders(class="num">1) == class="num">0) { if(sig < class="num">0.5) OrderSend(Symbol(),OP_BUY,LotsOptimized(), Ask, class="num">0, Bid-stoploss*_Point, class="num">0, NULL, OrderMagic); else if(sig > class="num">0.5) OrderSend(Symbol(),OP_SELL,LotsOptimized(), Bid, class="num">0, Ask+stoploss*_Point, class="num">0, NULL, OrderMagic); class="kw">return; }
「用策略测试器跑这个MLEA」
编译好的 EA 直接丢进 MT5 标准策略测试器即可,但时间框架必须和 Python 训练时用的对齐,否则特征分布错位。输入参数 look_back 与 MA_period 也得和训练脚本里完全一致,不然模型读到的窗口长度不对。 先在训练 covering 的训练+验证子样本上回测,曲线和定制测试器结果基本重合,只有点差带来的细微偏差。这一步只是确认工程链路没断,不代表样本外能力。 换成年初之后的全新数据再跑,性能明显下滑。外汇与贵金属属高杠杆高风险品种,这种样本外衰减倾向说明模型对时段结构敏感,实盘前必须自己用新行情重测。
初级模型的天花板与突破口
这套梯度提升方案被作者自己定位为“初级”,核心原因是它不带任何关于价格模式的先验知识。所有形态识别全靠模型在训练里自己啃出来,而纯靠 GBDT 这类方法去捞非线性结构,能力上限摆在那,复杂盘整区的边界很容易学歪。 另一个被点明的特征是:训练时用了交易的随机抽样。这导致换一个训练周期,跑出来的权重和准确率都会漂移——表面看是缺陷,但作者认为它其实支撑了“暴力搜索”思路,同一份数据多抽几次能探出策略稳健性的底。 模型对新数据的表现完全不可控,你永远不知道它拿到没见过的行情会怎么判。外汇与贵金属杠杆高、跳空频繁,这种未知泛化风险必须当成实盘前的硬门槛。 后续提了三条可能提效的路:按外部标准(比如样本外新数据表现)筛模型;换数据采样与堆叠分类器;以及把先验假设做成不同性质的特征喂进去。开 MT5 用 Python/R 桥接跑一遍随机抽样+GBDT,先量出你品种的周期漂移幅度,再谈升级。
◍ 把 CatBoost 模型真正跑进 MT5 之前
这套 CatBoost 二分类思路已经走完闭环:Python 侧训练、导出模型、再用 MQL5 写成可直接挂终端的 EA。压缩包里三个文件是硬通货——catboost_learning.py 约 5.45 KB,cat_model.mqh 约 194.17 KB,cat_trader.mq5 约 4.98 KB,下载后先别急着拖进图表。 评论区里 Busingye Tusasirwe 在 2025 年 3 月反馈:MT5 测试仪没能复现 Python 同期的收益曲线,作者回帖点出 CatBoost 新版可能改了模型解析逻辑,导致旧 mqh 与新训练产物不兼容。也就是说,直接拿原文附件跑,结果可能偏离预期。 Ney Borges 做了实用改动:让 mqh 按图表周期分别保存,EA 同时加载多周期训练文件出信号。外汇与贵金属属高风险品种,模型信号仅作概率参考,实盘前务必在策略测试器用历史数据验证。 清理训练数据里的缺失值是第一步,下面这行就是原文处理 NaN 的入口,少做一步,后面编译或预测都可能带坑。
prices.dropna()