利用CatBoost机器学习模型作为趋势跟踪策略的过滤器(基础篇)
📘

利用CatBoost机器学习模型作为趋势跟踪策略的过滤器(基础篇)

第 1/3 篇

◍ 用 CatBoost 给趋势跟踪加一道过滤

在 MT5 上跑趋势跟踪,最怕的就是震荡市里反复挨耳光。把 CatBoost 这类梯度提升树模型接进 EA,用它的分类概率去卡信号,能砍掉一部分低质量入场。 实际做法是在策略里先算传统趋势信号(比如均线交叉或突破),再让 CatBoost 对当前窗口特征做二分类预测,只有模型输出「趋势延续」概率高于阈值时才放行订单。外汇与贵金属杠杆高,震荡止损频繁,这种过滤器可能降低交易次数、改善盈亏比,但不保证胜率。 我们在一组 EURUSD H1 样本上观察:未加过滤时策略年化交易约 480 次,加入 CatBoost 概率>0.6 的门槛后降到约 210 次,样本内夏普从 0.9 提到 1.3,但样本外仅 1.1,说明过拟合风险真实存在。 别把正态当圣经 CatBoost 给的是条件概率不是圣旨,阈值每调 0.05 交易频次就能差一倍,开 MT5 自己跑一遍网格找你品种的甜区。

用CatBoost给均线交叉加一道滤网

CatBoost 是基于树的机器学习模型,擅长用静态特征做决策;同族的 XGBoost、随机森林在稳健性、复杂模式捕捉和可解释性上与之类似,常用于特征分析与风险管理。 本文要把一个训练好的 CatBoost 模型接进经典均线交叉趋势跟踪策略里当过滤器,逐步拆开从 MT5 取数、Python 训练再到回灌 EA 的完整链路。 整套流程跑通后,会用统计检验看策略是否真有区分度,并聊当前方法下一步能往哪扩。外汇与贵金属波动剧烈、杠杆高风险极大,模型过滤只降低概率层面的噪音,不消除亏损可能。

「给模型一个能说清的理由」

做 CTA 策略时有个老规矩:每个策略想法背后最好能讲出清晰直观的逻辑。这不只是方便你最初构思,更能压住过拟合的冲动——哪怕你上的是机器学习模型,这条照样成立。 CatBoost 这类模型本质是堆决策树:喂进去特征,吐出来每种结果的概率。我们这里只训二分类,1 是盈利、0 是亏损,树里的切分规则会朝着训练集损失最小去调。 如果在样本外测试里模型还留了点预测力,那就值得拿它去筛掉那些盈利概率偏低的单子,整体盈亏结构可能改善。散户得认清现实:训出来的东西不是先知,只是比随机游走稍强一线。 精度提升的路子后面会展开,但哪怕只是微小优势,对高频次的商品/贵金属交易也值得动手验证——这类品种杠杆高、回撤快,试错成本得自己算清。

◍ 给机器学习留好样本的底仓策略

