This indicator quantifies the risk of a prevailing market structure break by combining trend context, structure, and momentum into a composite 0–100 risk score.
//@version=6
// ============================================================================
// Structure Break Risk [WavesUnchained]
// Version: 2.3.1
// Author: WavesUnchained
// Build: 2026-10-02 00:00:00
// ============================================================================
// Indicator that answers: how close is the PREVAILING trend's structure to
// breaking — and against WHICH level? Symmetric by design — in an uptrend it
// reads top-break risk, in a downtrend bottom-break risk; the asymmetry comes
// from the situation (which trend is active), never from a hardcoded long/short.
//
// Two coordinated panes make the read intuitive:
// ✓ RSI pane (this pane) — the RSI with momentum-divergence lines drawn directly
// on it: when price makes a higher high while RSI makes a LOWER high (uptrend),
// or price a lower low while RSI a higher low (downtrend), a line marks the
// failing momentum. Read against the price above — sharing the same time axis —
// the divergence is unmistakable: the earliest structural crack. A divergence
// is price-vs-momentum and is only readable where BOTH are visible
// ✓ Price chart (drawn via force_overlay) — the decisive break-level line the
// trend must hold, a risk-shaded cushion zone between price and that level, a
// thin EMA trend-context line, word event labels (Watch / Break Pressure /
// Structure Broken / Pressure Faded) and a compact info label carrying score,
// state, direction, break level, distance and a mini-bar per sensor
//
// Risk magnitude is the same 0-100 composite as before — colour encodes the RISK
// band (neutral → orange → red → dark red), never the trade direction, so it
// never reads as buy/sell. Direction is stated in words.
//
// Four pre-break evidence sensors across the roles (weighted, never an
// AND-chain) plus BOS as a separate confirmed structure event:
// ✓ Near Break Level (Location→Trigger) — distance-to-BOS in ATR
// ✓ Failed Breakout / SFP (Trigger+Location) — swing sweep that closes back
// ✓ Structure Erosion (Structure) — lower high (up) / higher low (down)
// ✓ Pivot Divergence (Momentum) — price pivot extends, RSI at the pivot does not
// ✓ Confirmed BOS (Event) — close beyond the opposite swing level
//
// Pivots provide swing LEVELS only (Location reference); the break event is the
// Trigger — pivots never act as the signal themselves.
//
// ============================================================================
indicator("Structure Break Risk [WavesUnchained]", "SBR", overlay=false, max_labels_count=500, max_lines_count=100)
// ============================================================================
// Inputs
// ============================================================================
biasLen = input.int(50, "Trend Bias Length", minval=10, group="Trend Context", tooltip="EMA whose level + slope defines the prevailing trend; direction latches and holds through transitions so risk can build before the trend flips")
pivLen = input.int(5, "Swing Pivot Length", minval=2, group="Structure", tooltip="Left/right bars for swing high/low detection — these are LEVEL references, not signals")
nearAtr = input.float(1.5, "Near Break Level ATR Distance", minval=0.2, group="Structure", tooltip="Distance to the break level (in ATR) at which Near-Level risk reaches full")
rsiLen = input.int(14, "RSI Length", minval=5, group="Momentum")
holdBars = input.int(10, "Evidence Memory Bars", minval=1, group="Core", tooltip="Transient evidence decays over this many bars so old structure pressure fades instead of sticking as a hard hold")
smoothLen = input.int(3, "Risk Smoothing", minval=1, group="Core")
wNear = input.float(25.0, "Weight: Near Break Level", minval=0, group="Sensor Weights")
wSfp = input.float(20.0, "Weight: Failed Breakout", minval=0, group="Sensor Weights")
wStruct = input.float(15.0, "Weight: Structure Erosion", minval=0, group="Sensor Weights")
wDiv = input.float(10.0, "Weight: Pivot Divergence", minval=0, group="Sensor Weights")
lvlElev = input.int(35, "State: Watch ≥", group="State Levels")
lvlHigh = input.int(60, "State: Break Pressure ≥", group="State Levels")
lvlCrit = input.int(80, "State: Critical ≥", group="State Levels")
showDiv = input.bool(true, "Show Divergence Lines", group="RSI Pane", tooltip="Draw momentum-divergence lines on the RSI: price extends but RSI does not (uptrend: price higher high / RSI lower high — downtrend: price lower low / RSI higher low). The earliest structural crack, shown where it is actually readable — against the price above.")
showEma = input.bool(true, "Show Trend Context Line", group="Chart", tooltip="Thin EMA line on the price chart whose level + slope defines the prevailing trend that is at risk")
showLevel = input.bool(true, "Show Break-Level Line", group="Chart", tooltip="The decisive swing the prevailing trend must hold, drawn on the price chart; colour = current risk band")
showZone = input.bool(true, "Show Risk Zone", group="Chart", tooltip="Shades the cushion between price and the break level on the price chart; the fill intensifies as risk rises")
showMarkers = input.bool(true, "Show Event Labels", group="Chart", tooltip="Compact structure-risk events: Watch, Break Pressure, Structure Broken, Pressure Faded. Context events, not trade signals.")
markerCooldown = input.int(10, "Event Label Cooldown", minval=0, group="Chart", tooltip="Minimum bars between visible non-BOS event labels")
requireContextFlip = input.bool(true, "Require Context Flip for Break", group="Chart", tooltip="Only calls Structure Broken when the swing break also reclaims the EMA context. A swing break without the flip stays Break Pressure; if the EMA context flips later while price is still beyond that unconsumed swing level, Structure Broken fires on the flip bar.")
showLabel = input.bool(true, "Show Info Label", group="Info Label", tooltip="Compact, mobile-friendly text label (not a table) carrying the full read — score, state, direction, break level, distance, reason and a mini-bar per sensor")
labelOffset = input.int(5, "Label Offset (bars right)", minval=0, group="Info Label", tooltip="How far to the right of the last bar the label floats, so it never sits on the candles")
labelSize = input.string("Normal", "Label Text Size", options=["Small", "Normal", "Large"], group="Info Label")
// ============================================================================
// Helpers
// ============================================================================
f_clamp(x, lo, hi) =>
math.max(lo, math.min(hi, x))
f_norm(x, lo, hi) =>
hi == lo ? 0.0 : f_clamp((x - lo) / (hi - lo), 0.0, 1.0)
// Risk-band colours — encode the RISK level, never the trade direction.
color cNeutral = #64748b
color cOrange = #f59e0b
color cRed = #dc2626
color cDarkRed = #991b1b
color cCalm = #16a34a
f_riskCol(r) =>
r >= lvlCrit ? cDarkRed : r >= lvlHigh ? cRed : r >= lvlElev ? cOrange : cNeutral
// Five-cell mini-bar from a 0..100 value (robust, no str.repeat edge cases).
f_bar(v) =>
int n = math.round(f_clamp(v, 0.0, 100.0) / 20.0)
string s = ""
for i = 0 to 4
s := s + (i < n ? "█" : "░")
s
// Right-pad a 0..100 value to 3 chars so the number column lines up in the label.
f_pad(v) =>
int n = math.round(f_clamp(v, 0.0, 100.0))
(n < 10 ? " " : n < 100 ? " " : "") + str.tostring(n, "#")
// One sensor row: bar FIRST (uniform glyph width → bars align in a proportional
// font), then the padded value, then the name.
f_row(name, v) =>
f_bar(v) + " " + f_pad(v) + " " + name
// ============================================================================
// Stable trend context — latches the last clear direction through transitions
// ============================================================================
ema = ta.ema(close, biasLen)
emaSlope = ema - ema[5]
rawTrendDir = close > ema and emaSlope > 0 ? 1 : close < ema and emaSlope < 0 ? -1 : 0
var int trendDir = 0
if rawTrendDir != 0
trendDir := rawTrendDir
// ============================================================================
// Swing levels (Location reference only) + ATR scale
// ============================================================================
atr = math.max(ta.atr(14), syminfo.mintick)
ph = ta.pivothigh(high, pivLen, pivLen)
pl = ta.pivotlow(low, pivLen, pivLen)
var float lastPH = na
var float prevPH = na
var float lastPL = na
var float prevPL = na
if not na(ph)
prevPH := lastPH
lastPH := ph
if not na(pl)
prevPL := lastPL
lastPL := pl
// ============================================================================
// Pivot-based RSI divergence — RSI sampled at the price-pivot bar
// ============================================================================
rsi = ta.rsi(close, rsiLen)
var float lastPricePH = na
var float prevPricePH = na
var float lastRsiPH = na
var float prevRsiPH = na
var float lastPricePL = na
var float prevPricePL = na
var float lastRsiPL = na
var float prevRsiPL = na
// Bar index of each pivot (the swing occurred pivLen bars before confirmation) —
// the endpoints the divergence line connects.
var int lastPHbar = na
var int prevPHbar = na
var int lastPLbar = na
var int prevPLbar = na
if not na(ph)
prevPricePH := lastPricePH
lastPricePH := ph
prevRsiPH := lastRsiPH
lastRsiPH := rsi[pivLen]
prevPHbar := lastPHbar
lastPHbar := bar_index - pivLen
if not na(pl)
prevPricePL := lastPricePL
lastPricePL := pl
prevRsiPL := lastRsiPL
lastRsiPL := rsi[pivLen]
prevPLbar := lastPLbar
lastPLbar := bar_index - pivLen
// ============================================================================
// Evidence sensors — each 0..1, selected symmetrically by the prevailing trend
// ============================================================================
float sNear = 0.0 // distance to the decisive break level (rises on approach)
float sBos = 0.0 // confirmed break of structure
float sSfp = 0.0 // failed breakout / stop-run that closes back
float sStruct = 0.0 // structure erosion (lower high / higher low)
float sDiv = 0.0 // pivot RSI divergence
float distToBreak = na
float breakLevel = na
bool approachingBreak = false
if trendDir == 1
// --- top-break risk (uptrend losing its structure) ---
breakLevel := lastPL
if not na(lastPL)
distToBreak := (close - lastPL) / atr
approachingBreak := not na(distToBreak[1]) and distToBreak < distToBreak[1]
sNear := distToBreak >= 0.0 and approachingBreak ? 1.0 - f_norm(distToBreak, 0.0, nearAtr) : 0.0
if close < lastPL
sBos := math.max(0.65, f_norm((lastPL - close) / atr, 0.0, 1.5))
if not na(lastPH) and high > lastPH and close < lastPH
sSfp := 1.0
// Structure / divergence fire on the pivot-confirmation bar only, then decay.
if not na(ph) and not na(lastPH) and not na(prevPH) and lastPH < prevPH
sStruct := f_norm((prevPH - lastPH) / atr, 0.0, 2.0)
if not na(ph) and not na(lastPricePH) and not na(prevPricePH) and not na(lastRsiPH) and not na(prevRsiPH)
if lastPricePH > prevPricePH and lastRsiPH < prevRsiPH
sDiv := 1.0
else if trendDir == -1
// --- bottom-break risk (downtrend losing its structure) ---
breakLevel := lastPH
if not na(lastPH)
distToBreak := (lastPH - close) / atr
approachingBreak := not na(distToBreak[1]) and distToBreak < distToBreak[1]
sNear := distToBreak >= 0.0 and approachingBreak ? 1.0 - f_norm(distToBreak, 0.0, nearAtr) : 0.0
if close > lastPH
sBos := math.max(0.65, f_norm((close - lastPH) / atr, 0.0, 1.5))
if not na(lastPL) and low < lastPL and close > lastPL
sSfp := 1.0
// Structure / divergence fire on the pivot-confirmation bar only, then decay.
if not na(pl) and not na(lastPL) and not na(prevPL) and lastPL > prevPL
sStruct := f_norm((lastPL - prevPL) / atr, 0.0, 2.0)
if not na(pl) and not na(lastPricePL) and not na(prevPricePL) and not na(lastRsiPL) and not na(prevRsiPL)
if lastPricePL < prevPricePL and lastRsiPL > prevRsiPL
sDiv := 1.0
// Transient evidence decays so old pressure fades instead of sticking as a
// rectangular hold window.
decayFactor = holdBars <= 1 ? 0.0 : 1.0 - 1.0 / holdBars
var float sNearH = 0.0
var float sBosH = 0.0
var float sSfpH = 0.0
var float sStructH = 0.0
var float sDivH = 0.0
sNearH := math.max(sNear, nz(sNearH[1]) * decayFactor)
sBosH := math.max(sBos, nz(sBosH[1]) * decayFactor)
sSfpH := math.max(sSfp, nz(sSfpH[1]) * decayFactor)
sStructH := math.max(sStruct, nz(sStructH[1]) * decayFactor)
sDivH := math.max(sDiv, nz(sDivH[1]) * decayFactor)
// Confirmed BOS this bar (real structural event). Swing breaks are only called
// a confirmed break when the broader EMA context is also reclaimed/flipped;
// otherwise they stay break-pressure events so local counter-swing breaks do not
// read as trend breaks. The break is judged against the trend that was in force
// BEFORE this bar — the latch above already flips on the breaking bar itself.
bosTrendDir = nz(trendDir[1])
rawBosUpBreak = bosTrendDir == 1 and not na(lastPL) and close < lastPL and close[1] >= lastPL
rawBosDnBreak = bosTrendDir == -1 and not na(lastPH) and close > lastPH and close[1] <= lastPH
bosContextOk = not requireContextFlip or (rawBosUpBreak and close < ema and emaSlope < 0) or (rawBosDnBreak and close > ema and emaSlope > 0)
// A swing break that stayed Break Pressure becomes a confirmed break on the bar the
// EMA context flips, as long as price still holds beyond that swing level and the
// level was not already consumed by an earlier confirmed break.
var float consumedPL = na
var float consumedPH = na
flipUpBreak = requireContextFlip and bosTrendDir == 1 and trendDir == -1 and not rawBosUpBreak and not na(lastPL) and close < lastPL and (na(consumedPL) or lastPL != consumedPL)
flipDnBreak = requireContextFlip and bosTrendDir == -1 and trendDir == 1 and not rawBosDnBreak and not na(lastPH) and close > lastPH and (na(consumedPH) or lastPH != consumedPH)
bosUpNow = (rawBosUpBreak and bosContextOk) or flipUpBreak
bosDnNow = (rawBosDnBreak and bosContextOk) or flipDnBreak
bosNow = bosUpNow or bosDnNow
bosPressureNow = (rawBosUpBreak or rawBosDnBreak) and not bosContextOk
if bosUpNow
consumedPL := lastPL
if bosDnNow
consumedPH := lastPH
// ============================================================================
// Composite risk
// ============================================================================
weightSum = wNear + wSfp + wStruct + wDiv
// Structure Erosion and Pivot Divergence exclude each other at the same pivot pair
// (erosion needs a lower high, divergence a higher high), so the reachable maximum
// is the weight sum minus the smaller of the two — scaling by it makes 100 reachable.
reachableMax = weightSum - math.min(wStruct, wDiv)
float riskRaw = 0.0
if trendDir != 0 and reachableMax > 0.0
riskRaw := math.min((sNearH * wNear + sSfpH * wSfp + sStructH * wStruct + sDivH * wDiv) / reachableMax * 100.0, 100.0)
risk = ta.ema(riskRaw, smoothLen)
// ============================================================================
// State / explanation
// ============================================================================
string state = "Quiet"
if trendDir == 0
state := "No trend"
else if bosNow
state := "Structure Broken"
else if bosPressureNow
state := "Break Pressure"
else if risk >= lvlCrit
state := "Critical"
else if risk >= lvlHigh
state := "Break Pressure"
else if risk >= lvlElev
state := "Watch"
string reason = "No clear evidence"
if bosNow
reason := "Break confirmed"
else if bosPressureNow
reason := "Local swing break"
else if sNearH > 0.7
reason := "Approaching break level"
else if sSfpH > 0.5
reason := "Failed breakout"
else if sStructH > 0.5
reason := "Structure eroding"
else if sDivH > 0.5
reason := "Momentum divergence"
dirTxt = trendDir == 1 ? "Uptrend at risk" : trendDir == -1 ? "Downtrend at risk" : "No trend context"
riskCol = f_riskCol(risk)
stateCol = state == "Structure Broken" or state == "Critical" ? cDarkRed : state == "Break Pressure" ? cRed : state == "Watch" ? cOrange : cNeutral
// ============================================================================
// Price chart overlay — drawn via force_overlay (this indicator lives in its own
// RSI pane). Break-level line, risk-shaded cushion zone, thin trend-context EMA.
// ============================================================================
hasCtx = trendDir != 0 and not na(breakLevel)
// Thin EMA trend-context line — neutral colour (direction is read from slope + the
// info text, never from a buy/sell-coloured line).
plot(showEma ? ema : na, "Trend Context", color=color.new(cNeutral, 25), linewidth=1, force_overlay=true)
// Break-level line + cushion zone. Plot the level whenever there is context so the
// zone fill always has an anchor; the LINE's visibility is controlled by its colour
// (na = hidden) independent of the zone.
pLevel = plot(hasCtx ? breakLevel : na, "Break Level", color=showLevel ? riskCol : na, linewidth=2, style=plot.style_linebr, force_overlay=true)
pPrice = plot(hasCtx ? close : na, "", color=color.new(color.gray, 100), display=display.none, editable=false, force_overlay=true)
// Zone fill intensifies as risk rises; only shown once risk is at least Watch.
zoneAlpha = f_clamp(92.0 - risk * 0.3, 70.0, 92.0)
fill(pLevel, pPrice, color=showZone and hasCtx and risk >= lvlElev ? color.new(riskCol, zoneAlpha) : na, title="Risk Zone")
// ============================================================================
// RSI pane (this pane) — the RSI with momentum-divergence lines drawn on it. A
// divergence is price-vs-momentum, so it is only readable where BOTH are visible:
// the RSI here and the price above, sharing the same time axis. When price makes a
// higher high while RSI makes a LOWER high (uptrend) — or price a lower low while
// RSI a higher low (downtrend) — a line marks the failing momentum: the earliest
// structural crack. Drawn in the warning (orange) risk band, never buy/sell.
// ============================================================================
plot(rsi, "RSI", color=color.new(#3179f5, 0), linewidth=1, format=format.price, precision=2)
hline(70, "RSI Overbought", color=color.new(color.gray, 70), linestyle=hline.style_dotted)
hline(50, "RSI Midline", color=color.new(color.gray, 85), linestyle=hline.style_dotted)
hline(30, "RSI Oversold", color=color.new(color.gray, 70), linestyle=hline.style_dotted)
bearDivEvent = trendDir == 1 and not na(ph) and not na(prevPricePH) and not na(prevRsiPH) and lastPricePH > prevPricePH and lastRsiPH < prevRsiPH
bullDivEvent = trendDir == -1 and not na(pl) and not na(prevPricePL) and not na(prevRsiPL) and lastPricePL < prevPricePL and lastRsiPL > prevRsiPL
if showDiv and barstate.isconfirmed and bearDivEvent and not na(prevPHbar)
line.new(prevPHbar, prevRsiPH, lastPHbar, lastRsiPH, xloc=xloc.bar_index, color=cOrange, width=2)
label.new(lastPHbar, lastRsiPH, "Div", xloc=xloc.bar_index, yloc=yloc.price, style=label.style_label_down,
color=color.new(cOrange, 8), textcolor=color.white, size=size.tiny,
tooltip="Bearish momentum divergence\nPrice: higher high — RSI: lower high (" + str.tostring(prevRsiPH, "#.0") + " → " + str.tostring(lastRsiPH, "#.0") + ")\nThe upthrust is weakening — the uptrend's structure is at risk.")
if showDiv and barstate.isconfirmed and bullDivEvent and not na(prevPLbar)
line.new(prevPLbar, prevRsiPL, lastPLbar, lastRsiPL, xloc=xloc.bar_index, color=cOrange, width=2)
label.new(lastPLbar, lastRsiPL, "Div", xloc=xloc.bar_index, yloc=yloc.price, style=label.style_label_up,
color=color.new(cOrange, 8), textcolor=color.white, size=size.tiny,
tooltip="Bullish momentum divergence\nPrice: lower low — RSI: higher low (" + str.tostring(prevRsiPL, "#.0") + " → " + str.tostring(lastRsiPL, "#.0") + ")\nThe down-thrust is weakening — the downtrend's structure is at risk.")
// ============================================================================
// Event labels — words on the chart, placed on the side that is at risk
// ============================================================================
watchEvent = ta.crossover(risk, lvlElev)
pressureEvent = ta.crossover(risk, lvlHigh) or ta.crossover(risk, lvlCrit)
resetEvent = ta.crossunder(risk, lvlElev)
string eventText = ""
string eventTip = ""
color eventColor = cNeutral
if bosNow
eventText := "Structure Broken"
eventColor := cDarkRed
eventTip := bosUpNow
? "Structure Broken\nUptrend structure broke: close below last swing low.\nThis confirms structural damage after risk was building."
: "Structure Broken\nDowntrend structure broke: close above last swing high.\nThis confirms structural damage after risk was building."
else if bosPressureNow
eventText := "Break Pressure"
eventColor := cRed
eventTip := rawBosUpBreak
? "Break Pressure\nLocal swing low broke, but trend context did not flip.\nTreat as pressure, not a confirmed trend break."
: "Break Pressure\nLocal swing high broke, but trend context did not flip.\nTreat as pressure, not a confirmed trend break."
else if pressureEvent
eventText := "Break Pressure"
eventColor := risk >= lvlCrit ? cDarkRed : cRed
eventTip := (risk >= lvlCrit ? "Critical\n" : "Break Pressure\n") + dirTxt + ": break pressure is active.\nReason: " + reason + ". Continuation is vulnerable."
else if watchEvent
eventText := "Watch"
eventColor := cOrange
eventTip := "Watch\n" + dirTxt + ": price is moving toward a structure-break condition.\nReason: " + reason + ". Watch the break level; this is not confirmation."
else if resetEvent
eventText := "Pressure Faded"
eventColor := cCalm
eventTip := "Pressure Faded\n" + dirTxt + ": break pressure faded.\nThe structure-break setup is off unless pressure rebuilds."
hasKeyEvent = eventText != ""
var int lastEventBar = na
// Only genuine confirmed structure events bypass the cooldown; threshold
// crossings (Watch / Break Pressure / Pressure Faded) respect it, so a High and
// a Critical crossover on adjacent bars no longer print two stacked labels.
eventReady = bosNow or bosPressureNow or na(lastEventBar) or bar_index - lastEventBar >= markerCooldown
// Anchor every event to the break-level line it concerns, so the label's pointer
// touches the line instead of floating in empty space. Pressure-building events
// hang just below the line, the relief event just above — opposite sides, both
// connected to the structure they describe.
bosLevel = rawBosUpBreak or flipUpBreak ? lastPL : rawBosDnBreak or flipDnBreak ? lastPH : na
evAnchor = (bosNow or bosPressureNow) and not na(bosLevel) ? bosLevel : not na(breakLevel) ? breakLevel : close
isRelief = eventText == "Pressure Faded"
evStyle = isRelief ? label.style_label_up : label.style_label_down
if showMarkers and barstate.isconfirmed and hasKeyEvent and eventReady
label.new(bar_index, evAnchor, eventText,
xloc=xloc.bar_index, yloc=yloc.price, style=evStyle, force_overlay=true,
color=color.new(eventColor, 8), textcolor=color.white, size=size.tiny,
tooltip=eventTip + "\nRisk: " + str.tostring(risk, "#.0") +
"\nNear " + str.tostring(sNearH * 100, "#.0") +
" · BOS " + str.tostring(sBosH * 100, "#.0") +
" · SFP " + str.tostring(sSfpH * 100, "#.0") +
" · Erode " + str.tostring(sStructH * 100, "#.0") +
" · Div " + str.tostring(sDivH * 100, "#.0"))
lastEventBar := bar_index
// ============================================================================
// Info label — one compact, mobile-friendly text box (not a table) carrying the
// full read: score, state, direction, break level, distance, reason and a
// mini-bar per evidence sensor. Recreated on the last bar; the previous one is
// deleted so no leak builds up on realtime ticks.
// ============================================================================
f_lblSize(s) =>
s == "Small" ? size.small : s == "Large" ? size.large : size.normal
var label infoLbl = na
if showLabel and barstate.islast
label.delete(infoLbl)
distTxt = na(distToBreak) ? "—" : str.tostring(distToBreak, "0.0") + " ATR"
lvlTxt = na(breakLevel) ? "—" : str.tostring(breakLevel, format.mintick)
txt = "Structure Break Risk " + str.tostring(risk, "#.0") + " · " + state + "\n" +
dirTxt + " · " + reason + "\n" +
"Break " + lvlTxt + " · " + distTxt + "\n" +
f_row("Near", sNearH * 100) + "\n" +
f_row("SFP", sSfpH * 100) + "\n" +
f_row("Erode", sStructH * 100) + "\n" +
f_row("Div", sDivH * 100) + "\n" +
f_row("BOS", sBosH * 100) + " (event)"
tip = "Structure Break Risk\n" +
"Score = pre-break structure pressure (0-100); colour = risk band.\n" +
"BOS is NOT mixed into the score — it is a separate confirmed event.\n" +
"Bars show each sensor's current contribution; Reason names the dominant one."
infoLbl := label.new(bar_index + labelOffset, close, txt,
xloc=xloc.bar_index, yloc=yloc.price, style=label.style_label_left, force_overlay=true,
color=color.new(color.white, 8), textcolor=stateCol,
size=f_lblSize(labelSize), textalign=text.align_left, tooltip=tip)
else if not showLabel and barstate.islast
label.delete(infoLbl)
// ============================================================================
// Debug — log on every state change (why it changed)
// ============================================================================
if barstate.isconfirmed and state != state[1]
log.info("SBR {0} -> {1} | risk={2} dir={3} reason={4} Near={5} BOS={6} SFP={7} Erode={8} Div={9}",
state[1], state, str.tostring(risk, "#.0"), dirTxt, reason,
str.tostring(sNearH, "#.00"), str.tostring(sBosH, "#.00"), str.tostring(sSfpH, "#.00"),
str.tostring(sStructH, "#.00"), str.tostring(sDivH, "#.00"))
// ============================================================================
// Alerts
// ============================================================================
bool alertsOnBarClose = input.bool(true, "Alerts only on bar close (confirmed)", group="Alerts")
bool alertOk = not alertsOnBarClose or barstate.isconfirmed
alertcondition(alertOk and (ta.crossover(risk, lvlHigh)), "SBR Break Pressure", "SBR · BREAK PRESSURE HIGH · {{ticker}} {{interval}}")
alertcondition(alertOk and (ta.crossover(risk, lvlCrit)), "SBR Critical", "SBR · RISK CRITICAL · {{ticker}} {{interval}}")
alertcondition(alertOk and (bosUpNow), "SBR Top Break", "SBR · TOP BREAK · {{ticker}} {{interval}}")
alertcondition(alertOk and (bosDnNow), "SBR Bottom Break", "SBR · BOTTOM BREAK · {{ticker}} {{interval}}")
Overview
Structure Break Risk plots an RSI oscillator with momentum divergence lines in a separate pane while projecting key break levels, risk cushion zones, and status labels on the price chart. Four weighted sensors monitor ATR proximity to break levels, swing sweeps, structure erosion, and pivot divergences. Settings allow customization of trend bias lengths, pivot detection periods, evidence weights, and alert thresholds.
Composite market structure break risk score from 0 to 100
RSI momentum pane with automatic divergence detection
Dynamic break levels and ATR risk cushion zones on price chart
Four weighted evidence sensors including SFP and structure erosion
The inputs you can change from the gear icon on the chart. Defaults are the author's.
Setting
Type
Default
Variable
Trend Bias Length
int
50
biasLen
Swing Pivot Length
int
5
pivLen
Near Break Level ATR Distance
float
1.5
nearAtr
RSI Length
int
14
rsiLen
Evidence Memory Bars
int
10
holdBars
Risk Smoothing
int
3
smoothLen
Weight: Near Break Level
float
25.0
wNear
Weight: Failed Breakout
float
20.0
wSfp
Weight: Structure Erosion
float
15.0
wStruct
Weight: Pivot Divergence
float
10.0
wDiv
State: Watch ≥
int
35
lvlElev
State: Break Pressure ≥
int
60
lvlHigh
Show all 24 settings
Setting
Type
Default
Variable
State: Critical ≥
int
80
lvlCrit
Show Divergence Lines
bool
true
showDiv
Show Trend Context Line
bool
true
showEma
Show Break-Level Line
bool
true
showLevel
Show Risk Zone
bool
true
showZone
Show Event Labels
bool
true
showMarkers
Event Label Cooldown
int
10
markerCooldown
Require Context Flip for Break
bool
true
requireContextFlip
Show Info Label
bool
true
showLabel
Label Offset (bars right)
int
5
labelOffset
Label Text Size
string
Normal
labelSize
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.
This indicator identifies Smart Money Concepts market structure across higher and lower timeframes directly on the main price chart. It detects Break of Structure, Market Structure Shifts, Order Blocks, and regular or inverse Fair Value Gaps. Entry signals are
This indicator combines a scaled Forecast Oscillator with a Point of Force state detector. The Forecast Oscillator calculates the percentage deviation of price from its linear regression curve and normalizes the output between 0 and 1, plotting a line in a sep
This indicator detects equal highs and lows by comparing prices within user-defined left and right bars using a tolerance percentage. It plots horizontal lines on the price chart for equal highs in one color and lows in another. A screener allows input of mult
This Pine Script indicator plots a ZigZag on the price chart. It identifies reversal points when price moves more than a set percentage from the last pivot. The script calculates forecasts using zigzag data and labels detected geometric patterns including harm
This indicator plots market structure on the price chart by identifying swing highs and lows. It connects these points with lines to visualize trend direction and detect Break of Structure (BOS) when price breaks a prior swing in the current trend. Change of C
This indicator implements the Fractal Model concept to plot market structure shifts, liquidity sweeps, and Change in State of Delivery (CISD) levels. It overlays custom higher-timeframe candle previews, open prices, and time markers directly on the price chart