Indicator · Pine Script v6 · On the chart

SMT Divergence

by Rickylel

An indicator that detects SMT divergence between correlated markets, mainly NQ and ES.

View on GitHub

Repaints

GitHub stars1on the repository
Pine versionv6current
Lines of code245
Settings29inputs
LicenceMIT
Last update6 weeks ago11 Sep 2026

Add It to Your TradingView Chart

  1. 1Copy the scriptPress Copy script above, or the copy button on the code below.
  2. 2Open the Pine EditorOpen a TradingView chart and choose Pine Editor at the bottom of the screen.
  3. 3Paste over the templateCreate a new indicator, select all of the template text and paste. Press Save and give it a name.
  4. 4Add it to the chartPress Add to chart, then adjust the settings with the gear icon next to its name.

Source Code

245 lines of Pine Script v6 from SMT Divergence [r1ckylel].pine, published under MIT. Keep the author's credit and licence lines when you reuse it.

SMT Divergence [r1ckylel].pine
//@version=6
indicator("SMT Divergence [r1ckylel]", overlay = true, max_lines_count = 500, max_labels_count = 500)

// USAGE: Use standard intraday candles. In Manual mode, compare NQ with ES
// using equivalent contract expiries, sessions, and data providers.
// Automatic mode selects the correlated continuous CME contract.
// Reference levels are confirmed pivots paired chronologically, one to one.
// Both levels must remain unbroken when the pair is formed. Signals that
// occurred before both pivots were available are not reconstructed.
// Subsequent breaks are evaluated intrabar when Real-time SMTs is enabled.
// Wicks count; touching a level exactly does NOT count as a break.
// Up to 'capacity' pairs are tracked per side. Exceeding this limit removes
// the oldest pair and its drawing, even if the divergence is still valid.
// Historical OHLC data cannot reconstruct the tick order within a candle.

// Compact input layout. TradingView controls the width of input fields.
const string G_MAIN = "SMT"
const string G_ADVANCED = "ADVANCED"
const string G_ALERTS = "ALERTS"
string assetMode = input.string("Automatic", "Correlated Asset", options = ["Automatic", "Manual"], group = G_MAIN, display = display.none, tooltip = "Automatic: NQ/MNQ → ES1!, ES/MES → NQ1!. Uses continuous CME contracts. For a specific expiry or another market, select Manual.")
string manualAsset = input.symbol("CME_MINI:ES1!", "", group = G_MAIN, active = assetMode == "Manual", display = display.none)
string sensitivity = input.string("Sensitive", "Sensitivity", options = ["Very sensitive", "Sensitive", "Balanced", "Conservative", "Custom"], group = G_MAIN, display = display.none, tooltip = "Pivot bars on each side: 1 / 3 / 5 / 8. Custom uses the value in Advanced. The reference pivot must be confirmed before monitoring a live break.")
int maxVisible = input.int(5, "Maximum visible SMTs", minval = 1, maxval = 50, group = G_MAIN, display = display.none, tooltip = "Total bullish + bearish lines after overlap filtering. Prioritizes the most recent endpoints, then the most recent origins. Hidden pairs continue to be monitored.")
bool realTime = input.bool(true, "Real-time SMTs", group = G_MAIN, tooltip = "Enabled: update and invalidate during the current candle. Disabled: update only at candle close.")
color bullColor = input.color(color.black, "Bullish:", group = G_MAIN, inline = "bull")
string bullWeight = input.string("Thin", "", options = ["Thin", "Medium", "Thick"], group = G_MAIN, inline = "bull", display = display.none)
string bullStyleInput = input.string("Solid", "", options = ["Solid", "Dashed", "Dotted"], group = G_MAIN, inline = "bull", display = display.none)
color bearColor = input.color(color.black, "Bearish:", group = G_MAIN, inline = "bear")
string bearWeight = input.string("Thin", "", options = ["Thin", "Medium", "Thick"], group = G_MAIN, inline = "bear", display = display.none)
string bearStyleInput = input.string("Solid", "", options = ["Solid", "Dashed", "Dotted"], group = G_MAIN, inline = "bear", display = display.none)
bool labelsOn = input.bool(true, "Label", group = G_MAIN, inline = "label")
color customTextColor = input.color(color.gray, "", group = G_MAIN, inline = "label", active = labelsOn)
string labelSizeInput = input.string("Tiny", "Size", options = ["Tiny", "Small", "Normal", "Large"], group = G_MAIN, inline = "label", active = labelsOn, display = display.none)
bool showSymbol = input.bool(true, "Show symbol", group = G_MAIN, inline = "label", active = labelsOn)

