Edgoro
Guides

How to code RSI divergence in Pine Script v6 (without repainting)

Detect bullish and bearish RSI divergence in Pine Script v6 with confirmed pivots, draw and alert on it, and avoid the v6 detail that breaks many scripts.

RSI divergence is one of the most requested indicators and one of the most loosely defined. Everyone agrees on the picture: price makes a new low, RSI doesn't. Turning that picture into code forces you to answer questions the picture skips. What is a low? How far apart can two lows be? When exactly is the divergence known?

This guide answers them with a complete Pine Script v6 indicator that detects regular bullish and bearish divergence on confirmed pivots, draws it, and sends alerts, without repainting. It's a tool for study, not a trading signal on its own.

What counts as divergence

TypePriceRSIUsually read as
Regular bullishLower lowHigher lowSelling momentum fading
Regular bearishHigher highLower highBuying momentum fading
Hidden bullishHigher lowLower lowPossible continuation of an uptrend
Hidden bearishLower highHigher highPossible continuation of a downtrend

This indicator handles the two regular types. Hidden divergence uses the same machinery with the comparisons flipped, covered at the end.

Turning the idea into rules

  1. Swings are pivots on RSI. A pivot low is an RSI value lower than the N values on each side. We use 5 bars left and 5 right.
  2. Compare consecutive pivots. When a new RSI pivot low is confirmed, compare it with the previous one. The price comparison uses the bar lows at the same two bars.
  3. Limit the distance. Pivots more than 60 bars apart are too far to be related; ignore that pair.
  4. Signal at confirmation. A pivot needs 5 bars on its right, so the divergence is known 5 bars after the pivot. That's when we signal.

Rule 4 is what keeps the indicator honest. Scripts that draw the signal on the pivot bar itself look sharper in hindsight, but on a live chart they show it five bars after the fact anyway.

The complete indicator

//@version=6
indicator("RSI divergence (confirmed pivots)", shorttitle = "RSI div", max_lines_count = 200)

rsiLen    = input.int(14, "RSI length", minval = 2)
leftBars  = input.int(5, "Pivot bars to the left", minval = 1)
rightBars = input.int(5, "Pivot bars to the right", minval = 1)
maxGap    = input.int(60, "Max bars between pivots", minval = 5)

r = ta.rsi(close, rsiLen)

// A pivot is confirmed rightBars bars after it forms: signals arrive late, but they never move.
pl = ta.pivotlow(r, leftBars, rightBars)
ph = ta.pivothigh(r, leftBars, rightBars)

var float prevLowRsi    = na
var float prevLowPrice  = na
var int   prevLowBar    = na
var float prevHighRsi   = na
var float prevHighPrice = na
var int   prevHighBar   = na

bullDiv = false
bearDiv = false

if not na(pl)
    pivotBar   = bar_index - rightBars
    pivotPrice = low[rightBars]
    if not na(prevLowRsi) and pivotBar - prevLowBar <= maxGap
        // Regular bullish: price makes a lower low, RSI a higher low.
        bullDiv := pivotPrice < prevLowPrice and pl > prevLowRsi
        if bullDiv
            line.new(prevLowBar, prevLowRsi, pivotBar, pl, color = color.new(color.teal, 0), width = 2)
    prevLowRsi   := pl
    prevLowPrice := pivotPrice
    prevLowBar   := pivotBar

if not na(ph)
    pivotBar   = bar_index - rightBars
    pivotPrice = high[rightBars]
    if not na(prevHighRsi) and pivotBar - prevHighBar <= maxGap
        // Regular bearish: price makes a higher high, RSI a lower high.
        bearDiv := pivotPrice > prevHighPrice and ph < prevHighRsi
        if bearDiv
            line.new(prevHighBar, prevHighRsi, pivotBar, ph, color = color.new(color.red, 0), width = 2)
    prevHighRsi   := ph
    prevHighPrice := pivotPrice
    prevHighBar   := pivotBar

