//@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.
Composite swing conviction scoring from 0 to 100
Three-factor weighting for speed, cleanliness, and volume gradient
Bullish and bearish price versus conviction divergence detection
On-pane step-line history and summary dashboard display
The inputs you can change from the gear icon on the chart. Defaults are the author's.
Setting
Type
Default
Variable
Left Bars
int
5
pivLbL
Right Bars
int
5
pivLbR
Max Leg Bars
int
100
maxLeg
Speed Weight
float
0.4
wSpeed
Cleanliness Weight
float
0.4
wClean
Volume Gradient Wt
float
0.2
wVol
ATR Length
int
14
atrLen
Speed Reference (ATR×/bar)
float
1.0
speedRef
Show Divergence Labels
bool
true
showDiv
Show Dashboard
bool
true
showDash
Alerts only on bar close (confirmed)
bool
true
alertsOnBarClose
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.
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
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
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,
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
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
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