bool showBull = input.bool(true, "Show bullish", group = G_ADVANCED, inline = "show")
bool showBear = input.bool(true, "Show bearish", group = G_ADVANCED, inline = "show")
bool filterOverlap = input.bool(true, "One SMT per endpoint", group = G_ADVANCED, tooltip = "Same end candle and price: lowest bullish origin or highest bearish origin wins. Ties favor the most recent origin.")
int customStrength = input.int(3, "Custom sensitivity", minval = 1, maxval = 30, group = G_ADVANCED, active = sensitivity == "Custom", display = display.none)
int tolerance = input.int(3, "Pivot matching distance (bars)", minval = 0, maxval = 30, group = G_ADVANCED, display = display.none)
string bullText = input.string("SMT", "Bullish label", group = G_ADVANCED, active = labelsOn, display = display.none)
string bearText = input.string("SMT", "Bearish label", group = G_ADVANCED, active = labelsOn, display = display.none)
string labelMode = input.string("Text only", "Label style", options = ["Text only", "Background"], group = G_ADVANCED, active = labelsOn, display = display.none)
bool textMatch = input.bool(false, "Match label to line color", group = G_ADVANCED, active = labelsOn)
int labelTransparency = input.int(85, "Background transparency", minval = 0, maxval = 100, group = G_ADVANCED, active = labelsOn and labelMode == "Background", display = display.none)
int capacity = input.int(100, "Tracked pairs per side", minval = 10, maxval = 200, group = G_ADVANCED, display = display.none, tooltip = "Includes hidden and pending pairs. When full, the oldest pair is removed even if still valid. Separate from the visible line limit.")
bool showStatus = input.bool(true, "Show missing-data warning", group = G_ADVANCED)
bool bullAlerts = input.bool(true, "Bullish alerts", group = G_ALERTS, inline = "alerts")
bool bearAlerts = input.bool(true, "Bearish alerts", group = G_ALERTS, inline = "alerts", tooltip = "Create alerts separately in TradingView. Use Once per bar for real-time alerts. Only visible signals notify. Sent alerts cannot be erased; recreate alerts after changing settings.")

bool nqChart = syminfo.root == "NQ" or syminfo.root == "MNQ"
bool esChart = syminfo.root == "ES" or syminfo.root == "MES"
string other = assetMode == "Manual" ? manualAsset : nqChart ? "CME_MINI:ES1!" : "CME_MINI:NQ1!"
if barstate.isfirst and assetMode == "Automatic" and not nqChart and not esChart
    runtime.error("Automatic supports NQ, MNQ, ES and MES. Choose Manual for other symbols.")
string otherShort = array.last(str.split(other, ":"))
int strength = sensitivity == "Very sensitive" ? 1 : sensitivity == "Sensitive" ? 3 : sensitivity == "Balanced" ? 5 : sensitivity == "Conservative" ? 8 : customStrength
int bullWidth = bullWeight == "Thin" ? 1 : bullWeight == "Medium" ? 2 : 3
int bearWidth = bearWeight == "Thin" ? 1 : bearWeight == "Medium" ? 2 : 3
string bullStyle = bullStyleInput == "Dashed" ? line.style_dashed : bullStyleInput == "Dotted" ? line.style_dotted : line.style_solid
string bearStyle = bearStyleInput == "Dashed" ? line.style_dashed : bearStyleInput == "Dotted" ? line.style_dotted : line.style_solid
string labelSize = labelSizeInput == "Small" ? size.small : labelSizeInput == "Normal" ? size.normal : labelSizeInput == "Large" ? size.large : size.tiny

if barstate.isfirst and (not chart.is_standard or not timeframe.isintraday)
    runtime.error("Use standard candles and an intraday timeframe.")
if barstate.isfirst and ticker.standard(other) == ticker.standard(syminfo.tickerid)
    runtime.error("Select a comparison symbol different from the chart symbol.")

