价格行为分析工具包开发(第十三部分):RSI 哨兵工具·综合运用
🛡️

价格行为分析工具包开发(第十三部分):RSI 哨兵工具·综合运用

(3/3)·手动找 RSI 背离又慢又漏?这篇把检测、标记与摘要一次性写进 EA 收尾

实战向进阶 第 3/3 篇
很多交易者盯着副图 RSI 肉眼比对高低点,结果不是漏掉隐藏背离,就是把噪声当反转信号。手动扫描在多线程品种下几乎必然出错,且无法复盘统计。把这套活交给代码,你只做最后确认。

「RSI背离检测的参数与数据抓取骨架」

做价格行为结合动能的背离扫描,第一道关是先定好摆动识别的宽松度。把摆幅最小百分比差设到 0.05,意味着价格只要相对前段有 5 个基点的反向位移就可能被记为摆动点;RSI 两点差阈值压到 1.0,是为了在震荡市里别漏掉弱背离。 默认不开启超卖过滤(InpUseRSIThreshold=false),也就是牛背离不强制要求前一段 RSI 进 30 以下。真要严判底部背离,再把开关打开,配合 30/70 这两条线用。 OnInit 里用 iRSI 拿句柄,周期取当前图表 _Period、收盘价计算;句柄无效直接 INIT_FAILED,这步不过后面全白跑。 OnTick 用 iTime 比对 1 号 bar 时间,相同就 return,保证每根收完的 K 线只算一次。随后把 RSI、高低收、时间四类数据按 InpLookback 长度拷进数组,ArraySetAsSeries 设成 true 才能用 [0] 代表最新 bar。任何 Copy 返回 ≤0 都打印错误并退出,避免脏数据进分析。

