🎓 Tutorials

TradingView Pine Script RSI Divergence Indicator: Build One That Actually Works (2026)

⚠️ Disclosure: Some links on this page are affiliate links. If you sign up through them, I may earn a commission — at no extra cost to you. I only review tools I actually use.
# TradingView Pine Script RSI Divergence Indicator: Build One That Actually Works (2026)

*Most RSI divergence indicators on TradingView's library are either too noisy or miss real setups entirely. I built my own in Pine Script v6 — here's exactly how.*

---

RSI divergence is one of those concepts that sounds simple in textbooks but turns into a mess when you try to code it. The price makes a new high, RSI doesn't — bullish exhaustion, right? In theory, yes. In practice, every public RSI divergence indicator I tested on TradingView either painted signals on every other candle or missed the divergences that actually mattered.

I trade USDJPY on daily charts using a momentum-based system. After months of squinting at RSI and price action trying to spot divergences manually, I decided to automate it. This tutorial walks through building an RSI divergence indicator from scratch in Pine Script v6 — one that uses proper pivot detection, filters out noise, and gives you alerts you can actually trade on.

If you've already gone through my Pine Script moving average crossover tutorial, you'll recognize the approach: start simple, then layer in the filters that make it production-ready.

---

What Is RSI Divergence (and Why Should You Care)?

RSI divergence happens when price and the RSI oscillator disagree about momentum:

There's also hidden divergence (continuation signals), but we'll start with regular divergence since it's what most traders look for. Why I use it: RSI divergence doesn't predict *when* a reversal will happen — it signals that the current move is running out of steam. Combined with other signals (like moving average structure), it helps me avoid entering trades right before a reversal. On USDJPY, I've found bearish divergence on the daily chart to be particularly reliable around seasonal turning points.

---

Step 1: Basic RSI Divergence Indicator (The Naive Approach)

Let's start with what most tutorials give you, so you can see *why* it doesn't work well. Open the Pine Editor on TradingView and paste:

//@version=6
indicator("RSI Divergence — Basic", overlay=false)

// --- Inputs ---
rsiLen    = input.int(14, "RSI Length", minval=2)
src       = input.source(close, "Source")

// --- RSI Calculation ---
rsiValue = ta.rsi(src, rsiLen)
plot(rsiValue, "RSI", color=color.purple, linewidth=2)
hline(70, "Overbought", color=color.red, linestyle=hline.style_dotted)
hline(30, "Oversold", color=color.green, linestyle=hline.style_dotted)

// --- Naive Divergence: Compare Current Bar to N Bars Ago ---
lookback = input.int(14, "Lookback Bars", minval=5)

// Bullish: price lower low, RSI higher low
bullDiv = (low < low[lookback]) and (rsiValue > rsiValue[lookback]) and (rsiValue < 40)
// Bearish: price higher high, RSI lower high
bearDiv = (high > high[lookback]) and (rsiValue < rsiValue[lookback]) and (rsiValue > 60)

plotshape(bullDiv, "Bull Div", shape.triangleup, location.bottom, color.green, size=size.small)
plotshape(bearDiv, "Bear Div", shape.triangledown, location.top, color.red, size=size.small)

Add it to your chart. You'll immediately see the problem: signals everywhere. On a USDJPY daily chart, this fires 2-3 times per week. Most are noise because comparing to "N bars ago" is arbitrary — the price N bars ago might not even be a meaningful swing point.

The fix: We need to compare *actual pivot highs and lows*, not just arbitrary lookback points.

---

Step 2: Pivot-Based RSI Divergence (The Right Way)

This is the core improvement. Instead of comparing to a fixed lookback, we detect actual swing highs and swing lows using ta.pivothigh() and ta.pivotlow(), then compare the RSI values at those pivots:

//@version=6
indicator("RSI Divergence — Pivot Based", overlay=false, max_lines_count=500)

