数据科学和机器学习(第 37 部分):利用烛条形态和人工智能战胜市场·综合运用
🕯️

数据科学和机器学习(第 37 部分):利用烛条形态和人工智能战胜市场·综合运用

(3/3)·从单根柱线形态编码到机器学习预测,再到交易机器人终结,这一步走完才谈得上闭环

实战向进阶 第 3/3 篇
很多交易者把烛条形态当肉眼信号用,却从没想过把它们变成可量化特征。单根阳线阴线本身不预测任何事,真正有价值的是成体系地采集、标注并交给模型去学。本篇顺着前两篇的基础,直接把这条路走通。

「用 K 线形态向量喂给 CatBoost 下单」

这段逻辑跑在 OnTick 里,先把上一根已完成 K 线(索引 1)的 OHLC 抓出来,作为形态识别的输入源。 具体用了 CTALib 里的 10 个蜡烛函数:白线、黑线、十字星、蜻蜓十字、墓碑十字、锤子、倒锤、纺锤、看涨光头光脚、看跌光头光脚,拼成一个 vector x。训练侧对应的列名就是 White Candle 到 BullishMarubozu 这 10 维特征,维度必须对齐否则预测会错位。 x 丢进 catboost.predict(x).cls 拿到信号类:1 倾向做多、0 倾向做空。开仓前用 PosExists 查当前有无同反向持仓,无仓才用 m_trade.Buy/Sell 以最小手数 volume_ 进场,止损止盈先传 0 留给后续管理。 平仓靠 CloseTradeAfterTime,参数是「周期分钟数 × lookahead × 60」秒,也就是按训练时用的 timeframe 和前瞻窗口强制收单。外汇与贵金属杠杆高,信号只是概率倾向,实盘前务必在 MT5 策略测试器用历史数据验证信号分布。

MQL5 / C++
class="type">void OnTick()
  {
class=class="str">"cmt">//---

   class="type">class="kw">double open = iOpen(Symbol(), Period(), class="num">1),
         high = iHigh(Symbol(), Period(), class="num">1),
         low  = iLow(Symbol(), Period(), class="num">1),
         close = iClose(Symbol(), Period(), class="num">1);  

   vector x = {
               CTALib::CDLWHITECANDLE(open, close),
               CTALib::CDLBLACKCANDLE(open, close),
               CTALib::CDLDOJI(open, close),
               CTALib::CDLDRAGONFLYDOJI(open, high, low, close),
               CTALib::CDLGRAVESTONEDOJI(open, high, low, close),
               CTALib::CDLHAMMER(open, high, low, close),
               CTALib::CDLINVERTEDHAMMER(open, high, low, close),
               CTALib::CDLSPINNINGTOP(open, high, low, close),
               CTALib::CDLBULLISHMARUBOZU(open, high, low, close),
               CTALib::CDLBEARISHMARUBOZU(open, high, low, close)
              };

   class="type">long signal = catboost.predict(x).cls; class=class="str">"cmt">//Predicted class
   class="type">MqlTick ticks;
   if (!SymbolInfoTick(Symbol(), ticks))
      {
         printf("Failed to obtain ticks information, Error = %d",GetLastError());
         class="kw">return;
      }

   class="type">class="kw">double volume_ = SymbolInfoDouble(Symbol(), SYMBOL_VOLUME_MIN);  

   if (signal == class="num">1)
     {         
         if (!PosExists(POSITION_TYPE_BUY) && !PosExists(POSITION_TYPE_SELL))  
            m_trade.Buy(volume_, Symbol(), ticks.ask,class="num">0,class="num">0);
     }

   if (signal == class="num">0)
     {         
         if (!PosExists(POSITION_TYPE_SELL) && !PosExists(POSITION_TYPE_BUY))  
            m_trade.Sell(volume_, Symbol(), ticks.bid,class="num">0,class="num">0);
     }

    CloseTradeAfterTime((Timeframe2Minutes(Period())*lookahead)*class="num">60); class=class="str">"cmt">//Close the trade after a certain lookahead and according the the trained timeframe
  }

◍ 把K线形态喂给模型前先过一道滤网

这段逻辑在 OnTick 里先取了前一根已收盘K线(索引1)的 open/high/low/close,用 CTALib 里的一堆形态函数把单根K线转成特征向量 x,再丢给 catboost 做分类预测,返回 signal 作为大方向。 但注意被高亮的那段:真正下单前还有第二道闸门。代码里另外建了 patterns 向量,只装了十字、蜻蜓十字、墓碑十字、锤子、倒锤、纺锤、看涨/看跌marubozu这8种特殊形态,并在开仓条件里要求 patterns.Sum() > 0。也就是说,模型喊多(signal==1)还不够,当前K线必须至少命中一种特殊形态才放单。 外汇和贵金属波动受消息面干扰大,这种双确认能砍掉一部分模型在平淡K线上的假信号,但特殊形态本身在欧盘流动性差时也常失效,实际触发频率可能偏低,建议先在 MT5 策略测试器里跑一周 tick 数据看 patterns.Sum()>0 的命中率再决定阈值。