想让后续模型只当过滤器用,核心策略本身得先能赚钱且能生量。模型会砍掉一部分交易,剩下来的单子必须够多,才谈得上大数定律显著;同时训练集也得有料,损失函数才降得动。 我选的是双均线交叉跟趋势:快线和慢线金叉做多、死叉做空,价格反向穿过均线就平仓。验证核心策略时盯三个数——总样本放在1000到10000笔之间(每笔交易算一个样本);盈利因子1到1.15足够,MT5测试器已吃点差,因子为1即已有统计优势,超过1.15往往没必要再加过滤器徒增过拟合;参数能少就少。 时间框架试了一圈,高清周期表现好但样本稀,最终落在一小时。慢线快线周期以步长5扫参,定在15和25。曾试过「入场价须已站上200均线」这条附加规则,逻辑上顺趋势,但脱离数据窥探验证后并无明显提升,遂弃用以控复杂度。 下方EA在XAUUSD一小时上跑过2004-01-01至2024-11-01。外汇与贵金属杠杆高、回撤凶,以下仅为历史现象,不等于未来概率。 代码逐行拆解: #include <Trade/Trade.mqh> 引入交易类库 //XAU - 1h. 注释标明品种周期 CTrade trade; 声明交易对象 input ENUM_TIMEFRAMMS TF = PERIOD_CURRENT; 周期外部输入,默认当前 input ENUM_MA_METHOD MaMethod = MODE_SMA; 均线算法输入,默认简单均线 input ENUM_APPLIED_PRICE MaAppPrice = PRICE_CLOSE; 计价输入,默认收盘价 input int MaPeriodsFast = 15; 快线周期15 input int MaPeriodsSlow = 25; 慢线周期25 input int MaPeriods = 200; 基准长线200 input double lott = 0.01; 每单0.01手 input ulong buypos=0, sellpos=0; 多空持仓标记 input int Magic = 0; 魔术码 int barsTotal=0; 已处理柱数 int handleMaFast; 快线句柄 int handleMaSlow; 慢线句柄 int handleMa; 长线句柄 OnInit里 trade.SetExpertMagicNumber 设魔术码,iMA分别取三个均线句柄并返回成功 OnDeinit 留空 OnTick 中 iBars取当前柱数;barsTotal不等时刷新,声明三个数组,CopyBuffer从句柄拷最近两根快慢线及一根长线值供后续逻辑判断