// ─── Inputs ─────────────────────────────────────────
rsiLen     = input.int(14, "RSI Length", minval=2)
pivotLeft  = input.int(5, "Pivot Lookback Left", minval=1)
pivotRight = input.int(5, "Pivot Lookback Right", minval=1)
maxBars    = input.int(60, "Max Bars Between Pivots", minval=10, maxval=200)
src        = input.source(close, "Source")

// ─── RSI ────────────────────────────────────────────
rsiValue = ta.rsi(src, rsiLen)
plot(rsiValue, "RSI", color=color.new(color.purple, 0), linewidth=2)
hline(70, "Overbought", color=color.red, linestyle=hline.style_dotted)
hline(30, "Oversold", color=color.green, linestyle=hline.style_dotted)

// ─── Pivot Detection ────────────────────────────────
// Pivots are confirmed `pivotRight` bars ago
pivotLowPrice  = ta.pivotlow(low, pivotLeft, pivotRight)
pivotHighPrice = ta.pivothigh(high, pivotLeft, pivotRight)

pivotLowRSI    = ta.pivotlow(rsiValue, pivotLeft, pivotRight)
pivotHighRSI   = ta.pivothigh(rsiValue, pivotLeft, pivotRight)

// ─── Track Previous Pivots ──────────────────────────
var float prevPivotLowPrice  = na
var int   prevPivotLowBar    = na
var float prevPivotLowRSI    = na

var float prevPivotHighPrice = na
var int   prevPivotHighBar   = na
var float prevPivotHighRSI   = na

// ─── Bullish Divergence (price lower low, RSI higher low) ───
bullDiv = false
if not na(pivotLowPrice)
    currBar = bar_index - pivotRight
    currPriceLow = pivotLowPrice
    currRSILow   = pivotLowRSI

    if not na(prevPivotLowPrice)
        barDiff = currBar - prevPivotLowBar
        if barDiff <= maxBars and barDiff > 0
            // Price: lower low | RSI: higher low
            if currPriceLow < prevPivotLowPrice and currRSILow > prevPivotLowRSI
                bullDiv := true
                // Draw line on RSI pane
                line.new(prevPivotLowBar, prevPivotLowRSI, currBar, currRSILow,
                     color=color.green, width=2, style=line.style_solid)

    prevPivotLowPrice := currPriceLow
    prevPivotLowBar   := currBar
    prevPivotLowRSI   := currRSILow

// ─── Bearish Divergence (price higher high, RSI lower high) ───
bearDiv = false
if not na(pivotHighPrice)
    currBar = bar_index - pivotRight
    currPriceHigh = pivotHighPrice
    currRSIHigh   = pivotHighRSI

    if not na(prevPivotHighPrice)
        barDiff = currBar - prevPivotHighBar
        if barDiff <= maxBars and barDiff > 0
            // Price: higher high | RSI: lower high
            if currPriceHigh > prevPivotHighPrice and currRSIHigh < prevPivotHighRSI
                bearDiv := true
                line.new(prevPivotHighBar, prevPivotHighRSI, currBar, currRSIHigh,
                     color=color.red, width=2, style=line.style_solid)

    prevPivotHighPrice := currPriceHigh
    prevPivotHighBar   := currBar
    prevPivotHighRSI   := currRSIHigh

// ─── Signals ────────────────────────────────────────
plotshape(bullDiv, "Bullish Divergence", shape.labelup, location.bottom,
     color=color.green, text="Bull", textcolor=color.white, size=size.small)
plotshape(bearDiv, "Bearish Divergence", shape.labeldown, location.top,
     color=color.red, text="Bear", textcolor=color.white, size=size.small)

// ─── Alerts ─────────────────────────────────────────
alertcondition(bullDiv, "Bullish RSI Divergence", "RSI bullish divergence detected")
alertcondition(bearDiv, "Bearish RSI Divergence", "RSI bearish divergence detected")

What changed and why:

1. Pivot detectionta.pivothigh() and ta.pivotlow() find actual swing points, not arbitrary lookbacks. A pivot low with pivotLeft=5, pivotRight=5 means the bar was lower than the 5 bars on either side. This is a real turning point.

