价格行为分析工具包开发(第十三部分):RSI 哨兵工具(基础篇)
📘

价格行为分析工具包开发(第十三部分):RSI 哨兵工具(基础篇)

第 1/3 篇

RSI 哨兵工具的基本定位

这一节是价格行为分析工具包第十三部分的开篇,主角是一个挂在 MT5 上的 RSI 哨兵工具,作者 Christian Benjamin 在 2025 年 10 月 29 日发布,原文页面显示阅读量 1 023。它不属于完整 EA,而是偏轻量的指标哨兵,用来在 RSI 越过阈值时给出盘面提示。 从系列结构看,本篇后续会依次走「策略概览 → MQL5 代码 → 代码分解 → 测试结果 → 结论」五步。也就是说,这一节只交代工具归属与阅读入口,真正的参数与逻辑要等代码段放出后才能在 MT5 里复现。 对外汇与贵金属交易者而言,这类哨兵只能当辅助预警,RSI 本身在震荡市有效、趋势市易钝化,实盘使用前务必先在策略测试器跑一遍历史数据。

◍ 为什么要把 RSI 背离交给 EA 跑

背离指价格与动量类指标走势出现分叉:价格刷出新高或新低,但 RSI 没有同步确认,这种错位往往意味着当前趋势的推动力量在衰减,反转或动量切换的概率上升。RSI 背离本身只是个朴素的反转预警手段,难点从来不在识别逻辑,而在人工盯盘时漏看和误判的成本。 手动在 K 线图上比对每一段波峰波谷与 RSI 拐点,既慢又容易疲劳出错,尤其多周期多品种同时看时基本不可行。把这套比对规则写成 MQL5 EA,让程序在每根蜡烛收盘后自动扫背离并用箭头标出来,交易者只需要在信号出现后做二次确认。 本文要落地的就是一个自动检测 RSI 背离的 EA:它在图表上画清晰箭头,并附带简短文字摘要,你开 MT5 加载后就能直接看到信号,不必再花几小时翻图。外汇与贵金属波动剧烈、杠杆风险高,信号只作待验证的线索,不代表一定会反转。

「RSI背离的四种形态与EA扫描逻辑」

RSI背离指价格与相对强弱指数走势不一致,往往出现在重大波动前,是动量转弱的提前量。常规背离看反转,隐藏背离看延续,两者都不能单独当入场令,只能视作概率倾斜。 常规看涨背离是价格走更低低点(LL)、RSI走更高低点(HL),暗示下跌动量衰减;常规看跌则是价格更高高点(HH)、RSI更低高点(LH)。隐藏看涨发生在上升趋势里价格HL而RSI的LL,视为回调蓄力;隐藏看跌是下降趋势中价格LH而RSI的HH,确认下行未止。 这套EA不做主观判断,只在定义回溯期内持续比对K线价格与RSI摆动点。它先抓局部摆动高低作锚,再分别匹配四类背离结构,命中后用箭头标签画图并写日志,方便你回测时复盘信号质量。 外汇与贵金属杠杆高,背离失效是常态,任何标记都只是‘可能’反转或延续,实盘前务必在MT5用历史数据跑一遍确认触发频率。

RSI背离指标的输入参数与全局句柄

