价格行为分析工具包开发(第二十六部分):针形线、吞没形态与RSI背离(多模式)工具·进阶篇
📘

价格行为分析工具包开发(第二十六部分):针形线、吞没形态与RSI背离(多模式)工具·进阶篇

第 2/3 篇

「EA 初始化与每根 K 线的数据抓取逻辑」

在 MT5 里写背离策略 EA,第一步是把 RSI 句柄抓到手:用 iRSI(_Symbol, _Period, RSI_Period, PRICE_CLOSE) 绑定当前品种当前周期,若返回 INVALID_HANDLE 直接 INIT_FAILED,避免后面空指针崩。 紧接着把所有价格与时间的数组用 ArraySetAsSeries(..., true) 设成时间序列,这样 rsiBuffer[0]、closeBuffer[0] 就是「最新一根」,写判断时不用倒着数下标,少出 bug。 OnTick 里先卡 Bars(_Symbol, _Period) < 20 就退出——少于 20 根Bar 时统计量不可信,外汇与贵金属波动大,样本不够容易误触发,属高风险环境。 数据拷贝用 CopyTime / CopyOpen / CopyHigh / CopyLow / CopyClose 一次性拉 20 根,任意一项 <=0 就 return;再用 timeBuffer[1]==lastBarTime 拦掉同一根 candle 的重复信号,只认「刚走完的那根」开仓逻辑。 最后 CopyBuffer 刷 RSI,调 FindSignalBar 拿方向,多空入场价与止损分别挂在 highBuffer[1]+EntryBuffer*_Point 与 lowBuffer[1]-SL_Pips*_Point 这类位置,并 DrawSignal 画出来——你打开 MT5 把这段粘进 EA,改 RSI_Period 就能看不同周期背离频率。

MQL5 / C++
rsiHandle = iRSI(_Symbol, _Period, RSI_Period, PRICE_CLOSE);
if(rsiHandle == INVALID_HANDLE)
   class="kw">return INIT_FAILED;
class=class="str">"cmt">// Set arrays as series for easier access to recent data
ArraySetAsSeries(rsiBuffer,  true);
ArraySetAsSeries(timeBuffer, true);
ArraySetAsSeries(openBuffer, true);
ArraySetAsSeries(highBuffer, true);
ArraySetAsSeries(lowBuffer,  true);
ArraySetAsSeries(closeBuffer, true);
class="kw">return INIT_SUCCEEDED;
}
class="type">void OnTick()
{
   class=class="str">"cmt">// Ensure enough data is available
   if(Bars(_Symbol, _Period) < class="num">20)
      class="kw">return;
   class=class="str">"cmt">// Copy recent market data
   if(CopyTime(_Symbol, _Period, class="num">0, class="num">20, timeBuffer)  <= class="num">0 ||
       CopyOpen(_Symbol, _Period, class="num">0, class="num">20, openBuffer)  <= class="num">0 ||
       CopyHigh(_Symbol, _Period, class="num">0, class="num">20, highBuffer)  <= class="num">0 ||
       CopyLow(_Symbol, _Period, class="num">0, class="num">20, lowBuffer)    <= class="num">0 ||
       CopyClose(_Symbol, _Period, class="num">0, class="num">20, closeBuffer) <= class="num">0)
      class="kw">return;
   class=class="str">"cmt">// Prevent multiple signals in the same candle
   if(timeBuffer[class="num">1] == lastBarTime)
      class="kw">return;
   lastBarTime = timeBuffer[class="num">1];
   class=class="str">"cmt">// Update RSI data
   if(CopyBuffer(rsiHandle, class="num">0, class="num">0, class="num">20, rsiBuffer) <= class="num">0)
      class="kw">return;
   class=class="str">"cmt">// Detect potential divergence and pattern
   class="type">int dir = FindSignalBar();
   if(dir == class="num">0)
      class="kw">return;
   class=class="str">"cmt">// Determine direction and compute entry/stop levels
   class="type">bool isBullish = (dir > class="num">0);
   class="type">int idx = class="num">1; class=class="str">"cmt">// most recent completed bar
   class="type">class="kw">double entry = isBullish
                 ? highBuffer[idx] + EntryBuffer * _Point
                 : lowBuffer[idx] - EntryBuffer * _Point;
   class="type">class="kw">double stopL = isBullish
                 ? lowBuffer[idx] - SL_Pips * _Point
                 : highBuffer[idx] + SL_Pips * _Point;
   class=class="str">"cmt">// Visualize and notify
   DrawSignal(idx, isBullish, entry, stopL);
}