2. Bar distance limit — The maxBars parameter prevents comparing pivots that are 200 bars apart. Divergences lose meaning over very long distances.

3. Visual lines — Green/red lines drawn between the RSI pivot points make divergence immediately visible.

4. Alert conditions — You can set TradingView alerts that fire when divergence is detected. If you're on the Plus plan or higher, these run server-side so you don't need to keep the browser open.

On USDJPY daily, this version fires roughly 1-2 signals per month — a dramatic reduction from the naive approach's 2-3 per week, and the signals actually correspond to meaningful swing points.

---

Step 3: Adding RSI Zone Filters (Reducing False Signals)

Not all divergences are created equal. A bearish divergence when RSI is at 55 is much weaker than one at 75. Let's add zone filters:

// ─── Add these inputs at the top ────────────────────
rsiOBLevel = input.int(60, "Min RSI for Bearish Div", minval=50, maxval=90)
rsiOSLevel = input.int(40, "Max RSI for Bullish Div", minval=10, maxval=50)

// ─── Modify the divergence conditions ───────────────
// In the bullish divergence block, add:
if currPriceLow < prevPivotLowPrice and currRSILow > prevPivotLowRSI
    if currRSILow < rsiOSLevel  // Only in oversold territory
        bullDiv := true
        // ... line drawing code

// In the bearish divergence block, add:
if currPriceHigh > prevPivotHighPrice and currRSIHigh < prevPivotHighRSI
    if currRSIHigh > rsiOBLevel  // Only in overbought territory
        bearDiv := true
        // ... line drawing code

Why this matters: From my experience trading USDJPY, divergences in the "middle zone" (RSI 40-60) are noise about 70% of the time. The indicator should only alert you when RSI is in meaningful territory — below 40 for bullish divergences, above 60 for bearish. These thresholds are adjustable in settings.

---

Step 4: Adding Hidden Divergence (Continuation Signals)

Hidden divergence signals trend continuation rather than reversal:

Add this to the pivot detection section:

// ─── Input toggle ───────────────────────────────────
showHidden = input.bool(true, "Show Hidden Divergence")

// ─── Hidden Bullish (higher low price, lower low RSI) ───
hiddenBullDiv = false
if showHidden and not na(pivotLowPrice)
    currBar = bar_index - pivotRight
    currPriceIdx = pivotLowPrice
    currRSIIdx   = pivotLowRSI

    if not na(prevPivotLowPrice)
        barDiff = currBar - prevPivotLowBar
        if barDiff <= maxBars and barDiff > 0
            if currPriceIdx > prevPivotLowPrice and currRSIIdx < prevPivotLowRSI
                hiddenBullDiv := true
                line.new(prevPivotLowBar, prevPivotLowRSI, currBar, currRSIIdx,
                     color=color.lime, width=1, style=line.style_dashed)

// ─── Hidden Bearish (lower high price, higher high RSI) ───
hiddenBearDiv = false
if showHidden and not na(pivotHighPrice)
    currBar = bar_index - pivotRight
    currPriceIdx = pivotHighPrice
    currRSIIdx   = pivotHighRSI

    if not na(prevPivotHighPrice)
        barDiff = currBar - prevPivotHighBar
        if barDiff <= maxBars and barDiff > 0
            if currPriceIdx < prevPivotHighPrice and currRSIIdx > prevPivotHighRSI
                hiddenBearDiv := true
                line.new(prevPivotHighBar, prevPivotHighRSI, currBar, currRSIIdx,
                     color=color.orange, width=1, style=line.style_dashed)

// ─── Hidden Divergence Plots ────────────────────────
plotshape(hiddenBullDiv, "Hidden Bull", shape.diamond, location.bottom,
     color=color.lime, text="H-Bull", textcolor=color.white, size=size.tiny)
plotshape(hiddenBearDiv, "Hidden Bear", shape.diamond, location.top,
     color=color.orange, text="H-Bear", textcolor=color.white, size=size.tiny)