MQL5 / C++
class="type">void OnTick()
  {
class=class="str">"cmt">//---

   class="type">class="kw">double open = iOpen(Symbol(), Period(), class="num">1),
         high = iHigh(Symbol(), Period(), class="num">1),
         low  = iLow(Symbol(), Period(), class="num">1),
         close = iClose(Symbol(), Period(), class="num">1);


   vector x = {
                   CTALib::CDLWHITECANDLE(open, close),
                   CTALib::CDLBLACKCANDLE(open, close),
                   CTALib::CDLDOJI(open, close),
                   CTALib::CDLDRAGONFLYDOJI(open, high, low, close),
                   CTALib::CDLGRAVESTONEDOJI(open, high, low, close),
                   CTALib::CDLHAMMER(open, high, low, close),
                   CTALib::CDLINVERTEDHAMMER(open, high, low, close),
                   CTALib::CDLSPINNINGTOP(open, high, low, close),
                   CTALib::CDLBULLISHMARUBOZU(open, high, low, close),
                   CTALib::CDLBEARISHMARUBOZU(open, high, low, close)
                  };

   vector patterns = {
                  CTALib::CDLDOJI(open, close),
                  CTALib::CDLDRAGONFLYDOJI(open, high, low, close),
                  CTALib::CDLGRAVESTONEDOJI(open, high, low, close),
                  CTALib::CDLHAMMER(open, high, low, close),
                  CTALib::CDLINVERTEDHAMMER(open, high, low, close),
                  CTALib::CDLSPINNINGTOP(open, high, low, close),
                  CTALib::CDLBULLISHMARUBOZU(open, high, low, close),
                  CTALib::CDLBEARISHMARUBOZU(open, high, low, close)
                 }; class=class="str">"cmt">//Store all the special patterns 

   class="type">long signal = catboost.predict(x).cls; class=class="str">"cmt">//Predicted class
   class="type">MqlTick ticks;
   if (!SymbolInfoTick(Symbol(), ticks))
      {
         printf("Failed to obtain ticks information, Error = %d",GetLastError());
         class="kw">return;
      }

   class="type">class="kw">double volume_ = SymbolInfoDouble(Symbol(), SYMBOL_VOLUME_MIN);  

   if (signal == class="num">1 && patterns.Sum()>class="num">0) class=class="str">"cmt">//Check if there are is atleast a special pattern before opening a trade
    {        

空仓时才按信号开单

这段逻辑卡的是「账户里没单才动手」的边界。signal 为 1 且 patterns.Sum() 大于 0 时,先确认当前既没有多单也没有空单,才用 m_trade.Buy 以 ticks.ask 市价买入;signal 为 0 且形态累加大于 0 时,对称地检查无空无多后才 m_trade.Sell 以 ticks.bid 市价卖出。 两个分支都调 PosExists 做仓位闸门,避免在同一根 K 线里重复追单把仓位叠厚。CloseTradeAfterTime 那行按训练时框定的周期换算秒数强平,例如 Period() 返回 5 分钟、lookahead 设为 3,则 (5*3)*60 = 900 秒后离场。 外汇与贵金属杠杆高,信号为 0 或 1 只代表模型倾向,实盘前请在 MT5 策略测试器用历史 tick 跑一遍这套开仓条件,确认 PosExists 的返回逻辑和你账户实际持仓枚举一致。

MQL5 / C++
if (!PosExists(POSITION_TYPE_BUY) && !PosExists(POSITION_TYPE_SELL))
      m_trade.Buy(volume_, Symbol(), ticks.ask,class="num">0,class="num">0);
   }

  if (signal == class="num">0 && patterns.Sum()>class="num">0) class=class="str">"cmt">//Check if there are is atleast a special pattern before opening a trade
   {
     if (!PosExists(POSITION_TYPE_SELL) && !PosExists(POSITION_TYPE_BUY))
         m_trade.Sell(volume_, Symbol(), ticks.bid,class="num">0,class="num">0);
   }

   CloseTradeAfterTime((Timeframe2Minutes(Period())*lookahead)*class="num">60); class=class="str">"cmt">//Close the trade after a certain lookahead and according the the trained timeframe
}

「把烛形喂给模型前先想清楚的事」

用 AI 做烛条形态预测确实跑得通,但和均线、MACD 这类数学指标不同,烛形靠的是“长得像不像”,一点视觉误读就可能让标签完全反转。评论区有人拿 D1 和 H4 举过例子:同一段报价历史,仅把小时内的分钟人为平移,形态就能从锤子变成蜻蜓十字星,经纪商时区偏移也会干同样的事。 作者自己在讨论里补了一刀:纯按外观打标签不够,锤子要当看涨信号至少得处在下降趋势里,趋势检测才是等式里的关键项。如果你打算用附件里的 Candlestick Patterns Collect.mq5 去导 CSV 训 Catboost,先把这段逻辑写进标注规则,不然模型学到的只是形状偏见。 附件里 Experts\CandlestickPatterns AI-EA.mq5 已经能加载 .onnx 跑预测,Indicators\Candlestick Identifier.mq5 可在图上肉眼核对形态。外汇和贵金属杠杆高、滑点狠,这类模型输出只代表概率倾向,开 MT5 接上脚本跑一轮自有数据再下结论。

把形态采集交给小布盯盘
这些诊断小布盯盘的 AIGC 已内置,打开对应品种页即可看到实时烛条形态标注与历史分布,你只需把特征导出接进自己的训练流程,省掉手写指标的第一步。

常见问题

多根柱线形态依赖上下文窗口,标注成本高且容易过拟合;单根形态在当前柱即可判定,适合高频采集与在线推理,模型迭代更轻。
统一的函数签名让形态检测可像技术指标一样链式调用,便于在 MQL5 指标和 EA 之间复用同一套逻辑,减少移植 bug。
外汇与贵金属属高风险市场,模型仅提供概率倾向,实盘表现受滑点、流动性与样本偏移影响,需做前向验证再考虑仓位。
小布盯盘内置了形态识别与数据面板,可辅助你完成采集和初筛,但模型训练与 EA 终结仍要在 MT5 环境自行部署。
更合理的是将模型推理结果作为信号层,EA 只负责执行与风控,这样后续换模型不必重写订单逻辑,关于信号层设计见前篇基础架构。