[bh, bl, bt] = request.security(other, timeframe.period, [high, low, time], gaps = barmerge.gaps_off, lookahead = barmerge.lookahead_off)
bool synced = not na(bt) and bt == time and not na(bh) and not na(bl)
// Do not use stale prices to fill gaps when calculating pivots.
float otherHigh = synced ? bh : na
float otherLow = synced ? bl : na
float coverage = ta.sma(synced ? 1.0 : 0.0, strength * 2 + 1)
float ahp = ta.pivothigh(high, strength, strength)
float alp = ta.pivotlow(low, strength, strength)
float bhp = ta.pivothigh(otherHigh, strength, strength)
float blp = ta.pivotlow(otherLow, strength, strength)

// Normal persistent structures (var) participate in Pine rollback. The current
// candle's accumulated highs and lows allow signal creation and invalidation
// to be recalculated on every update.
type Pair
    float a
    float b
    int anchor
    bool sweptA = false
    bool sweptB = false
    float endPrice = na
    int endTime = na
    bool notified = false
    bool selected = false
    line ln = na
    label tag = na

type Pending
    float price = na
    int stamp = na
    bool swept = false

var array<Pair> highs = array.new<Pair>()
var array<Pair> lows = array.new<Pair>()
var Pending ha = Pending.new()
var Pending hb = Pending.new()
var Pending la = Pending.new()
var Pending lb = Pending.new()

clearDrawing(Pair p) =>
    line.delete(p.ln)
    label.delete(p.tag)
    p.ln := na
    p.tag := na

process(array<Pair> pairs, bool isHigh, float remote, array<Pair> candidates) =>
    // First pass: update ALL pairs, including hidden ones.
    // Separate state updates from rendering to keep selection independent
    // of the order in which pairs are updated.
    if array.size(pairs) > 0
        for i = array.size(pairs) - 1 to 0
            Pair p = array.get(pairs, i)
            p.selected := false
            p.sweptA := p.sweptA or (isHigh ? high > p.a : low < p.a)
            p.sweptB := p.sweptB or (synced and (isHigh ? remote > p.b : remote < p.b))
            if p.sweptA and p.sweptB
                clearDrawing(p)
                array.remove(pairs, i)
            else if synced and p.sweptA != p.sweptB
                float endpoint = isHigh ? high : low
                bool extends = na(p.endPrice) or (isHigh ? endpoint > p.endPrice : endpoint < p.endPrice)
                if extends
                    p.endPrice := endpoint
                    p.endTime := time

    // A shared endpoint means the same end candle and price. Independent
    // divergences are not grouped merely because their lines intersect.
    // Bullish: lowest origin wins. Bearish: highest origin wins.
    // When origin prices tie, select the most recent origin.
    if array.size(pairs) > 0
        for i = 0 to array.size(pairs) - 1
            Pair p = array.get(pairs, i)
            if not na(p.endTime)
                bool visible = isHigh ? showBear : showBull
                if filterOverlap
                    for j = 0 to array.size(pairs) - 1
                        if j != i
                            Pair q = array.get(pairs, j)
                            bool sameEnd = not na(q.endTime) and q.endTime == p.endTime and q.endPrice == p.endPrice
                            bool stronger = isHigh ? q.a > p.a : q.a < p.a
                            bool tieWinner = q.a == p.a and (q.anchor > p.anchor or (q.anchor == p.anchor and j > i))
                            if sameEnd and (stronger or tieWinner)
                                visible := false
                                break
                if visible
                    array.push(candidates, p)

// Limit is global, after overlap filtering. Select references, not copies.
selectVisible(array<Pair> candidates) =>
    int count = math.min(maxVisible, array.size(candidates))
    if count > 0
        for slot = 0 to count - 1
            int best = 0
            if array.size(candidates) > 1
                for i = 1 to array.size(candidates) - 1
                    Pair candidate = array.get(candidates, i)
                    Pair incumbent = array.get(candidates, best)
                    if candidate.endTime > incumbent.endTime or (candidate.endTime == incumbent.endTime and candidate.anchor > incumbent.anchor)
                        best := i
            Pair chosen = array.remove(candidates, best)
            chosen.selected := true