Hidden divergence is less popular but I find it useful as a confirmation tool. If I'm already in a USDJPY long based on my momentum system and I see a hidden bullish divergence, it gives me confidence to hold the position rather than taking early profits.

---

Step 5: Combining RSI Divergence With Moving Averages

RSI divergence alone isn't a trading system — it's a filter. In my own setup, I combine it with moving average structure (covered in detail in my MA crossover tutorial):

The combo logic: Here's how to add a trend filter to the indicator:

// ─── Trend Context ──────────────────────────────────
trendMA   = input.int(200, "Trend MA Length", minval=50)
trendLine = ta.ema(close, trendMA)
upTrend   = close > trendLine
downTrend = close < trendLine

// ─── Modify alert conditions for trend-aligned signals ───
trendAlignedBull = bullDiv and upTrend
trendAlignedBear = bearDiv and downTrend

alertcondition(trendAlignedBull, "Trend-Aligned Bull Div",
     "Bullish RSI divergence WITH uptrend — high confidence")
alertcondition(trendAlignedBear, "Trend-Aligned Bear Div",
     "Bearish RSI divergence WITH downtrend — high confidence")

This is where Pine Script indicators start becoming actual trading tools. The trend filter alone cuts out about half the signals — and from what I've seen on my USDJPY charts, it cuts the *wrong* half.

---

Step 6: Setting Up Alerts

The whole point of building a custom indicator is to get alerts without staring at charts all day. If you've followed my TradingView backtest settings guide, you know I'm a big fan of automating what can be automated.

To set alerts:

1. Add the indicator to your chart

2. Click the Alert button (clock icon) or press Alt+A 3. Under "Condition", select your indicator name 4. Choose the specific alert condition (e.g., "Trend-Aligned Bull Div") 5. Set notification method: app push, email, webhook, or SMS 6. For webhooks (Telegram bots, etc.), the alert message is customizable Pro tip: TradingView's free plan only allows 1 active alert. If you're running multiple indicators, you'll want at least the Plus plan for 5 server-side alerts. I run about 4 alerts across different indicators and timeframes for USDJPY.

---

Common Mistakes When Coding RSI Divergence

After iterating on this indicator across several months, here's what tripped me up:

1. Not Accounting for Pivot Confirmation Delay

ta.pivothigh(high, 5, 5) confirms a pivot 5 bars after the actual high. Your signal is delayed by pivotRight bars. This is intentional — you can't confirm a swing high until you see bars declining after it. But it means you're never catching the exact top/bottom. Workaround: I use pivotLeft=5, pivotRight=3 as a compromise — slightly faster confirmation with acceptable reliability.

2. Comparing Price Pivots to RSI Non-Pivots

Some indicators compare price pivot lows to RSI values at the same bar, but don't check if RSI also formed a pivot at that bar. This leads to phantom divergences. In my indicator, I use ta.pivotlow(rsiValue, ...) separately to ensure RSI also has a genuine swing point.

3. No Maximum Distance Between Pivots

Without maxBars, the indicator might compare a pivot from 6 months ago to today's pivot and call it a divergence. Technically correct, practically useless. I cap it at 60 bars (about 3 months on daily charts).

4. Ignoring the Trend

Counter-trend divergences have a much lower win rate. A bullish divergence in a strong downtrend (price well below 200 EMA) often just leads to a brief bounce before the downtrend continues. Always check trend context.

---

RSI Divergence Settings: What I Use

SettingMy ValueWhy
RSI Length14Standard, well-tested
Pivot Left5Enough bars for a real swing
Pivot Right3Faster confirmation than 5
Max Bars60~3 months on daily
Min RSI (Bear)60Filter out mid-range noise
Max RSI (Bull)40Only oversold divergences
Trend MA200Standard trend reference
Hidden DivOnUseful for holding positions
These work for USDJPY on daily timeframes. If you trade crypto or lower timeframes, you'll want to adjust — smaller maxBars, possibly looser RSI thresholds since crypto RSI behaves differently.

---

FAQ

Does RSI divergence work on all timeframes?