下面这段 MQL5 头部定义了背离扫描器的可调入口与全局容器,直接决定了 MT5 里信号的灵敏度。默认 RSI 周期 14、左右摆点各看 1 根 K 线、回溯 100 根扫描背离,属于偏松的日内设置。 InpMinSwingDiffPct 设成 0.05、InpMinRSIDiff 设成 1.0,意味着价格摆点只需 5% 幅度差、RSI 两点差 1 个单位就能算作有效摆动,比传统 30/70 阈值过滤更容易出信号,但也更容易在震荡里刷假背离。 全局里 rsiHandle 是指标句柄,rsiBuffer / lowBuffer / highBuffer 三个数组分别接 RSI 值、最低价、最高价,后续 OnCalculate 里靠它们做点线对比。外汇与贵金属杠杆高,这类松参数策略回测前先想清楚滑点成本。 [CODE] 逐行拆解: //+--- 开头注释块仅标注脚本名与版权,无执行意义 #property copyright / link / version / strict 是元数据与强制编译检查 input int InpRSIPeriod=14 // RSI周期,经典值 input int InpSwingLeft=1 // 摆点检测向左看1根 input int InpSwingRight=1 // 向右看1根,松弛判定 input int InpLookback=100 // 扫描背离的总柱数 input int InpEvalBars=5 // 信号出现后隔5根再评估 input int InpMinBarsBetweenSignals=1 // 同类型信号最小间隔1根,允许频繁重入 input double InpArrowOffset=3.0 // 箭头偏移3点显示 input double InpMinSwingDiffPct=0.05 // 摆点最小幅度差5% input double InpMinRSIDiff=1.0 // RSI两点最小差1.0 input bool InpUseRSIThreshold=false // 默认不开启超卖过滤 input double InpRSIOversold=30 // 超卖线 input double InpRSIOverbought=70 // 超买线 int rsiHandle // RSI指标句柄 double rsiBuffer[] // RSI值缓冲 double lowBuffer[] // 低价缓冲 double highBuffer[] // 高价缓冲

MQL5 / C++
<span class="comment">class=class="str">"cmt">//+--------------------------------------------------------------------+</span>
<span class="comment">class=class="str">"cmt">//|&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;RSI Divergence.mql5 |</span>
<span class="comment">class=class="str">"cmt">//|&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; Copyright class="num">2025, Christian Benjamin |</span>
<span class="comment">class=class="str">"cmt">//|&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; [MQL5官方文档] |</span>
<span class="comment">class=class="str">"cmt">//+--------------------------------------------------------------------+</span>
<span class="preprocessor">class="macro">#class="kw">property </span><span class="macro">copyright</span> <span class="class="type">class="kw">string">"class="num">2025, MetaQuotes Software Corp."</span>
<span class="preprocessor">class="macro">#class="kw">property </span><span class="macro">link</span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="class="type">class="kw">string">"[MQL5官方文档]
<span class="preprocessor">class="macro">#class="kw">property </span><span class="macro">version</span>&nbsp;&nbsp; <span class="class="type">class="kw">string">"class="num">1.0"</span>
<span class="preprocessor">class="macro">#class="kw">property </span><span class="macro">strict</span>
<span class="comment">class=class="str">"cmt">//---- Input parameters</span>
<span class="keyword">input</span> <span class="keyword">class="type">int</span>&nbsp;&nbsp;&nbsp;&nbsp;InpRSIPeriod&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; = <span class="number">class="num">14</span>;&nbsp;&nbsp;&nbsp;&nbsp;<span class="comment">class=class="str">"cmt">// RSI period</span>
<span class="keyword">input</span> <span class="keyword">class="type">int</span>&nbsp;&nbsp;&nbsp;&nbsp;InpSwingLeft&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; = <span class="number">class="num">1</span>;&nbsp;&nbsp;&nbsp;&nbsp; <span class="comment">class=class="str">"cmt">// Bars to the left for swing detection(relaxed)</span>
<span class="keyword">input</span> <span class="keyword">class="type">int</span>&nbsp;&nbsp;&nbsp;&nbsp;InpSwingRight&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;= <span class="number">class="num">1</span>;&nbsp;&nbsp;&nbsp;&nbsp; <span class="comment">class=class="str">"cmt">// Bars to the right for swing detection(relaxed)</span>
<span class="keyword">input</span> <span class="keyword">class="type">int</span>&nbsp;&nbsp;&nbsp;&nbsp;InpLookback&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;= <span class="number">class="num">100</span>;&nbsp;&nbsp; <span class="comment">class=class="str">"cmt">// Number of bars to scan for divergence</span>
<span class="keyword">input</span> <span class="keyword">class="type">int</span>&nbsp;&nbsp;&nbsp;&nbsp;InpEvalBars&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;= <span class="number">class="num">5</span>;&nbsp;&nbsp;&nbsp;&nbsp; <span class="comment">class=class="str">"cmt">// Bars after which to evaluate a signal</span>
<span class="keyword">input</span> <span class="keyword">class="type">int</span>&nbsp;&nbsp;&nbsp;&nbsp;InpMinBarsBetweenSignals = <span class="number">class="num">1</span>;&nbsp;&nbsp;&nbsp;&nbsp; <span class="comment">class=class="str">"cmt">// Minimum bars between same-type signals(allows frequent re-entry)</span>
<span class="keyword">input</span> <span class="keyword">class="type">class="kw">double</span> InpArrowOffset&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; = <span class="number">class="num">3.0</span>;&nbsp;&nbsp; <span class="comment">class=class="str">"cmt">// Arrow offset(in points) for display</span>
<span class="keyword">input</span> <span class="keyword">class="type">class="kw">double</span> InpMinSwingDiffPct&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; = <span class="number">class="num">0.05</span>;&nbsp;&nbsp;<span class="comment">class=class="str">"cmt">// Lower minimum % difference to qualify as a swing</span>
<span class="keyword">input</span> <span class="keyword">class="type">class="kw">double</span> InpMinRSIDiff&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;= <span class="number">class="num">1.0</span>;&nbsp;&nbsp; <span class="comment">class=class="str">"cmt">// Lower minimum difference in RSI between swing points</span>
<span class="comment">class=class="str">"cmt">// Optional RSI threshold filter for bullish divergence(disabled by class="kw">default)</span>
<span class="keyword">input</span> <span class="keyword">class="type">bool</span>&nbsp;&nbsp; InpUseRSIThreshold&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; = <span class="macro">class="kw">false</span>; <span class="comment">class=class="str">"cmt">// If true, require earlier RSI swing to be oversold for bullish divergence</span>
<span class="keyword">input</span> <span class="keyword">class="type">class="kw">double</span> InpRSIOversold&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; = <span class="number">class="num">30</span>;&nbsp;&nbsp;&nbsp;&nbsp;<span class="comment">class=class="str">"cmt">// RSI oversold level</span>
<span class="keyword">input</span> <span class="keyword">class="type">class="kw">double</span> InpRSIOverbought&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; = <span class="number">class="num">70</span>;&nbsp;&nbsp;&nbsp;&nbsp;<span class="comment">class=class="str">"cmt">// RSI overbought level(if needed for bearish)</span>
<span class="comment">class=class="str">"cmt">//---- Global variables</span>
<span class="keyword">class="type">int</span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;rsiHandle;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <span class="comment">class=class="str">"cmt">// Handle for the RSI indicator</span>
<span class="keyword">class="type">class="kw">double</span>&nbsp;&nbsp; rsiBuffer[];&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <span class="comment">class=class="str">"cmt">// Buffer for RSI values</span>
<span class="keyword">class="type">class="kw">double</span>&nbsp;&nbsp; lowBuffer[];&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <span class="comment">class=class="str">"cmt">// Buffer for low prices</span>
<span class="keyword">class="type">class="kw">double</span>&nbsp;&nbsp; highBuffer[];&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="comment">class=class="str">"cmt">// Buffer for high prices</span>

