Indicator · Pine Script v6 · Own pane

Swing Conviction Radar [WavesUnchained]

by casoon

Swing Conviction Radar measures the strength of individual price swings by calculating a composite conviction score from 0 to 100 at confirmed pivots.

View on GitHub

GitHub stars9on the repository
Pine versionv6current
Lines of code271
Settings11inputs
LicenceMIT
Last update3 days ago27 Oct 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

271 lines of Pine Script v6 from indicators/market_structure/swing_conviction_radar/swing_conviction_radar.pine, published under MIT. Keep the author's credit and licence lines when you reuse it.

swing_conviction_radar.pine
//@version=6
// ============================================================================
// Swing Conviction Radar [WavesUnchained]
// Version: 1.2.2
// Author: WavesUnchained
// Build: 2026-10-12
// ============================================================================
// Scores each price swing (pivot-to-pivot leg) by three conviction factors:
// speed (ATR-normalized distance per bar), cleanliness (fraction of on-trend
// bars in the leg), and volume gradient (volume rising toward the pivot).
// Divergences between successive price extremes and conviction are early
// reversal warnings: price extends further but the swing drives it less hard.
//
// Features:
//   ✓ Per-leg conviction score 0–100 at each confirmed pivot
//   ✓ Three components: Speed (40%), Cleanliness (40%), Volume Gradient (20%)
//   ✓ Bearish divergence: price higher high, conviction lower → hidden weakness
//   ✓ Bullish divergence: price lower low, conviction lower → exhausted sellers
//   ✓ Step-line conviction history for both bull and bear legs
//   ✓ Dashboard: last/prev leg scores + divergence state
//
// Data Contract:
//   Price:     REQUIRED   chart symbol
//   Volume:    OPTIONAL   real trade volume only — Volume Gradient weight
//                         degrades to 0 and Speed/Cleanliness renormalize
//   OI:        NO
//   Reference: NO
//   Verdict:   CFD-degraded
//
// ============================================================================

indicator("Swing Conviction Radar [WavesUnchained]", "SCR [WU]", format=format.price, precision=2, overlay=false,
     max_bars_back=500, max_labels_count=200)

// ─── Inputs ───────────────────────────────────────────────────────────────────

grPiv    = "Pivot Settings"
pivLbL   = input.int(5,   "Left Bars",      minval=2, maxval=20,  group=grPiv)
pivLbR   = input.int(5,   "Right Bars",     minval=2, maxval=20,  group=grPiv)
maxLeg   = input.int(100, "Max Leg Bars",   minval=10, maxval=300, group=grPiv,
     tooltip="Cap on bars scanned per leg. Increase for very long swings.")

grScore  = "Scoring"
wSpeed   = input.float(0.4, "Speed Weight",       minval=0.0, maxval=1.0, step=0.1, group=grScore)
wClean   = input.float(0.4, "Cleanliness Weight", minval=0.0, maxval=1.0, step=0.1, group=grScore)
wVol     = input.float(0.2, "Volume Gradient Wt", minval=0.0, maxval=1.0, step=0.1, group=grScore)
atrLen   = input.int(14,    "ATR Length",          minval=5, maxval=50,   group=grScore)
speedRef = input.float(1.0, "Speed Reference (ATR×/bar)", minval=0.5, maxval=10.0, step=0.5, group=grScore,
     tooltip="Speed at which conviction maxes out. Tune to your instrument. Provisional default — not yet validated on data.")

grD      = "Display"
showDiv  = input.bool(true, "Show Divergence Labels", group=grD)
showDash = input.bool(true, "Show Dashboard",          group=grD)

// ─── Pivot detection ──────────────────────────────────────────────────────────

pivHigh = ta.pivothigh(high, pivLbL, pivLbR)
pivLow  = ta.pivotlow(low,  pivLbL, pivLbR)
atrVal  = ta.atr(atrLen)

// Real trade volume only (DATA_VALIDITY.md §2/§7) - tick/n/a volume is not a
// participation signal. Volume Gradient's weight is zeroed out below when
// this is false, and the Speed/Cleanliness weights renormalize automatically
// through f_legScore's existing totalW division.
volumeIsReal = (ta.cum(nz(volume)) > 0 and syminfo.volumetype != "tick") and not na(volume)

// ─── Leg anchor state ─────────────────────────────────────────────────────────