It works on any timeframe, but reliability increases with higher timeframes. On 1-minute or 5-minute charts, you'll get far more false signals because intrabar noise creates meaningless "pivots." I primarily use it on daily charts, and occasionally on 4-hour. Below that, the signal-to-noise ratio drops fast.

Why does my RSI divergence indicator show signals that don't match what I see visually?

Most likely a pivot detection issue. If your indicator uses a fixed lookback (rsiValue[14]) instead of actual pivot detection (ta.pivotlow()), it's comparing arbitrary bars rather than genuine swing points. The pivot-based approach in this tutorial solves that.

Can I use RSI divergence as a standalone trading signal?

I wouldn't recommend it. RSI divergence tells you momentum is weakening — it doesn't tell you *when* the reversal will happen. A market can stay divergent for weeks. I use it as a filter alongside my momentum strategy: divergence + trend alignment + moving average structure = a trade I might take. Divergence alone = an observation I note.

What's the difference between regular and hidden divergence?

Regular divergence signals potential reversal — momentum disagrees with price at extremes. Hidden divergence signals continuation — during a pullback within a trend, RSI dips lower than the previous pullback but price holds higher (or vice versa for downtrends). Think of regular as "the move is ending" and hidden as "the trend is still alive."

How do I avoid RSI divergence false signals?

Three filters I use: (1) RSI zone filter — only take bullish divergences when RSI is below 40 and bearish above 60, (2) Trend alignment — match divergence direction to the 200 EMA trend, (3) Minimum bar distance — don't compare pivots that are too close together (less than 10 bars) or too far apart (more than 60 bars). Together these cut false signals by roughly 60% in my testing.

---

Wrapping Up

RSI divergence is one of those indicators that's simple in concept but surprisingly tricky to code properly. The difference between a useful indicator and a noisy one comes down to three things: real pivot detection, zone filtering, and trend context.

The complete Pine Script v6 code in this tutorial gives you all three. Add it to your chart, adjust the settings for your instrument and timeframe, and set alerts so you don't have to babysit the chart.

If you're building a broader Pine Script trading system, start with the moving average crossover as your trend engine, then layer this RSI divergence indicator as a timing filter. That's essentially what I do for USDJPY.

Ready to build your own indicators? TradingView is the platform I use for all my charting and Pine Script development. The free tier gets you started — upgrade when you need server-side alerts.

---

*Affiliate disclosure: Some links in this article are affiliate links. I only recommend tools I actually use. This doesn't affect my opinions or the technical content.*

*Risk warning: Trading involves substantial risk of loss. RSI divergence is a technical analysis tool, not a guarantee of future results. Never trade with money you can't afford to lose.*

TradingView

Ready to get started? Use the link below — it helps support ChartedTrader at no cost to you.

Try TradingView — the platform I use daily for charting and alerts →
📈

About the author

I'm a systematic trader running live strategies on IB (USDJPY momentum) and Hyperliquid (crypto perps). Every tool reviewed here is something I've used with real capital. Questions? Reach out.

📚 Related Articles

🎓 Tutorials

Interactive Brokers Flex Query + Python: How to Automate Your Trading Reports (2026 Guide)

I automated my IB trading reports with Flex Queries and 40 lines of Python. Saves me 2 hours every week — here's the exact code and setup.

March 2, 2026 ⏱ 10 min read
📖 Guides

TradingView Strategy Tester Backtest Settings Explained (2026 Guide)

I backtested 200+ USDJPY trades on TradingView and discovered my results were 40% off until I fixed 3 settings. Here's what actually matters.

March 2, 2026 ⏱ 10 min read
📖 Guides

How to Deposit USDC to Hyperliquid from OKX (Step-by-Step Guide 2026)

I bridged USDC from OKX to Hyperliquid in under 20 minutes. Here's every step, the exact fees I paid, and two mistakes to avoid.

March 2, 2026 ⏱ 6 min read

📬 Get weekly trading insights

Real trades, honest reviews, no fluff. One email per week.