renderPairs(array<Pair> pairs, bool isHigh, color tint) =>
    bool appeared = false
    if array.size(pairs) > 0
        for i = 0 to array.size(pairs) - 1
            Pair p = array.get(pairs, i)
            if not p.selected
                clearDrawing(p)
            else
                if na(p.ln)
                    p.ln := line.new(p.anchor, p.a, p.endTime, p.endPrice, xloc = xloc.bar_time, color = tint, width = isHigh ? bearWidth : bullWidth, style = isHigh ? bearStyle : bullStyle)
                    if labelsOn
                        string caption = (isHigh ? bearText : bullText) + (showSymbol ? " " + otherShort : "")
                        p.tag := label.new(p.endTime, p.endPrice, caption, xloc = xloc.bar_time, style = labelMode == "Text only" ? label.style_none : (isHigh ? label.style_label_down : label.style_label_up), color = color.new(tint, labelTransparency), textcolor = textMatch ? tint : customTextColor, size = labelSize)
                else
                    line.set_xy2(p.ln, p.endTime, p.endPrice)
                    if not na(p.tag)
                        label.set_xy(p.tag, p.endTime, p.endPrice)
                if not p.notified
                    appeared := true
                    p.notified := true
    appeared

pairPending(Pending a, Pending b, array<Pair> pairs) =>
    if not na(a.stamp) and not na(b.stamp)
        int maxDistance = int(timeframe.in_seconds() * 1000 * tolerance)
        if math.abs(a.stamp - b.stamp) <= maxDistance and not a.swept and not b.swept
            array.push(pairs, Pair.new(a.price, b.price, a.stamp))
            a.stamp := na
            b.stamp := na
            if array.size(pairs) > capacity
                Pair expired = array.shift(pairs)
                clearDrawing(expired)

bool newBear = false
bool newBull = false
// Default is intrabar. In close-only mode, committed drawings stay until close.
if realTime or barstate.isconfirmed
    array<Pair> candidates = array.new<Pair>()
    process(highs, true, bh, candidates)
    process(lows, false, bl, candidates)
    selectVisible(candidates)
    newBear := renderPairs(highs, true, bearColor)
    newBull := renderPairs(lows, false, bullColor)

// Reference levels are fixed when their pivots are confirmed. In real-time
// mode, the breakout candle does NOT need to close. Updating pending levels
// before pairing prevents the use of levels that have already been swept.
if not na(ha.stamp)
    ha.swept := ha.swept or high > ha.price
if not na(la.stamp)
    la.swept := la.swept or low < la.price
if synced
    if not na(hb.stamp)
        hb.swept := hb.swept or bh > hb.price
    if not na(lb.stamp)
        lb.swept := lb.swept or bl < lb.price

if barstate.isconfirmed and coverage == 1.0
    if not na(ahp)
        ha := Pending.new(ahp, time[strength], false)
    if not na(alp)
        la := Pending.new(alp, time[strength], false)
    if not na(bhp)
        hb := Pending.new(bhp, time[strength], false)
    if not na(blp)
        lb := Pending.new(blp, time[strength], false)
    pairPending(ha, hb, highs)
    pairPending(la, lb, lows)

alertcondition(newBear and bearAlerts, "Provisional bearish SMT", "Provisional bearish SMT divergence. The signal may invalidate; a sent alert cannot be withdrawn.")
alertcondition(newBull and bullAlerts, "Provisional bullish SMT", "Provisional bullish SMT divergence. The signal may invalidate; a sent alert cannot be withdrawn.")

var table status = table.new(position.bottom_right, 1, 1)
if barstate.islast
    table.cell(status, 0, 0, (synced or not showStatus) ? "" : "No synchronized data for the comparison symbol", text_color = color.orange, text_size = size.small)

Overview

SMT Divergence pairs confirmed swing highs and lows from both instruments and draws a line and label when one market breaks its reference level and the other does not. The correlated asset is chosen automatically or manually. Settings cover pivot sensitivity, maximum visible signals, overlap filtering, line and label styling, and separate bullish and bearish alerts. In real-time mode signals are provisional and can disappear when invalidated.

Details

Published by

Rickylel on GitHub

Draws

On the price chart

Markets

Indices

Timeframes

M15-H1

Risk Notes

  • Signals can change on the live bar or after it closes (repainting)

Settings

The inputs you can change from the gear icon on the chart. Defaults are the author's.