var int   lastLowBar  = 0
var float lastLowVal  = 0.0
var bool  hasLow      = false

var int   lastHighBar = 0
var float lastHighVal = 0.0
var bool  hasHigh     = false

// Conviction history (last two of each direction for divergence)
var float lastHighConv  = na
var float prevHighConv  = na
var float lastHighPrice = na
var float prevHighPrice = na

var float lastLowConv   = na
var float prevLowConv   = na
var float lastLowPrice  = na
var float prevLowPrice  = na

// Step-line values
var float highStep = na
var float lowStep  = na

// ─── Leg scoring ──────────────────────────────────────────────────────────────

f_legScore(isBull, legBars, legDist, atrVal) =>
    // Speed component
    speedRaw = legBars > 0 and atrVal > 0 ? legDist / legBars / atrVal : 0.0
    speedN   = math.min(speedRaw / speedRef, 1.0)

    // Cleanliness + volume gradient via loop over leg bars
    maxI    = math.min(legBars - 1, maxLeg - 1)
    onCount = 0
    vol1    = 0.0  // bars nearer the end of the leg (small i)
    vol2    = 0.0  // bars nearer the start of the leg (large i)
    vol1ct  = 0
    vol2ct  = 0

    for i = 0 to maxI
        offset = pivLbR + i
        bool isOn = false
        if isBull
            isOn := close[offset] > close[offset + 1]
        else
            isOn := close[offset] < close[offset + 1]
        if isOn
            onCount += 1
        if i * 2 <= maxI
            vol1  += nz(volume[offset])
            vol1ct += 1
        else
            vol2  += nz(volume[offset])
            vol2ct += 1

    loopLen  = maxI + 1
    cleanN   = loopLen > 0 ? onCount / loopLen : 0.0

    vol1Avg  = vol1ct > 0 ? vol1 / vol1ct : 0.0
    vol2Avg  = vol2ct > 0 ? vol2 / vol2ct : 1.0
    volGrad  = vol2Avg > 0 ? vol1Avg / vol2Avg - 1.0 : 0.0
    volN     = math.max(math.min(volGrad + 0.5, 1.0), 0.0)

    effWVol  = volumeIsReal ? wVol : 0.0
    totalW   = wSpeed + wClean + effWVol
    totalW > 0 ? (speedN * wSpeed + cleanN * wClean + volN * effWVol) / totalW * 100.0 : 0.0

// ─── Process pivot highs ──────────────────────────────────────────────────────

var bool bearDiv = false
bearDiv := false

