价格行为分析工具包开发(第十三部分):RSI 哨兵工具(基础篇)
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[] // 高价缓冲
<span class="comment">class=class="str">"cmt">//+--------------------------------------------------------------------+</span> <span class="comment">class=class="str">"cmt">//| RSI Divergence.mql5 |</span> <span class="comment">class=class="str">"cmt">//| Copyright class="num">2025, Christian Benjamin |</span> <span class="comment">class=class="str">"cmt">//| [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> <span class="class="type">class="kw">string">"[MQL5官方文档] <span class="preprocessor">class="macro">#class="kw">property </span><span class="macro">version</span> <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> InpRSIPeriod = <span class="number">class="num">14</span>; <span class="comment">class=class="str">"cmt">// RSI period</span> <span class="keyword">input</span> <span class="keyword">class="type">int</span> InpSwingLeft = <span class="number">class="num">1</span>; <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> InpSwingRight = <span class="number">class="num">1</span>; <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> InpLookback = <span class="number">class="num">100</span>; <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> InpEvalBars = <span class="number">class="num">5</span>; <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> InpMinBarsBetweenSignals = <span class="number">class="num">1</span>; <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 = <span class="number">class="num">3.0</span>; <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 = <span class="number">class="num">0.05</span>; <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 = <span class="number">class="num">1.0</span>; <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> InpUseRSIThreshold = <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 = <span class="number">class="num">30</span>; <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 = <span class="number">class="num">70</span>; <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> rsiHandle; <span class="comment">class=class="str">"cmt">// Handle for the RSI indicator</span> <span class="keyword">class="type">class="kw">double</span> rsiBuffer[]; <span class="comment">class=class="str">"cmt">// Buffer for RSI values</span> <span class="keyword">class="type">class="kw">double</span> lowBuffer[]; <span class="comment">class=class="str">"cmt">// Buffer for low prices</span> <span class="keyword">class="type">class="kw">double</span> highBuffer[]; <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 策略测试器用历史数据验证。
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 出的检测日志。 外汇与贵金属杠杆高,背离只是概率倾向信号,实盘前务必在策略测试器用历史数据验证触发频率与假信号比例。
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) {