SettingTypeDefaultVariable
Correlated AssetstringAutomaticassetMode
manualAssetsymbolCME_MINI:ES1!manualAsset
SensitivitystringSensitivesensitivity
Maximum visible SMTsint5maxVisible
Real-time SMTsbooltruerealTime
Bullish:colorcolor.blackbullColor
bullWeightstringThinbullWeight
bullStyleInputstringSolidbullStyleInput
Bearish:colorcolor.blackbearColor
bearWeightstringThinbearWeight
bearStyleInputstringSolidbearStyleInput
LabelbooltruelabelsOn
Show all 29 settings
SettingTypeDefaultVariable
customTextColorcolorcolor.graycustomTextColor
SizestringTinylabelSizeInput
Show symbolbooltrueshowSymbol
Show bullishbooltrueshowBull
Show bearishbooltrueshowBear
One SMT per endpointbooltruefilterOverlap
Custom sensitivityint3customStrength
Pivot matching distance (bars)int3tolerance
Bullish labelstringSMTbullText
Bearish labelstringSMTbearText
Label stylestringText onlylabelMode
Match label to line colorboolfalsetextMatch
Background transparencyint85labelTransparency
Tracked pairs per sideint100capacity
Show missing-data warningbooltrueshowStatus
Bullish alertsbooltruebullAlerts
Bearish alertsbooltruebearAlerts
Third-party code. ForexR did not write, test or endorse this script, and the summary above was generated automatically from its code and README. Indicators describe the past, and a strategy's backtest can look better than live trading, especially when a script repaints. Check it in the Strategy Tester and on a demo account before you rely on it.

Similar Pine Scripts

Bandz HTF

by samuelharting
18 stars

Bandz HTF projects higher-timeframe candle structures, ranges, and key levels directly onto the main price chart. It visualizes multiple configurable timeframe lanes from intraday to weekly alongside primary and macro range levels, including 25%, 50%, and 75%

Indicator v6 SMC / ICT / price actionSupport & resistance
Updated 2 weeks ago · MPL-2.0 GitHub repository of Bandz HTF

Nexural QWAP

by JasonTeixeira
9 stars

Nexural QWAP calculates a volume-weighted average price combined with a cumulative volume delta order flow engine directly on the main price chart. It analyzes market structure by detecting volume absorption, liquidity sweeps, price-CVD divergence, stacked imb

Indicator v6 SMC / ICT / price actionVolume
Updated 13 days ago · Custom licence GitHub repository of Nexural QWAP

Larper Suite

by DeolinNaidoo
8 stars

Larper Suite is an overlay indicator for ICT and quarterly-theory analysis. It plots true session, daily, weekly, monthly, and yearly opens plus prior and current high-low liquidity levels. It tracks multi-asset SMT, swing divergence, fair-value-gap fills, and

Indicator v6 SMC / ICT / price actionDivergence Repaints
Updated 3 months ago · GPL-3.0 GitHub repository of Larper Suite
6 stars

Apex Protocol v3.0 - The SMC Engine is an overlay indicator designed for Smart Money Concepts (SMC) trading strategies. It detects market structure shifts (MSS), breaks of structure (BOS), M5 Fair Value Gaps (FVG), Order Blocks (OB), and Asian session ranges.

Indicator v5 SMC / ICT / price actionSupport & resistance Repaints
Updated 7 months ago · None stated GitHub repository of Apex Protocol v3.0 - The SMC Engine
2 stars

MTF Divergence Scanner detects regular and hidden divergences in RSI or MACD oscillators across multiple timeframes. It scans up to three timeframes simultaneously using request.security with confirmed-bar logic to identify reversal or continuation signals. Th

Indicator v6 Oscillators & momentumDivergence Repaints
Updated 3 months ago · MPL-2.0 GitHub repository of MTF Divergence Scanner
2 stars

Strong Wealth Magnet is an overlay indicator that scores price action and draws selected narrative layers on the price chart. Up to three modes can run together, including a Pro layout and a Six Eyes view that reviews past signals over a chosen window and labe

Indicator v6 SMC / ICT / price actionSupport & resistance
Updated 6 months ago · None stated GitHub repository of Strong Wealth Magnet

More SMC / ICT scripts

Related on ForexR

Trade Your Charts With a Regulated Broker

Compare brokers that connect to TradingView, with spreads, minimum deposits and leverage side by side.

Compare brokers