plot(r, "RSI", color = color.new(color.purple, 0), linewidth = 2)
hline(70, "Overbought", color = color.gray, linestyle = hline.style_dashed)
hline(30, "Oversold", color = color.gray, linestyle = hline.style_dashed)
plotshape(bullDiv, "Bullish divergence", shape.labelup, location.bottom, color.new(color.teal, 0), text = "Bull", textcolor = color.white, size = size.tiny)
plotshape(bearDiv, "Bearish divergence", shape.labeldown, location.top, color.new(color.red, 0), text = "Bear", textcolor = color.white, size = size.tiny)

alertcondition(bullDiv, "Bullish RSI divergence", "Bullish RSI divergence confirmed on {{ticker}} ({{interval}})")
alertcondition(bearDiv, "Bearish RSI divergence", "Bearish RSI divergence confirmed on {{ticker}} ({{interval}})")

How it works

Pivots. ta.pivotlow(r, 5, 5) returns the RSI value of a pivot low on the bar where it becomes confirmed, five bars after it formed, and na on every other bar. So not na(pl) means "a pivot low was just confirmed", and the pivot itself sits at bar_index - rightBars.

Remembering the previous pivot. The var variables keep the RSI value, the price low and the bar index of the last pivot from one confirmation to the next. Each new pivot is compared with them, then replaces them.

Price at the pivot. low[rightBars] is the bar low at the pivot bar. Comparing lows at the RSI pivots is a common, simple definition. A stricter version would also require a price pivot near the same bar.

Drawing. When a divergence is found, line.new() connects the two RSI pivots in the indicator pane, and a label marks the bar where the divergence was confirmed. Nothing is drawn retroactively except the line, which joins two bars that were already in the past when it was drawn.

The v6 detail that breaks many divergence scripts

A lot of published divergence code was written for older versions of Pine and looks like this:

inRange = plFound and ta.barssince(plFound[1]) <= 60

In Pine Script v6, and and or are lazy: the right-hand side is only evaluated when the left-hand side doesn't already decide the result. Here ta.barssince() would only run on bars where plFound is true, so its internal count skips most bars and returns nonsense. The script compiles; the logic is simply wrong.

The rule: call every function that keeps history (ta.barssince, ta.valuewhen, ta.crossover, ta.highest and the rest) on every bar, assign the result to a variable at the top level, and combine plain variables with and. The script above avoids the problem by storing pivot data in var variables instead of searching back through history.

Adding hidden divergence

Hidden divergence reverses the comparisons. In the pivot-low block, a hidden bullish divergence is pivotPrice > prevLowPrice and pl < prevLowRsi: price makes a higher low while RSI makes a lower low. In the pivot-high block, hidden bearish is pivotPrice < prevHighPrice and ph > prevHighRsi. Add a variable for each, compute it next to the regular one, and give it its own plot and alert condition.

Making it more selective

  • Zone filter: only count bullish divergence when the earlier RSI low was below 30, and bearish when the earlier high was above 70.
  • Trend filter: only show bullish divergence when price is above a long moving average, or when the higher-timeframe trend agrees.
  • Trigger: wait for a confirming event after the divergence, such as a close above the high of the pivot bar, before considering a trade.

Using it responsibly

Divergence describes momentum that is slowing. It doesn't say when, or whether, price will turn, and in strong trends it can appear several times in a row while the trend continues. Treat a confirmed divergence as a prompt to look closer, not as an order. If you build a strategy on it, test it with costs and look at enough trades before trusting the result. Because a pivot is only confirmed a few bars after it forms, any divergence script that marks it earlier will repaint.

For the indicator itself, see the RSI guide; the same pivot logic works on the stochastic oscillator or MACD by swapping the series.

Sources and further reading

Try it with Edgoro

Describe your exact definition — "regular and hidden RSI(14) divergence on 5/5 pivots no more than 60 bars apart, bullish only below 30, alert when the next bar closes above the pivot bar's high" — and Edgoro writes the Pine Script v6 indicator for you. Build your divergence indicator with Edgoro.

Divergence is not a reliable reversal signal on its own. Indicators describe the past; trading involves substantial risk of loss.

Build it with Edgoro

Describe your strategy in plain English and get a TradingView indicator in Pine Script v6 or a MetaTrader 5 Expert Advisor in minutes. Every first purchase is covered by a 7-day money-back guarantee.

Build your indicator with Edgoro