◍ 背离叠加蜡烛形态的信号判定函数

下面这段逻辑把「RSI 背离」和「价格行为形态」绑在一起才肯发信号,单独出现背离一律不认。函数扫描最近 5~15 根 K 线,只要价格创新低而 RSI 未创新低且处于超卖区,就标记 bullDiv;反向同理标记 bearDiv。 若两个背离标志都为假,直接 return 0 不浪费算力。只有背离成立、且当前根(索引 1)出现看涨 Pin Bar 或看涨吞没,才返回 +1;看跌组合则返回 -1。外汇与贵金属波动剧烈,这种双重过滤只能提高信号质量,无法消除假突破风险。 Pin Bar 的几何约束很硬:下影线必须大于实体的 2 倍,上影线小于实体 0.5 倍,实体自身还得超过整根振幅的 10%。熊版只是把方向和下影/上影角色对调。 吞没形态这边只写了半截——函数先排除当前根收阴或开盘高于前一根收盘的情况,剩下部分通常要继续比对实体完全包裹前一根才返回 true。开 MT5 把这段补完,回测 EURUSD 15 分钟,超卖阈值设 30 时信号频率会明显下降。

MQL5 / C++
class="type">int FindSignalBar()
{
   class="type">bool bullDiv = class="kw">false, bearDiv = class="kw">false;
   for(class="type">int i = class="num">5; i <= class="num">15; i++)
   {
      class=class="str">"cmt">// Bullish divergence condition
      if(lowBuffer[i] > lowBuffer[class="num">1] && rsiBuffer[i] < rsiBuffer[class="num">1] && rsiBuffer[class="num">1] < RSI_Oversold)
         bullDiv = true;
      class=class="str">"cmt">// Bearish divergence condition
      if(highBuffer[i] < highBuffer[class="num">1] && rsiBuffer[i] > rsiBuffer[class="num">1] && rsiBuffer[class="num">1] > RSI_Overbought)
         bearDiv = true;
   }
   class=class="str">"cmt">// No divergence detected
   if(!bullDiv && !bearDiv)
      class="kw">return class="num">0;
   class=class="str">"cmt">// Check for candlestick patterns supporting divergence
   class="type">bool bullPat = IsBullishPinBar(class="num">1) || IsBullishEngulfing(class="num">1);
   class="type">bool bearPat = IsBearishPinBar(class="num">1) || IsBearishEngulfing(class="num">1);
   class=class="str">"cmt">// Confirmed signals
   if(bullDiv && bullPat)
      class="kw">return +class="num">1;
   if(bearDiv && bearPat)
      class="kw">return -class="num">1;
   class="kw">return class="num">0; class=class="str">"cmt">// No valid signal
}
class=class="str">"cmt">// Bullish Pin Bar Pattern
class="type">bool IsBullishPinBar(class="type">int i)
{
   class="type">class="kw">double body = MathAbs(openBuffer[i] - closeBuffer[i]);
   class="type">class="kw">double rng   = highBuffer[i] - lowBuffer[i];
   class="type">class="kw">double lw   = MathMin(openBuffer[i], closeBuffer[i]) - lowBuffer[i];
   class="type">class="kw">double uw   = highBuffer[i] - MathMax(openBuffer[i], closeBuffer[i]);
   class="kw">return closeBuffer[i] > openBuffer[i]
          && lw > class="num">2.0 * body
          && uw < class="num">0.5 * body
          && body > class="num">0.1 * rng;
}
class=class="str">"cmt">// Bearish Pin Bar Pattern
class="type">bool IsBearishPinBar(class="type">int i)
{
   class="type">class="kw">double body = MathAbs(openBuffer[i] - closeBuffer[i]);
   class="type">class="kw">double rng   = highBuffer[i] - lowBuffer[i];
   class="type">class="kw">double uw   = highBuffer[i] - MathMax(openBuffer[i], closeBuffer[i]);
   class="type">class="kw">double lw   = MathMin(openBuffer[i], closeBuffer[i]) - lowBuffer[i];
   class="kw">return closeBuffer[i] < openBuffer[i]
          && uw > class="num">2.0 * body
          && lw < class="num">0.5 * body
          && body > class="num">0.1 * rng;
}
class=class="str">"cmt">// Bullish Engulfing Pattern
class="type">bool IsBullishEngulfing(class="type">int i)
{
   if(closeBuffer[i] <= openBuffer[i])
      class="kw">return class="kw">false;
   if(openBuffer[i] > closeBuffer[i+class="num">1])
      class="kw">return class="kw">false;

吞没形态判定与信号可视化落点

看吞没形态不能只看两根 K 线颜色,核心在实体对前一根的包裹关系。下方函数是 Bullish 与 Bearish Engulfing 的判定逻辑,直接在 MT5 自定义指标里复用即可。

MQL5 / C++
if(closeBuffer[i] < openBuffer[i+class="num">1])
      class="kw">return class="kw">false;
   class="kw">return true;
}
class=class="str">"cmt">// Bearish Engulfing Pattern
class="type">bool IsBearishEngulfing(class="type">int i)
{
   if(closeBuffer[i] >= openBuffer[i])
      class="kw">return class="kw">false;
   if(openBuffer[i] < closeBuffer[i+class="num">1])
      class="kw">return class="kw">false;
   if(closeBuffer[i] > openBuffer[i+class="num">1])
      class="kw">return class="kw">false;
   class="kw">return true;
}
逐行拆解:Bullish 判定里 closeBuffer[i] < openBuffer[i+1] 表示当前收低于前一根开,不成立才返回 true,即当前为阳线且吞没前阴。Bearish 函数三重过滤——当前非阴线直接否;当前开不低于前收(没形成向下跳空吞没)否;当前收不高于前开(没包住前实体)否;全过才是有效看跌吞没。 DrawSignal 负责把信号画出来:箭头 code 233/234 分别对应多空,entry 画 aqua 横线、stopL 画橙红虚线,再用未来 5~14 根内更低的低点(多)或更高的高点(空)拉一条趋势线做背离确认。外汇与贵金属杠杆高,信号仅提高概率,实盘须自设止损。 开 MT5 把这两段塞进你的指标 OnCalculate,用 EURUSD 15M 回看最近 300 根,统计吞没后 10 根内反向运行≥20 点的比例,大概率在 55%~65% 区间,可作为调参基线。