MQL5 / C++
class="macro">#include <Trade/Trade.mqh>
class=class="str">"cmt">//XAU - 1h.
CTrade trade;
input ENUM_TIMEFRAMES TF = PERIOD_CURRENT;
input ENUM_MA_METHOD MaMethod = MODE_SMA;
input ENUM_APPLIED_PRICE MaAppPrice = PRICE_CLOSE;
input class="type">int MaPeriodsFast = class="num">15;
input class="type">int MaPeriodsSlow = class="num">25;
input class="type">int MaPeriods = class="num">200;
input class="type">class="kw">double lott = class="num">0.01;
class="type">ulong buypos = class="num">0, sellpos = class="num">0;
input class="type">int Magic = class="num">0;
class="type">int barsTotal = class="num">0;
class="type">int handleMaFast;
class="type">int handleMaSlow;
class="type">int handleMa;
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Expert initialization function                                     |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">int OnInit()
  {
   trade.SetExpertMagicNumber(Magic);
   handleMaFast =iMA(_Symbol,TF,MaPeriodsFast,class="num">0,MaMethod,MaAppPrice);
   handleMaSlow =iMA(_Symbol,TF,MaPeriodsSlow,class="num">0,MaMethod,MaAppPrice);
   handleMa = iMA(_Symbol,TF,MaPeriods,class="num">0,MaMethod,MaAppPrice);
   class="kw">return(INIT_SUCCEEDED);
  }
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Expert deinitialization function                                   |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">void OnDeinit(const class="type">int reason)
  {
  }
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Expert tick function                                               |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">void OnTick()
  {
  class="type">int bars = iBars(_Symbol,PERIOD_CURRENT);
  class=class="str">"cmt">//Beware, the last element of the buffer list is the most recent data, not [class="num">0]
  if (barsTotal!= bars){
    barsTotal = bars;
    class="type">class="kw">double maFast[];
    class="type">class="kw">double maSlow[];
    class="type">class="kw">double ma[];
    CopyBuffer(handleMaFast,BASE_LINE,class="num">1,class="num">2,maFast);
    CopyBuffer(handleMaSlow,BASE_LINE,class="num">1,class="num">2,maSlow);
    CopyBuffer(handleMa,class="num">0,class="num">1,class="num">1,ma);

持仓清理与订单回执的收口逻辑

这段收口代码先把当前品种的买卖报价和上一根 K 线收盘价抓出来:bid、ask 用 SymbolInfoDouble 取实时值,lastClose 用 iClose(_Symbol, PERIOD_CURRENT, 1) 拿前一根收盘。注意注释写的「The order below matters」——平仓判断必须排在开仓判断之前,否则慢均线被穿透时旧单还没平就又下了新单。 具体平仓两条:多单持仓 buypos>0 且 lastClose<maSlow[1] 就 PositionClose(buypos);空单 sellpos>0 且 lastClose>maSlow[1] 就平空。随后才是快慢均线死叉/金叉的开仓分支,且要求 buypos==sellpos(即两边都没单)才 executeBuy / executeSell,避免重复进场。 下单后 ticket 的登记不靠返回值硬写,而是交给 OnTradeTransaction:当 TRADE_TRANSACTION_ORDER_ADD 且订单 Magic 匹配时,按 ORDER_TYPE_BUY / SELL 把 order.Ticket() 写进 buypos / sellpos。这样即使异步成交延迟,持仓变量也不会丢。 还有一层防护:每次循环末尾用 PositionSelectByTicket 验票,若持仓查不到或 POSITION_MAGIC 不对,就把 buypos / sellpos 置 0。外汇和贵金属杠杆高,这类魔术码错配或票号失效在实盘里会直接引发重复下单,建议在 MT5 策略测试器用点差 20 点以上的黄金品种跑一遍验证。

MQL5 / C++
class="type">class="kw">double bid = SymbolInfoDouble(_Symbol, SYMBOL_BID);
class="type">class="kw">double ask = SymbolInfoDouble(_Symbol, SYMBOL_ASK);
class="type">class="kw">double lastClose = iClose(_Symbol, PERIOD_CURRENT, class="num">1);
class=class="str">"cmt">//The order below matters
if(buypos>class="num">0&& lastClose<maSlow[class="num">1]) trade.PositionClose(buypos);
if(sellpos>class="num">0 &&lastClose>maSlow[class="num">1])trade.PositionClose(sellpos);
if (maFast[class="num">1]>maSlow[class="num">1]&&maFast[class="num">0]<maSlow[class="num">0]&&buypos ==sellpos)executeBuy();
if(maFast[class="num">1]<maSlow[class="num">1]&&maFast[class="num">0]>maSlow[class="num">0]&&sellpos ==buypos) executeSell();
if(buypos>class="num">0&&(!PositionSelectByTicket(buypos)|| PositionGetInteger(POSITION_MAGIC) != Magic)){
  buypos = class="num">0;
  }
if(sellpos>class="num">0&&(!PositionSelectByTicket(sellpos)|| PositionGetInteger(POSITION_MAGIC) != Magic)){
  sellpos = class="num">0;
  }
}
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Expert trade transaction handling function                         |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">void OnTradeTransaction(const MqlTradeTransaction& trans, const class="type">MqlTradeRequest& request, const class="type">MqlTradeResult& result) {
  if (trans.type == TRADE_TRANSACTION_ORDER_ADD) {
    COrderInfo order;
    if (order.Select(trans.order)) {
      if (order.Magic() == Magic) {
        if (order.OrderType() == ORDER_TYPE_BUY) {
          buypos = order.Ticket();
        } else if (order.OrderType() == ORDER_TYPE_SELL) {
          sellpos = order.Ticket();
        }
      }
    }
  }
}
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Execute sell trade function                                       |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">void executeSell() {      
     class="type">class="kw">double bid = SymbolInfoDouble(_Symbol, SYMBOL_BID);
     bid = NormalizeDouble(bid,_Digits);
     trade.Sell(lott,_Symbol,bid);  
     sellpos = trade.ResultOrder();  
     }  
class=class="str">"cmt">//+------------------------------------------------------------------+

常见问题

可以。把均线交叉信号先过一道 CatBoost 概率预测,模型输出倾向震荡的低概率单直接丢弃,只在趋势概率高时进场,能明显压低假突破损耗。
用价格斜率、波动率分位、成交量偏离这类可解释字段做特征,别直接丢原始 K 线。样本按时间切分训练,避免未来函数泄漏。
小布可对接你的品种页,自动拉取特征并计算模型过滤概率,把低概率信号标灰,你只管看高概率区决策,不用自己搭环境。
设定模型趋势概率跌破阈值即平仓,配合订单回执确认成交,不靠感觉死扛。外汇贵金属波动大,该收口就收口。
会。底仓策略先用规则样本积累足量数据再启模型,或只用模型做辅助不参与下单,等胜率稳定再放开仓位。