◍ RSI背离信号的缓冲与初始化骨架

做背离检测的第一件事,是在全局层把价格、时间与信号分开存。下面这段代码用 closeBuffer 和 timeBuffer 承接收盘价与K线时间,g_totalBars 记录已拷贝根数,lastBarTime 锁住上一根已收线时间,避免每跳 tick 都重算。 SignalInfo 结构体是整套逻辑的核心容器:type 区分「RegBearish Divergence」或「HiddenBullish Divergence」等类型,barIndex 与 signalTime 定位信号K线,signalPrice 存对应的摆动极点(看空取.swing high,看多取 swing low)。全局 signals[] 数组让你在回测结束后仍能逐条打印复盘。 OnInit 里只干一件实事——用 iRSI(_Symbol, _Period, InpRSIPeriod, PRICE_CLOSE) 建句柄,失败就返回 INIT_FAILED,EA 根本不会跑。OnDeinit 释放句柄前先调 EvaluateSignalsAndPrint(),保证退出时信号不丢。 OnTick 用 iTime(_Symbol, _Period, 1) 取倒数第二根时间,与 lastBarTime 相等就直接 return,相当于「一根K线只处理一次」。随后 ArrayResize 配 ArraySetAsSeries(true),把 rsiBuffer 按时间序列排好,CopyBuffer 拉取 InpLookback 长度数据;价格侧 lowBuffer / highBuffer 同理扩容,为后续摆动识别备好原料。外汇与贵金属杠杆高,这套结构仅用于信号研究,实盘前请在 MT5 策略测试器用历史数据验证。

