RSI Divergence Flagger with Strict Pivot Rules
Relative Strength Index (RSI) divergence occurs when price and momentum move in opposite directions. A bullish divergence forms when price prints a lower low but RSI prints a higher low; a bearish divergence emerges when price prints a higher high while RSI prints a lower high. These mismatches can signal weakening momentum ahead of reversals. Traders, particularly those working intraday or swing timeframes, use divergence detection to anticipate trend exhaustion or continuation failures. This indicator applies strict pivot validation rules to reduce false signals inherent in mechanical divergence detection.
//@version=6
indicator("RSI Divergence Flagger", overlay=false)
// Inputs
rsi_length = input.int(14, "RSI Length", minval=2)
pivot_lookback = input.int(5, "Pivot Lookback Bars", minval=2)
show_divergences = input.bool(true, "Show Divergences")
show_pivots = input.bool(true, "Show Pivot Points")
// Calculate RSI
rsi = ta.rsi(close, rsi_length)
// Detect swing pivots using strict rules
is_high_pivot = high[pivot_lookback] > ta.highest(high[pivot_lookback + 1], pivot_lookback) and
high[pivot_lookback] > ta.highest(high[1], pivot_lookback)
is_low_pivot = low[pivot_lookback] < ta.lowest(low[pivot_lookback + 1], pivot_lookback) and
low[pivot_lookback] < ta.lowest(low[1], pivot_lookback)
// Store pivot values and RSI at pivot bars
var array<float> price_highs = array.new<float>()
var array<float> price_lows = array.new<float>()
var array<float> rsi_at_highs = array.new<float>()
var array<float> rsi_at_lows = array.new<float>()
var array<int> high_bars = array.new<int>()
var array<int> low_bars = array.new<int>()
// Store new pivots
if is_high_pivot
array.push(price_highs, high[pivot_lookback])
array.push(rsi_at_highs, rsi[pivot_lookback])
array.push(high_bars, bar_index - pivot_lookback)
if array.size(price_highs) > 10
array.shift(price_highs)
array.shift(rsi_at_highs)
array.shift(high_bars)
if is_low_pivot
array.push(price_lows, low[pivot_lookback])
array.push(rsi_at_lows, rsi[pivot_lookback])
array.push(low_bars, bar_index - pivot_lookback)
if array.size(price_lows) > 10
array.shift(price_lows)
array.shift(rsi_at_lows)
array.shift(low_bars)
// Detect bullish divergence: price LL, RSI HL
bullish_div = false
if array.size(price_lows) >= 2
recent_low = array.get(price_lows, array.size(price_lows) - 1)
prior_low = array.get(price_lows, array.size(price_lows) - 2)
recent_rsi = array.get(rsi_at_lows, array.size(rsi_at_lows) - 1)
prior_rsi = array.get(rsi_at_lows, array.size(rsi_at_lows) - 2)
bullish_div := recent_low < prior_low and recent_rsi > prior_rsi
// Detect bearish divergence: price HH, RSI LH
bearish_div = false
if array.size(price_highs) >= 2
recent_high = array.get(price_highs, array.size(price_highs) - 1)
prior_high = array.get(price_highs, array.size(price_highs) - 2)
recent_rsi = array.get(rsi_at_highs, array.size(rsi_at_highs) - 1)
prior_rsi = array.get(rsi_at_highs, array.size(rsi_at_highs) - 2)
bearish_div := recent_high > prior_high and recent_rsi < prior_rsi
// Plotting
plot(rsi, "RSI", color=color.new(color.blue, 0), linewidth=2)
hline(50, "Midline", color=color.gray, linestyle=hline.style_dashed)
hline(70, "Overbought", color=color.red, linestyle=hline.style_dotted)
hline(30, "Oversold", color=color.green, linestyle=hline.style_dotted)
// Plot divergence flags
if show_divergences
if bullish_div
alert("Bullish Divergence Detected", alert.freq_once_per_bar)
plotshape(series=rsi, location=location.bottom, shape=shape.labelup,
color=color.new(color.green, 0), text="Bull Div", size=size.small)
if bearish_div
alert("Bearish Divergence Detected", alert.freq_once_per_bar)
plotshape(series=rsi, location=location.top, shape=shape.labeldown,
color=color.new(color.red, 0), text="Bear Div", size=size.small)
// Mark pivot points on RSI
if show_pivots
if is_high_pivot
plotchar(series=rsi[pivot_lookback], location=location.top, char="-",
color=color.new(color.orange, 0), size=size.tiny)
if is_low_pivot
plotchar(series=rsi[pivot_lookback], location=location.bottom, char="-",
color=color.new(color.purple, 0), size=size.tiny)
How the code works
The indicator uses two core mechanics: pivot detection and divergence comparison.
Pivot detection applies a strict rule: a high pivot is only confirmed if the bar at position pivot_lookback bars ago is both higher than the pivot_lookback bars immediately after it and higher than the pivot_lookback bars immediately before it. This ensures a true swing high, not a noise spike. The same symmetry applies to low pivots, looking left and right from the anchor bar. By checking both sides, the logic avoids lookahead bias while still validating structure.
Divergence detection maintains rolling arrays of the last two price pivots and their corresponding RSI values. When a new low pivot forms, the code compares it to the prior low pivot. If price is lower but RSI is higher, a bullish divergence is recorded. For highs, if price is higher but RSI is lower, a bearish divergence is flagged. The bars stored in the array allow visual connection between pivots across the chart.
The RSI itself is a standard 14-period (configurable) Wilder smoothing calculation, built in via ta.rsi(). The indicator does not look ahead; it only confirms pivots after they are pivot_lookback bars old, ensuring all data is historical at the time of detection.
Reading it on a chart
The indicator plots RSI in the lower panel with overbought (70) and oversold (30) bands marked. Orange and purple dots mark where RSI pivots occur. When a bullish divergence forms, a green "Bull Div" label appears at the RSI base; when a bearish divergence forms, a red "Bear Div" label appears at the RSI peak.
A bullish divergence typically appears near RSI oversold levels and often precedes a bounce or recovery. A bearish divergence near overbought levels may warn of pullback or reversal. However, divergence alone is not a trading signal; it flags a momentum shift that traders often combine with price structure, volume, or trend context before acting. Traders often wait for price confirmation (a break of a recent high or low) before treating the divergence as a turn signal.
Limitations
Lag and false signals: Pivots are confirmed only after pivot_lookback bars have passed, meaning divergences appear several bars behind the actual turning point in momentum. This lag trades immediacy for reliability but can place entries late if the move accelerates quickly.
Whipsaw in ranging markets: When price oscillates sideways, both price and RSI generate multiple pivots in quick succession, creating many divergence flags that rarely lead to directional moves. The indicator does not distinguish between trending and consolidation contexts.
RSI's own limitations: RSI tends to stay overbought during strong uptrends and oversold during strong downtrends without reversing, so divergences in extreme conditions often fail. The 70/30 bands are arbitrary thresholds, not absolute turn zones.
Pivot sensitivity: The pivot_lookback parameter directly controls both pivot spacing and sensitivity. A small value (e.g., 2-3 bars) catches rapid divergences but generates noise; a large value (e.g., 8-10 bars) finds only major structure but may miss moves entirely. No single setting suits all timeframes or assets equally.
No volume or confirmation: The indicator flags divergence based solely on price and momentum. A large bearish divergence at an all-time high carries different risk than one near a support zone; the indicator provides no context weighting.
Key definitions
Divergence: A disagreement between price direction and an oscillator (here, RSI) direction across two or more pivot points.
Bullish divergence: Price prints a lower low while the momentum oscillator prints a higher low, suggesting weakening downward momentum.
Bearish divergence: Price prints a higher high while the momentum oscillator prints a lower high, suggesting weakening upward momentum.
Pivot: A local extremum, a bar that is higher than bars both to its left and right (high pivot) or lower than bars both to its left and right (low pivot).
RSI (Relative Strength Index): A momentum oscillator that measures the speed and magnitude of price changes on a scale of 0 to 100, calculated as 100 − [100 / (1 + RS)], where RS is the average of N-period gains divided by the average of N-period losses [1].
Lookahead bias: The error of using future information when making a trading decision or generating a signal; avoided here by confirming pivots only after they are several bars old.
References
- Wilder, J.W., "New Concepts in Technical Trading Systems." McLelland & Stewart (1978). Defines RSI and its Wilder smoothing method.
- CME Group, "Futures and Options Education: Technical Analysis." https://www.cmegroup.com/education/technical-analysis.html (n.d.). General technical analysis standards.
- Investopedia, "Divergence: Definition and Example." https://www.investopedia.com/terms/d/divergence.asp Explains price-oscillator divergence concepts and use.
- TradingView Pine Script Reference, "ta.rsi() Function." https://www.tradingview.com/pine-script-reference/v6/#fun_ta.rsi Documentation of RSI calculation in Pine Script v6.
Educational research on historical data only. Not investment advice, not a signal, and never a performance promise. Past results do not predict future performance. Every reference is link-verified before publication and every paper is re-audited weekly against the library's editorial standard.
Last reviewed by the PropLedger research pipeline: 2026-09-20. Educational research on historical data, not financial advice.
Keep reading
Volume Climax Detector (Z-Score)
Free open-source Pine Script indicator: volume climax detector (volume z-score). Full code and a plain-English walkthrough.
Swing Structure: BOS and CHoCH Labels
Free open-source Pine Script indicator: swing high/low structure marker (BOS and CHoCH labels). Full code and a plain-English walkthrough.
ATR-Based Volatility Bands
Free open-source Pine Script indicator: ATR-based volatility bands. Full code and a plain-English walkthrough.