MQL5 / C++
input class="type">class="kw">double InpMinSwingDiffPct      = class="num">0.05;   class=class="str">"cmt">// Lower minimum % difference to qualify as a swing
input class="type">class="kw">double InpMinRSIDiff            = class="num">1.0;    class=class="str">"cmt">// Lower minimum difference in RSI between swing points
class=class="str">"cmt">// Optional RSI threshold filter for bullish divergence(disabled by class="kw">default)
input class="type">bool   InpUseRSIThreshold      = class="kw">false; class=class="str">"cmt">// If true, require earlier RSI swing to be oversold for bullish divergence
input class="type">class="kw">double InpRSIOversold          = class="num">30;     class=class="str">"cmt">// RSI oversold level
input class="type">class="kw">double InpRSIOverbought        = class="num">70;     class=class="str">"cmt">// RSI overbought level(if needed for bearish)
class="type">int OnInit()
{
   class=class="str">"cmt">// Create the RSI indicator handle with the specified period
   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="type">void OnTick()
{
   class=class="str">"cmt">// Process only once per new closed candle by comparing bar times
   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 for a given lookback period
   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 data(lows, highs, closes) and time data for analysis
   ArrayResize(lowBuffer, InpLookback);
   ArrayResize(highBuffer, InpLookback);
   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;
   }

摆动点判定与背离信号的分水岭

摆点显著性靠的是左右邻域的相对幅度门槛,而不是单纯看高低。IsSignificantSwingLow 以当前低点为中心,向左 left 根、向右 right 根扫描,只要任一侧出现更低且偏离超过 InpMinSwingDiffPct(百分比)的低点,就否定当前摆点。 高低点两套函数逻辑对称:IsSignificantSwingHigh 把比较方向反过来,用 highBuffer 找局部最大。注意左右扫描都做了边界保护——左侧 i<0 跳过,右侧 i>=g_totalBars 直接 break,避免越界读缓冲。 背离部分只取排序后最近的两个摆动高点。常规看跌背离要求价更高、RSI 更低且差值 >= InpMinRSIDiff;隐藏看跌背离则价更低、RSI 更高。外汇与贵金属波动剧烈,这类信号只代表反转概率倾向,实盘须自担高风险。 把 InpMinSwingDiffPct 设到 0.3~0.5 之间,在 XAUUSD 的 M15 上能过滤掉大量毛刺摆点;设太小会把噪音当结构。

MQL5 / C++
g_totalBars = InpLookback;

class=class="str">"cmt">// (Further processing follows here...)
class="type">bool IsSignificantSwingLow(class="type">int index, class="type">int left, class="type">int right)
{
  class="type">class="kw">double currentLow = lowBuffer[index];
  class=class="str">"cmt">// Check left side for local minimum condition
  for(class="type">int i = index - left; i < index; i++)
  {
    if(i < class="num">0) class="kw">continue;
    class="type">class="kw">double pctDiff = MathAbs((lowBuffer[i] - currentLow) / currentLow) * class="num">100.0;
    if(lowBuffer[i] < currentLow && pctDiff > InpMinSwingDiffPct)
      class="kw">return class="kw">false;
  }
  class=class="str">"cmt">// Check right side for local minimum condition
  for(class="type">int i = index + class="num">1; i <= index + right; i++)
  {
    if(i >= g_totalBars) class="kw">break;
    class="type">class="kw">double pctDiff = MathAbs((lowBuffer[i] - currentLow) / currentLow) * class="num">100.0;
    if(lowBuffer[i] < currentLow && pctDiff > InpMinSwingDiffPct)
      class="kw">return class="kw">false;
  }
  class="kw">return true;
}
class="type">bool IsSignificantSwingHigh(class="type">int index, class="type">int left, class="type">int right)
{
  class="type">class="kw">double currentHigh = highBuffer[index];
  class=class="str">"cmt">// Check left side for local maximum condition
  for(class="type">int i = index - left; i < index; i++)
  {
    if(i < class="num">0) class="kw">continue;
    class="type">class="kw">double pctDiff = MathAbs((currentHigh - highBuffer[i]) / currentHigh) * class="num">100.0;
    if(highBuffer[i] > currentHigh && pctDiff > InpMinSwingDiffPct)
      class="kw">return class="kw">false;
  }
  class=class="str">"cmt">// Check right side for local maximum condition
  for(class="type">int i = index + class="num">1; i <= index + right; i++)
  {
    if(i >= g_totalBars) class="kw">break;
    class="type">class="kw">double pctDiff = MathAbs((currentHigh - highBuffer[i]) / currentHigh) * class="num">100.0;
    if(highBuffer[i] > currentHigh && pctDiff > InpMinSwingDiffPct)
      class="kw">return class="kw">false;
  }
  class="kw">return true;
}
class=class="str">"cmt">// --- Bearish Divergence(class="kw">using swing highs)
if(ArraySize(swingHighs) >= class="num">2)
{
  ArraySort(swingHighs); class=class="str">"cmt">// Ensure 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)
  {

◍ 牛背离与信号去重的代码骨架

在摆荡低点数组 swingLows 中,先取最近两个低点做比较。常规牛背离要求价格走出更低的低点,而 RSI 反而抬高,且两者差值不小于 InpMinRSIDiff;隐藏牛背离则相反,价格高点抬升、RSI 走低。 代码里对常规信号额外加了一道过滤:若开启 InpUseRSIThreshold,则前一次 RSI 摆动必须处于 InpRSIOversold 超卖线以下才放行。这一步能砍掉一批弱势反弹中的假信号。 DisplaySignal 函数负责抑制刷屏。它遍历已记录信号,若同类型信号与当前 bar 距离小于 InpMinBarsBetweenSignals,直接 return 退出,避免相邻 K 线重复报警。 对于含「Reg」字样的常规信号,脚本会在图表左上角(距边 10/20 像素)创建或更新一个名为 LatestSignal 的 OBJ_LABEL 文本,颜色白。你可在 MT5 里改 clrWhite 或坐标值,把提示放到不挡 K 线的位置。外汇与贵金属波动剧烈,背离仅代表概率倾斜,实盘须自担高风险。

MQL5 / C++
if(ArraySize(swingLows) >= class="num">2)
{
   ArraySort(swingLows); class=class="str">"cmt">// Ensure ascending order: index class="num">0 is most recent
   class="type">int recent   = swingLows[class="num">0];
   class="type">int previous = swingLows[class="num">1];
   
   class=class="str">"cmt">// Regular Bullish Divergence: Price makes a lower low class="kw">while RSI makes a higher low
   if(lowBuffer[recent] < lowBuffer[previous] &&
       rsiBuffer[recent] > rsiBuffer[previous] &&
       (rsiBuffer[recent] - rsiBuffer[previous]) >= InpMinRSIDiff)
   {
       class=class="str">"cmt">// Optionally require the earlier RSI swing to be oversold
       if(!InpUseRSIThreshold || rsiBuffer[previous] <= InpRSIOversold)
       {
           Print("Regular Bullish Divergence detected at bar ", recent);
           DisplaySignal("RegBullish Divergence", recent);
       }
   }
   class=class="str">"cmt">// Hidden Bullish Divergence: Price makes a higher low class="kw">while RSI makes a lower low
   else if(lowBuffer[recent] > lowBuffer[previous] &&
             rsiBuffer[recent] < rsiBuffer[previous] &&
             (rsiBuffer[previous] - rsiBuffer[recent]) >= InpMinRSIDiff)
   {
       Print("Hidden Bullish Divergence detected at bar ", recent);
       DisplaySignal("HiddenBullishDivergence", recent);
   }
}
class="type">void DisplaySignal(class="type">class="kw">string signalText, class="type">int barIndex)
{
   class=class="str">"cmt">// Prevent duplicate signals on the same or nearby bars
   for(class="type">int i = class="num">0; i < ArraySize(signals); i++)
   {
       if(StringFind(signals[i].type, signalText) != -class="num">1)
           if(MathAbs(signals[i].barIndex - barIndex) < InpMinBarsBetweenSignals)
               class="kw">return;
   }
       
   class=class="str">"cmt">// Update a label for the latest regular signal
   if(StringFind(signalText, "Reg") != -class="num">1)
   {
       class="type">class="kw">string labelName = "LatestSignal";
       if(ObjectFind(class="num">0, labelName) == -class="num">1)
       {
           if(!ObjectCreate(class="num">0, labelName, OBJ_LABEL, class="num">0, class="num">0, class="num">0))
           {
               Print("Failed to create LatestSignal label");
               class="kw">return;
           }
           ObjectSetInteger(class="num">0, labelName, OBJPROP_CORNER, class="num">0);
           ObjectSetInteger(class="num">0, labelName, OBJPROP_XDISTANCE, class="num">10);
           ObjectSetInteger(class="num">0, labelName, OBJPROP_YDISTANCE, class="num">20);
           ObjectSetInteger(class="num">0, labelName, OBJPROP_COLOR, clrWhite);
       }

「箭头标记与信号回测落库」

给图表打信号不能只写文字标签,箭头对象才是肉眼盯盘时的第一锚点。下面这段逻辑在发现 Bullish 字样的信号时,用 Wingdings 编码 233 画向上青绿箭头,位置落在对应 K 线最低价减去 InpArrowOffset 个 point;Bearish 则取编码 234 向下红箭头,挂在最高价加偏移处。

MQL5 / C++
class=class="str">"cmt">// 先给标签写信号文字
ObjectSetString(class="num">0, labelName, OBJPROP_TEXT, signalText);
}

class=class="str">"cmt">// 创建箭头对象在图表上标记信号
class="type">class="kw">string arrowName = "Arrow_" + signalText + "_" + IntegerToString(barIndex);
if(ObjectFind(class="num">0, arrowName) < class="num">0)
{
  class="type">int arrowCode = class="num">0;
  class="type">class="kw">double arrowPrice = class="num">0.0;
  class="type">class="kw">color arrowColor = clrWhite;
  class="type">class="kw">double point = SymbolInfoDouble(_Symbol, SYMBOL_POINT);
  
  if(StringFind(signalText, "Bullish") != -class="num">1)
  {
    arrowCode  = class="num">233; class=class="str">"cmt">// Wingdings 上箭头
    arrowColor = clrLime;
    arrowPrice = lowBuffer[barIndex] - (InpArrowOffset * point);
  }
  else if(StringFind(signalText, "Bearish") != -class="num">1)
  {
    arrowCode  = class="num">234; class=class="str">"cmt">// Wingdings 下箭头
    arrowColor = clrRed;
    arrowPrice = highBuffer[barIndex] + (InpArrowOffset * point);
  }
  
  if(!ObjectCreate(class="num">0, arrowName, OBJ_ARROW, class="num">0, timeBuffer[barIndex], arrowPrice))
  {
    Print("Failed to create arrow object ", arrowName);
    class="kw">return;
  }
  ObjectSetInteger(class="num">0, arrowName, OBJPROP_COLOR, arrowColor);
  ObjectSetInteger(class="num">0, arrowName, OBJPROP_ARROWCODE, arrowCode);
}

class=class="str">"cmt">// 记录信号明细供后续回测
SignalInfo sig;
sig.type = signalText;
sig.barIndex = barIndex;
sig.signalTime = timeBuffer[barIndex];
if(StringFind(signalText, "Bullish") != -class="num">1)
  sig.signalPrice = lowBuffer[barIndex];
else
  sig.signalPrice = highBuffer[barIndex];
ArrayResize(signals, ArraySize(signals) + class="num">1);
signals[ArraySize(signals) - class="num">1] = sig;

UpdateSignalCountLabel();
}
class="type">void EvaluateSignalsAndPrint()
{
  class="type">class="kw">double closeAll[];
  class="type">int totalBars = CopyClose(_Symbol, _Period, class="num">0, WHOLE_ARRAY, closeAll);
  if(totalBars <= class="num">0)
  {
    Print("Error copying complete close data for evaluation");
    class="kw">return;
  }
  ArraySetAsSeries(closeAll, true);

  class="type">int totalEvaluated = class="num">0, regTotal = class="num">0, hidTotal = class="num">0;
  class="type">int regEval = class="num">0, hidEval = class="num">0;
  class="type">int regCorrect = class="num">0, hidCorrect = class="num">0;
逐行拆解:arrowName 用信号文本加 Bar 索引拼成,避免同品种重复信号互相覆盖;ObjectFind 判断已存在就跳过,防止每帧重画闪屏。point 通过 SymbolInfoDouble 取实时点值,外汇和贵金属点差跳动大,硬编码偏移会在切换品种时错位。 信号落库那一段把 type、barIndex、signalTime 和触发价写进 SignalInfo 结构,再 ArrayResize 追加。注意 Bullish 记低价、Bearish 记高价,回测时以此作为入场参考基准,后续 EvaluateSignalsAndPrint 拉全量收盘价(WHOLE_ARRAY)并按时间倒序排,才能算胜率。外汇贵金属波动剧烈,这类统计只反映历史概率,实盘仍可能连续失效。 开 MT5 把 InpArrowOffset 从默认 2 改成 5,箭头会离影线更远,密集信号区可读性更好;改完编译挂 EURUSD 十五分钟图看是否有重叠遮挡。

MQL5 / C++
ObjectSetString(class="num">0, labelName, OBJPROP_TEXT, signalText);
}

class=class="str">"cmt">// Create an arrow object to mark the signal on the chart
class="type">class="kw">string arrowName = "Arrow_" + signalText + "_" + IntegerToString(barIndex);
if(ObjectFind(class="num">0, arrowName) < class="num">0)
{
  class="type">int arrowCode = class="num">0;
  class="type">class="kw">double arrowPrice = class="num">0.0;
  class="type">class="kw">color arrowColor = clrWhite;
  class="type">class="kw">double point = SymbolInfoDouble(_Symbol, SYMBOL_POINT);
  
  if(StringFind(signalText, "Bullish") != -class="num">1)
  {
    arrowCode  = class="num">233; class=class="str">"cmt">// Wingdings up arrow
    arrowColor = clrLime;
    arrowPrice = lowBuffer[barIndex] - (InpArrowOffset * point);
  }
  else if(StringFind(signalText, "Bearish") != -class="num">1)
  {
    arrowCode  = class="num">234; class=class="str">"cmt">// Wingdings down arrow
    arrowColor = clrRed;
    arrowPrice = highBuffer[barIndex] + (InpArrowOffset * point);
  }
  
  if(!ObjectCreate(class="num">0, arrowName, OBJ_ARROW, class="num">0, timeBuffer[barIndex], arrowPrice))
  {
    Print("Failed to create arrow object ", arrowName);
    class="kw">return;
  }
  ObjectSetInteger(class="num">0, arrowName, OBJPROP_COLOR, arrowColor);
  ObjectSetInteger(class="num">0, arrowName, OBJPROP_ARROWCODE, arrowCode);
}

class=class="str">"cmt">// Record the signal details for later backtesting evaluation
SignalInfo sig;
sig.type = signalText;
sig.barIndex = barIndex;
sig.signalTime = timeBuffer[barIndex];
if(StringFind(signalText, "Bullish") != -class="num">1)
  sig.signalPrice = lowBuffer[barIndex];
else
  sig.signalPrice = highBuffer[barIndex];
ArrayResize(signals, ArraySize(signals) + class="num">1);
signals[ArraySize(signals) - class="num">1] = sig;

UpdateSignalCountLabel();
}
class="type">void EvaluateSignalsAndPrint()
{
  class="type">class="kw">double closeAll[];
  class="type">int totalBars = CopyClose(_Symbol, _Period, class="num">0, WHOLE_ARRAY, closeAll);
  if(totalBars <= class="num">0)
  {
    Print("Error copying complete close data for evaluation");
    class="kw">return;
  }
  ArraySetAsSeries(closeAll, true);

  class="type">int totalEvaluated = class="num">0, regTotal = class="num">0, hidTotal = class="num">0;
  class="type">int regEval = class="num">0, hidEval = class="num">0;
  class="type">int regCorrect = class="num">0, hidCorrect = class="num">0;

逐根回测信号的命中率怎么算

信号回测不能只看生成了多少根,得把每根信号推后 InpEvalBars 根 K 线,用那时的收盘价和信号触发价比对错。下面这段循环就是干这事:遍历 signals 数组,算 evalIndex = barIndex - InpEvalBars,越界就跳过,否则取 closeAll[evalIndex] 作为评估收盘价。 分类靠字符串匹配:带 Bullish 且含 Reg 的常规多头信号,若 evalClose > signalPrice 记 regCorrect++;带 Hidden 的隐藏背离,同样逻辑记 hidCorrect++。Bearish 方向反过来,evalClose < signalPrice 才算对。最后 totalEvaluated 累计所有有效评估。 三个准确率分开算:overallAccuracy 是 (regCorrect+hidCorrect)/totalEvaluated*100,regAccuracy 和 hidAccuracy 各自除以 regEval、hidEval。若分母为 0 直接给 0.0,避免除零崩脚本。跑完用 Print 把总数、评估数和整体准确率(保留两位小数)打到日志,EURUSD 这类高杠杆品种回测前先认清外汇高风险。

MQL5 / C++
for(class="type">int i = class="num">0; i < ArraySize(signals); i++)
  {
      class="type">int evalIndex = signals[i].barIndex - InpEvalBars;
      if(evalIndex < class="num">0)
         class="kw">continue;
      class="type">class="kw">double evalClose = closeAll[evalIndex];
      
      if(StringFind(signals[i].type, "Bullish") != -class="num">1)
      {
          if(StringFind(signals[i].type, "Reg") != -class="num">1)
          {
              regTotal++;
              regEval++;
              if(evalClose > signals[i].signalPrice)
                 regCorrect++;
          }
          else if(StringFind(signals[i].type, "Hidden") != -class="num">1)
          {
              hidTotal++;
              hidEval++;
              if(evalClose > signals[i].signalPrice)
                 hidCorrect++;
          }
          totalEvaluated++;
      }
      else if(StringFind(signals[i].type, "Bearish") != -class="num">1)
      {
          if(StringFind(signals[i].type, "Reg") != -class="num">1)
          {
              regTotal++;
              regEval++;
              if(evalClose < signals[i].signalPrice)
                 regCorrect++;
          }
          else if(StringFind(signals[i].type, "Hidden") != -class="num">1)
          {
              hidTotal++;
              hidEval++;
              if(evalClose < signals[i].signalPrice)
                 hidCorrect++;
          }
          totalEvaluated++;
      }
  }
   
   class="type">class="kw">double overallAccuracy = (totalEvaluated > class="num">0) ? (class="type">class="kw">double)(regCorrect + hidCorrect) / totalEvaluated * class="num">100.0 : class="num">0.0;
   class="type">class="kw">double regAccuracy = (regEval > class="num">0) ? (class="type">class="kw">double)regCorrect / regEval * class="num">100.0 : class="num">0.0;
   class="type">class="kw">double hidAccuracy = (hidEval > class="num">0) ? (class="type">class="kw">double)hidCorrect / hidEval * class="num">100.0 : class="num">0.0;
   
   Print("----- Backtest Signal Evaluation -----");
   Print("Total Signals Generated: ", ArraySize(signals));
   Print("Signals Evaluated: ", totalEvaluated);
   Print("Overall Accuracy: ", DoubleToString(overallAccuracy, class="num">2), "%");

◍ 把信号统计打印到日志里

这段输出逻辑直接把两类信号的评估样本量与准确率落到 MT5 Experts 日志,方便你跑完回测后快速比对。 Regular 与 Hidden 分开打印,字段依次是总数、已评估数、保留两位小数后的准确率百分比;用 DoubleToString(...,2) 控制精度,避免浮点噪声干扰肉眼判断。 在外汇与贵金属这种高杠杆、高波动市场,信号准确率只是概率参考,任何一次统计都不能当作方向保证,开 MT5 按 F4 编译后看日志才是硬验证。

MQL5 / C++
  Print("Regular Signals: ", regTotal, " | Evaluated: ", regEval, " | Accuracy: ", DoubleToString(regAccuracy, class="num">2), "%");
  Print("Hidden Signals:  ", hidTotal, " | Evaluated: ", hidEval, " | Accuracy: ", DoubleToString(hidAccuracy, class="num">2), "%");

「拖进图表跑一遍信号对不对」

EA 在 MetaEditor 里编译通过后,直接拖到 MT5 图表上跑,第一步务必用模拟账户,外汇和贵金属杠杆品种爆仓速度远超股票,真金白银试错不划算。 想肉眼复核信号,就在「指标」面板里调出 RSI,参数必须和 EA 内部写死的周期、价位线一致,否则图表和策略会对不上。我们在一分钟周期上抓到过一次常规看涨背离,RSI 底背离配合价格新低,信号在副图里看得非常直白。 另一组在 Boom 500 上跑的测试,价格行为形态叠加 RSI 双重确认,给出了卖出信号;用回测 GIF 复看时,隐藏延续信号和常规信号混在一起,少数没拿到确认的单子该过滤就过滤,别全吃。 图表上能看到的确认案例:1 分钟周期常规背离、Boom 500 双确认空单、回测中隐藏+常规信号并存,这三组现象够你开 MT5 照着复现一遍。

把 RSI 背离工具接进你的验证流程

RSI 背离对比工具在系列里排第 13 号,发布于 14/02/25,定位是把价格走势与 RSI 背离做同屏比对,属于 Lynnchris 工具箱里偏价格行为验证的一环。前面 12 个工具从图表投影仪到 FibVWAP,覆盖叠加、表格预测、布林带+RSI+ATR 的 EA、外部资金流等,说明这条工具线一直在往「多源交叉」走。 测试里出现过有希望的结果和积极趋势,但作者明确点出:每个信号入场前都必须和你的整体策略交叉核对,工具只负责监控和确认,不替代判断。外汇与贵金属杠杆高、滑点跳空频繁,盲目跟信号可能放大亏损。 下一步值得做的是把外部库接入做精确摆动点识别,这能直接抬升背离信号的命中概率。建议你在 MT5 里加载 RSI_DIVERGENCE.mq5,先把 RSI 周期和背离阈值按自己品种调一遍,再决定是否进实盘观察。

让小布替你跑这套扫描
这些背离诊断小布盯盘的 AIGC 已内置,打开对应品种页即可看到 RSI 哨兵式的标记与摘要,你专注决策而非画线。

常见问题

常规背离抓趋势反转,价格与 RSI 高低点方向相反;隐藏背离抓趋势延续,方向相同但低点/高点层级错位。代码里用摆动点检测分别判定。
默认 14 是通用起点,但高波动贵金属可降到 9~11 以减少滞后,具体以回测样本为准,没有单一最优值。
概率上它只提示动量偏移,外汇贵金属属高风险,须结合价格行为结构确认,倾向视为待验证警报而非指令。
小布内置的 AIGC 诊断已覆盖同类背离扫描,无需自行编译;若想深度改逻辑,可参考本篇代码自行扩展。
震荡市中背离失效概率偏高,隐藏背离本就指向延续,需看所处趋势阶段与成交量配合,不能单看指标。