MQL5 / C++
class="type">class="kw">double   closeBuffer[];      class=class="str">"cmt">// Buffer for close prices
class="type">class="kw">datetime timeBuffer[];          class=class="str">"cmt">// Buffer for bar times
class="type">int      g_totalBars = class="num">0;       class=class="str">"cmt">// Number of bars in our copied arrays
class="type">class="kw">datetime lastBarTime = class="num">0;       class=class="str">"cmt">// Time of last closed bar
class=class="str">"cmt">//---- Structure to hold signal information
class="kw">struct SignalInfo
  {
   class="type">class="kw">string            type;         class=class="str">"cmt">// e.g. "RegBearish Divergence", "HiddenBullish Divergence"
   class="type">int               barIndex;     class=class="str">"cmt">// Bar index where the signal was generated
   class="type">class="kw">datetime           signalTime;   class=class="str">"cmt">// Time of the signal bar
   class="type">class="kw">double            signalPrice;  class=class="str">"cmt">// Price used for the signal(swing high for bearish, swing low for bullish)
  };
SignalInfo signals[];   class=class="str">"cmt">// Global array to store signals
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Expert initialization function                                     |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">int OnInit()
  {
   rsiHandle = iRSI(_Symbol, _Period, InpRSIPeriod, PRICE_CLOSE);
   if(rsiHandle == INVALID_HANDLE)
     {
      Print("Error creating RSI handle");
      class="kw">return(INIT_FAILED);
     }
   class="kw">return(INIT_SUCCEEDED);
  }
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Expert deinitialization function                                   |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">void OnDeinit(class="kw">const class="type">int reason)
  {
   if(rsiHandle != INVALID_HANDLE)
      IndicatorRelease(rsiHandle);
   EvaluateSignalsAndPrint();
  }
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Expert tick function                                               |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">void OnTick()
  {
class=class="str">"cmt">// Process once per new closed candle(class="kw">using bar1&class="macro">#x27;s time)
   class="type">class="kw">datetime currentBarTime = iTime(_Symbol, _Period, class="num">1);
   if(currentBarTime == lastBarTime)
      class="kw">return;
   lastBarTime = currentBarTime;
class=class="str">"cmt">//--- Copy RSI data
   ArrayResize(rsiBuffer, InpLookback);
   ArraySetAsSeries(rsiBuffer, true);
   if(CopyBuffer(rsiHandle, class="num">0, class="num">0, InpLookback, rsiBuffer) <= class="num">0)
     {
      Print("Error copying RSI data");
      class="kw">return;
     }
class=class="str">"cmt">//--- Copy price and time data
   ArrayResize(lowBuffer, InpLookback);
   ArrayResize(highBuffer, InpLookback);

「把摆点塞进数组再抓背离」

初始化阶段先给 closeBuffer 按 InpLookback 扩容,三个价格序列 lowBuffer、highBuffer、closeBuffer 全部用 ArraySetAsSeries 设成时间序列降序,这样下标 0 就是当前柱。CopyLow / CopyHigh / CopyClose 任一个返回 ≤0 就打印错误并 return,避免后续空数组越界。 timeBuffer 同样扩容并设降序,CopyTime 失败也直接退出;g_totalBars 记成 InpLookback 供后面循环用。接着声明 swingLows、swingHighs 两个动态数组,循环从 InpSwingLeft 到 g_totalBars - InpSwingRight,用 IsSignificantSwingLow / IsSignificantSwingHigh 判定显著摆点并追加下标。 熊背离只在大摆点 ≥2 时处理:ArraySort 后下标 0 是最新摆点、1 是前一个。常规熊背离要求价更高高、RSI 更低高且差值 ≥ InpMinRSIDiff;隐藏熊背离则价更低高、RSI 更高高且差值达标。开 MT5 把 InpMinRSIDiff 设 2~5 之间跑一遍,能直接看到 Print 出的检测日志。 外汇与贵金属杠杆高,背离只是概率倾向信号,实盘前务必在策略测试器用历史数据验证触发频率与假信号比例。

MQL5 / C++
  ArrayResize(closeBuffer, InpLookback);
  ArraySetAsSeries(lowBuffer, true);
  ArraySetAsSeries(highBuffer, true);
  ArraySetAsSeries(closeBuffer, true);
  if(CopyLow(_Symbol, _Period, class="num">0, InpLookback, lowBuffer) <= class="num">0 ||
     CopyHigh(_Symbol, _Period, class="num">0, InpLookback, highBuffer) <= class="num">0 ||
     CopyClose(_Symbol, _Period, class="num">0, InpLookback, closeBuffer) <= class="num">0)
    {
      Print("Error copying price data");
      class="kw">return;
    }
  ArrayResize(timeBuffer, InpLookback);
  ArraySetAsSeries(timeBuffer, true);
  if(CopyTime(_Symbol, _Period, class="num">0, InpLookback, timeBuffer) <= class="num">0)
    {
      Print("Error copying time data");
      class="kw">return;
    }
  g_totalBars = InpLookback;
class=class="str">"cmt">//--- Identify swing lows and swing highs
  class="type">int swingLows[];
  class="type">int swingHighs[];
  class="type">int startIndex = InpSwingLeft;
  class="type">int endIndex   = g_totalBars - InpSwingRight;
  for(class="type">int i = startIndex; i < endIndex; i++)
    {
      if(IsSignificantSwingLow(i, InpSwingLeft, InpSwingRight))
        {
         ArrayResize(swingLows, ArraySize(swingLows) + class="num">1);
         swingLows[ArraySize(swingLows) - class="num">1] = i;
        }
      if(IsSignificantSwingHigh(i, InpSwingLeft, InpSwingRight))
        {
         ArrayResize(swingHighs, ArraySize(swingHighs) + class="num">1);
         swingHighs[ArraySize(swingHighs) - class="num">1] = i;
        }
    }
class=class="str">"cmt">//--- Bearish Divergence(class="kw">using swing highs)
  if(ArraySize(swingHighs) >= class="num">2)
    {
      ArraySort(swingHighs); class=class="str">"cmt">// ascending order: index class="num">0 is most recent
      class="type">int recent   = swingHighs[class="num">0];
      class="type">int previous = swingHighs[class="num">1];
      class=class="str">"cmt">// Regular Bearish Divergence: Price makes a higher high class="kw">while RSI makes a lower high
      if(highBuffer[recent] > highBuffer[previous] &&
         rsiBuffer[recent] < rsiBuffer[previous] &&
         (rsiBuffer[previous] - rsiBuffer[recent]) >= InpMinRSIDiff)
        {
         Print("Regular Bearish Divergence detected at bar ", recent);
         DisplaySignal("RegBearish Divergence", recent);
        }
      class=class="str">"cmt">// Hidden Bearish Divergence: Price makes a lower high class="kw">while RSI makes a higher high
      else
       if(highBuffer[recent] < highBuffer[previous] &&
          rsiBuffer[recent] > rsiBuffer[previous] &&
          (rsiBuffer[recent] - rsiBuffer[previous]) >= InpMinRSIDiff)
         {

常见问题

把RSI背离扫描逻辑写成EA,用全局句柄循环读取各品种RSI缓冲,自动比对摆点数组抓背离,省去肉眼盯盘。
常用参数含RSI周期(如14)、价格摆点最少根数(如5)、背离确认根数;具体按品种波动调,贵金属和高风险外汇需谨慎。
小布盯盘的AIGC已内置RSI背离诊断,打开对应品种页即可看到自动标注的背离概率与形态。
四类为价格新低RSI抬高、价格新高RSI走低及对应看涨看跌变体;EA把摆点存入数组后比前后极值与RSI值方向判定。
缓冲存信号避免重绘,初始化骨架建全局句柄与数组,保证EA每次tick只更新增量,降低漏扫概率。