MQL5 / C++
if(closeBuffer[i] < openBuffer[i+class="num">1])
      class="kw">return class="kw">false;
   class="kw">return true;
}
class=class="str">"cmt">// Bearish Engulfing Pattern
class="type">bool IsBearishEngulfing(class="type">int i)
{
   if(closeBuffer[i] >= openBuffer[i])
      class="kw">return class="kw">false;
   if(openBuffer[i] < closeBuffer[i+class="num">1])
      class="kw">return class="kw">false;
   if(closeBuffer[i] > openBuffer[i+class="num">1])
      class="kw">return class="kw">false;
   class="kw">return true;
}

「把形态信号推送到多端」

在画出橙色趋势线标记结构后,EA 会根据多空分支构造告警文本。pattern 变量先判断当前是看涨吞没、看涨 PinBar,还是对应的空头形态,side 则直接给出 Buy / Sell 倾向,整套逻辑依赖前面 IsBullishEngulfing / IsBearishEngulfing 的布尔返回。 StringFormat 把形态名、方向、品种、K线时间、入场与止损价拼成一段多行文本,其中 entry 与 stopL 为前面计算出的浮点值,外汇品种默认保留 5 位小数(如 1.08452)。这一步是离屏提醒的关键,交易者不用盯盘也能拿到精确价位。 真正发出通知时,Alert 弹窗必然触发;若 EnableSound 为真则播放 alert.wav,EnablePush 走 SendNotification 推手机,EnableEmail 发邮件,最后 Print 进日志。外汇与贵金属波动剧烈、杠杆风险高,这类信号仅提示概率倾向,不代表必然反转。 下方输入参数决定了策略边界:RSI 周期 14、超买 70 / 超卖 30,止损止盈都默认 20 点。开 MT5 把 SL_Pips 调到 30 以上,可能更适配黄金的日内毛刺,但需自行回测验证。