if not na(pivHigh)
    pivBar  = bar_index - pivLbR
    pivVal  = high[pivLbR]
    legBars = hasLow ? pivBar - lastLowBar : 0
    legDist = hasLow ? math.abs(pivVal - lastLowVal) : 0.0

    conv = hasLow and legBars > 0 ? f_legScore(true, legBars, legDist, atrVal) : na

    if not na(conv)
        // Divergence: price higher high, conviction lower → hidden weakness
        if not na(lastHighConv) and not na(lastHighPrice)
            if pivVal > lastHighPrice and conv < lastHighConv
                bearDiv := true
                if showDiv
                    label.new(pivBar, conv + 8, "DIV ↓",
                         color=color.new(#d50000, 10), textcolor=color.white,
                         style=label.style_label_down, size=size.small)

        label.new(pivBar, conv, str.tostring(math.round(conv)),
             color=color.new(color.gray, 80), textcolor=color.new(color.gray, 20),
             style=label.style_label_up, size=size.tiny)

        prevHighConv  := lastHighConv
        prevHighPrice := lastHighPrice
        lastHighConv  := conv
        lastHighPrice := pivVal
        highStep      := conv

    hasHigh     := true
    lastHighBar := pivBar
    lastHighVal := pivVal

// ─── Process pivot lows ───────────────────────────────────────────────────────

var bool bullDiv = false
bullDiv := false

if not na(pivLow)
    pivBar  = bar_index - pivLbR
    pivVal  = low[pivLbR]
    legBars = hasHigh ? pivBar - lastHighBar : 0
    legDist = hasHigh ? math.abs(pivVal - lastHighVal) : 0.0

    conv = hasHigh and legBars > 0 ? f_legScore(false, legBars, legDist, atrVal) : na

    if not na(conv)
        // Divergence: price lower low, conviction lower → exhausted sellers
        if not na(lastLowConv) and not na(lastLowPrice)
            if pivVal < lastLowPrice and conv < lastLowConv
                bullDiv := true
                if showDiv
                    label.new(pivBar, -(conv + 8), "DIV ↑",
                         color=color.new(#00c853, 10), textcolor=color.white,
                         style=label.style_label_up, size=size.small)

        label.new(pivBar, -conv, str.tostring(math.round(conv)),
             color=color.new(color.gray, 80), textcolor=color.new(color.gray, 20),
             style=label.style_label_down, size=size.tiny)

        prevLowConv  := lastLowConv
        prevLowPrice := lastLowPrice
        lastLowConv  := conv
        lastLowPrice := pivVal
        lowStep      := -conv

    hasLow     := true
    lastLowBar := pivBar
    lastLowVal := pivVal

// ─── Plots ────────────────────────────────────────────────────────────────────

bullC = color.new(#00c853, 0)
bearC = color.new(#d50000, 0)

hline(0,    "Zero",          color=color.new(color.gray, 50))
hline(70,   "High Conv +",   color=color.new(#00c853, 70), linestyle=hline.style_dotted)
hline(-70,  "High Conv −",   color=color.new(#d50000, 70), linestyle=hline.style_dotted)

plot(highStep, "Bull Conviction", color=color.new(#00c853, 30), linewidth=1,
     style=plot.style_stepline)
plot(lowStep,  "Bear Conviction", color=color.new(#d50000, 30), linewidth=1,
     style=plot.style_stepline)

plotshape(bearDiv, "Bear Divergence", shape.triangledown, location.top,
     color=bearC, size=size.small)
plotshape(bullDiv, "Bull Divergence", shape.triangleup,   location.bottom,
     color=bullC, size=size.small)

// ─── Alerts ───────────────────────────────────────────────────────────────────

bool alertsOnBarClose = input.bool(true, "Alerts only on bar close (confirmed)", group="Alerts")
bool alertOk = not alertsOnBarClose or barstate.isconfirmed

alertcondition(alertOk and (bearDiv), "Bearish Conviction Divergence", "SCR · BEAR CONVICTION DIV SHORT · {{ticker}} {{interval}}")
alertcondition(alertOk and (bullDiv), "Bullish Conviction Divergence", "SCR · BULL CONVICTION DIV LONG · {{ticker}} {{interval}}")

// ─── Dashboard ────────────────────────────────────────────────────────────────

color tc = color.new(color.gray, 20)
color hd = color.new(color.gray, 90)

var table t = table.new(position.top_right, 2, 7,
     bgcolor=color.new(color.white, 5),
     border_color=color.new(color.gray, 60), border_width=1,
     frame_color=color.new(color.gray, 40), frame_width=1)

if showDash and barstate.islast
    table.cell(t, 0, 0, "Swing Conviction",  text_color=tc, bgcolor=hd, text_size=size.small)
    table.cell(t, 1, 0, "Score",             text_color=tc, bgcolor=hd, text_size=size.small)

    hStr  = na(lastHighConv) ? "—" : str.tostring(math.round(lastHighConv))
    hpStr = na(prevHighConv) ? "—" : str.tostring(math.round(prevHighConv))
    lStr  = na(lastLowConv)  ? "—" : str.tostring(math.round(lastLowConv))
    lpStr = na(prevLowConv)  ? "—" : str.tostring(math.round(prevLowConv))

    table.cell(t, 0, 1, "Bull Leg (last)",   text_color=tc,                    text_size=size.small)
    table.cell(t, 1, 1, hStr,                text_color=bullC,                 text_size=size.small)
    table.cell(t, 0, 2, "Bull Leg (prev)",   text_color=tc,                    text_size=size.small)
    table.cell(t, 1, 2, hpStr,              text_color=color.new(#00c853, 50), text_size=size.small)

    table.cell(t, 0, 3, "", bgcolor=color.new(color.gray, 80))
    table.cell(t, 1, 3, "", bgcolor=color.new(color.gray, 80))

    table.cell(t, 0, 4, "Bear Leg (last)",   text_color=tc,                    text_size=size.small)
    table.cell(t, 1, 4, lStr,                text_color=bearC,                 text_size=size.small)
    table.cell(t, 0, 5, "Bear Leg (prev)",   text_color=tc,                    text_size=size.small)
    table.cell(t, 1, 5, lpStr,              text_color=color.new(#d50000, 50), text_size=size.small)

    divState = bearDiv ? "Bear Div!" : bullDiv ? "Bull Div!" : "—"
    divC     = bearDiv ? bearC : bullDiv ? bullC : tc
    table.cell(t, 0, 6, "Divergence",        text_color=tc,   text_size=size.small)
    table.cell(t, 1, 6, divState,            text_color=divC, text_size=size.small)

Overview

Swing Conviction Radar [WavesUnchained] evaluates three swing components: ATR-normalized speed, trend cleanliness (fraction of on-trend bars), and volume gradient toward the pivot. The indicator identifies bullish and bearish divergences when price forms new extremes without matching conviction, signaling momentum exhaustion or hidden weakness. It plots step-line conviction history in a separate pane and displays a summary dashboard table. Due to pivot right-bar requirements, swing calculations confirm with a slight lag.

Details

Published by

casoon on GitHub

Draws

Separate pane below the chart

Markets

Any symbol

Timeframes

Any timeframe

Risk Notes

No repainting, lookahead or stacked entries were found in its code or README. That says nothing about how well it works: test it first.

Topics

pine-scriptpinescripttechnical-analysistrading-indicatorstrading-strategiestradingview

Settings

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

SettingTypeDefaultVariable
Left Barsint5pivLbL
Right Barsint5pivLbR
Max Leg Barsint100maxLeg
Speed Weightfloat0.4wSpeed
Cleanliness Weightfloat0.4wClean
Volume Gradient Wtfloat0.2wVol
ATR Lengthint14atrLen
Speed Reference (ATR×/bar)float1.0speedRef
Show Divergence LabelsbooltrueshowDiv
Show DashboardbooltrueshowDash
Alerts only on bar close (confirmed)booltruealertsOnBarClose
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

Oscillator Suite

by TamaraKGreen
46 stars

Oscillator Suite plots a normalised price oscillator in a separate pane and overlays a signal line that can use SMA, EMA, WMA, HMA, RMA, VWMA, T3, ZEMA, KAMA, or GKEMA. It marks bullish and bearish divergences, a smoothed money-flow series, confluence areas an

Indicator v5 Oscillators & momentumDivergence
Updated 8 weeks ago · None stated GitHub repository of Oscillator Suite
11 stars

This indicator plots a smoothed Z-score of cumulative volume delta in a separate pane, showing whether buying or selling pressure is dominant and when the reading enters extreme zones. It marks spot accumulation, climax or exhaustion, and pivot-based price-vol

Indicator v6 Oscillators & momentumVolume Repaints
Updated 7 weeks ago · MIT GitHub repository of CVD Z-Score [NeuNue]

Apex Vector

by app-builder-sketch
1 stars

Apex Vector is a separate-pane oscillator that plots a smoothed vector and classifies efficiency against superconductor and resistive thresholds, with a high-heat warning color. Optional volume flux uses a volume-normalization length. Users set vector length,

Indicator v6 Oscillators & momentumVolume
Updated 10 months ago · GPL-3.0 GitHub repository of Apex Vector

This indicator scans a pasted list of symbols for RSI directional momentum and shows matching long and short signals in a table in a separate pane. The user chooses continuous break, continuous retracement, flip break, flip retracement, divergence, or hidden d

Indicator v6 Oscillators & momentumDivergence
Updated 1 year ago · MPL-2.0 GitHub repository of Momentum Setup - RSI Directional Momentum Scanner
228 stars

This indicator calculates the Relative Strength Index (RSI) across four user-selected timeframes and plots the results in a separate pane. It identifies regular bullish and bearish divergences between price pivot points and the primary timeframe RSI value. Add

Indicator v6 Oscillators & momentumDivergence
Updated 3 months ago · None stated GitHub repository of Multi-Timeframe RSI with Divergence

MFI Divergences

by tistaharahap
25 stars

This indicator plots the Money Flow Index in a separate pane and marks regular and hidden bullish and bearish divergences between price pivots and the oscillator. It builds the index from positive and negative money flow, then finds pivot highs and lows using

Indicator v4 Oscillators & momentumVolume
Updated 6 years ago · None stated GitHub repository of MFI Divergences

More Oscillators & momentum 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