MQL5 / C++
ObjectCreate(class="num">0, nameL, OBJ_TREND, class="num">0,
                 timeBuffer[j], highBuffer[j],
                 timeBuffer[i], highBuffer[i]);
ObjectSetInteger(class="num">0, nameL, OBJPROP_COLOR, clrOrange);
class="kw">break;
}
class=class="str">"cmt">// Construct alert message
class="type">class="kw">string pattern = isBullish
                 ? (IsBullishEngulfing(i) ? "Engulfing" : "PinBar")
                 : (IsBearishEngulfing(i) ? "Engulfing" : "PinBar");
class="type">class="kw">string side = isBullish ? "Buy" : "Sell";
class="type">class="kw">string txt = StringFormat(
                 "%s + RSI Divergence %s Signal\nSymbol: %s\nTime: %s\nEntry: %.5f\nSL: %.5f",
                 pattern, side, _Symbol,
                 TimeToString(timeBuffer[i], TIME_MINUTES),
                 entry, stopL
                 );
class=class="str">"cmt">// Notify trader
Alert(txt);
if(EnableSound)
   PlaySound("alert.wav");
if(EnablePush)
   SendNotification(txt);
if(EnableEmail)
   SendMail("Signal EA Alert", txt);
Print(txt);

class="macro">#class="kw">property copyright "Copyright class="num">2025, MetaQuotes Ltd."
class="macro">#class="kw">property link     "[MQL5官方文档]
class="macro">#class="kw">property version  "class="num">1.0"
class="macro">#class="kw">property strict
class="macro">#include <Trade/Trade.mqh>
input class="type">int     RSI_Period     = class="num">14;
input class="type">class="kw">double  RSI_Overbought = class="num">70.0;
input class="type">class="kw">double  RSI_Oversold   = class="num">30.0;
input class="type">class="kw">double  SL_Pips        = class="num">20.0;  class=class="str">"cmt">// in pips
input class="type">class="kw">double  TP_Pips        = class="num">20.0;  class=class="str">"cmt">// in pips

◍ 信号触发后的挂单与缓冲管理

信号柱识别完后,真正的动作在 OnTick 收尾段:以上一根信号柱高低点加减 5 点缓冲(EntryBuffer 默认 5.0 点)挂突破单。多头取 highBuffer[1] + EntryBuffer*_Point,空头取 lowBuffer[1] - EntryBuffer*_Point,这个缓冲是为了过滤毛刺,外汇与贵金属点差波动大,直接穿价可能频繁假触发。 止损放在信号柱另一侧:isBullish 时 stopL 取 lowBuffer[idx],空头反之取 highBuffer[idx],距离入场通常落在 10~30 点区间(取决于该柱振幅)。MT5 里把 EntryBuffer 调到 2~3 点,回测欧美 M15 会看到触发次数上升但止损率也同步走高,属于典型参数权衡。 通知开关分开控:EnableSound / EnablePush 默认开,EnableEmail 默认关。实盘跑黄金时建议至少开 Push,否则跳空行情里挂单成交了你还在盯别的品种。 lastBarTime 这个变量是关键节流阀——只在每根新柱第一次 OnTick 时跑信号逻辑,避免同根柱内重复下单。复制这段代码到 MT5 策略测试器,用 2023 年 XAUUSD 数据跑一遍,重点看 lastBarTime 过滤后订单数是否减少 60% 以上。

MQL5 / C++
input class="type">class="kw">double EntryBuffer    = class="num">5.0;   class=class="str">"cmt">// in points
input class="type">bool   EnableSound     = true;
input class="type">bool   EnablePush      = true;
input class="type">bool   EnableEmail     = class="kw">false;
CTrade  trade;
class=class="str">"cmt">// internal buffers
class="type">class="kw">double   rsiBuffer[];
class="type">class="kw">datetime timeBuffer[];
class="type">class="kw">double   openBuffer[], highBuffer[], lowBuffer[], closeBuffer[];
class="type">int      rsiHandle;
class="type">class="kw">datetime lastBarTime = class="num">0;
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">int OnInit()
  {
   rsiHandle = iRSI(_Symbol, _Period, RSI_Period, PRICE_CLOSE);
   if(rsiHandle == INVALID_HANDLE)
      class="kw">return INIT_FAILED;
   ArraySetAsSeries(rsiBuffer,   true);
   ArraySetAsSeries(timeBuffer,   true);
   ArraySetAsSeries(openBuffer,   true);
   ArraySetAsSeries(highBuffer,   true);
   ArraySetAsSeries(lowBuffer,    true);
   ArraySetAsSeries(closeBuffer, true);
   class="kw">return INIT_SUCCEEDED;
   }
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">void OnTick()
  {
   if(Bars(_Symbol,_Period) < class="num">20)
      class="kw">return;
   if(CopyTime(_Symbol,_Period,class="num">0,class="num">20,timeBuffer)   <= class="num">0 ||
      CopyOpen(_Symbol,_Period,class="num">0,class="num">20,openBuffer)   <= class="num">0 ||
      CopyHigh(_Symbol,_Period,class="num">0,class="num">20,highBuffer)   <= class="num">0 ||
      CopyLow(_Symbol,_Period,class="num">0,class="num">20,lowBuffer)     <= class="num">0 ||
      CopyClose(_Symbol,_Period,class="num">0,class="num">20,closeBuffer) <= class="num">0)
      class="kw">return;
   if(timeBuffer[class="num">1] == lastBarTime)
      class="kw">return;
   lastBarTime = timeBuffer[class="num">1];
   if(CopyBuffer(rsiHandle,class="num">0,class="num">0,class="num">20,rsiBuffer) <= class="num">0)
      class="kw">return;
   class="type">int dir = FindSignalBar();
   if(dir == class="num">0)
      class="kw">return;
   class="type">int idx = class="num">1;
   class="type">bool isBullish = (dir > class="num">0);
   class="type">class="kw">double entry = isBullish
                  ? highBuffer[idx] + EntryBuffer * _Point
                  : lowBuffer[idx]  - EntryBuffer * _Point;
   class="type">class="kw">double stopL = isBullish

常见问题

看背离段对应的 RSI 斜率与针形线影线比例,影线大于实体 2 倍且 RSI 未同步新低,假突破概率偏低;建议先在历史品种页回看 30 根以上 K 线验证。
把可视化落点放在吞没第二根收盘价的上下 2 个点差位置,用不同颜色区分多空,避免和均线重叠导致漏看。
可以,小布盯盘的 AIGC 已内置针形线、吞没与背离多模式识别,打开对应品种页即可接收多端信号提醒,不用自己搭推送。
贵金属和高波动外汇建议缓冲 ≥ 15 点差,普通货币对 ≥ 8 点差;具体看品种日均波幅,波幅大的适当翻倍。
大概率是在 K 线事件里重复调用全量历史数组,改成只增量读取新根 OHLC 与最新 RSI 值,CPU 占用会明显下降。