Indicator · Pine Script v6 · On the chart

Vein: Exhaustion [WavesUnchained]

by casoon

Vein Exhaustion is a price-chart indicator that scores how stretched a directional move is before structure confirms a turn.

View on GitHub

GitHub stars9on the repository
Pine versionv6current
Lines of code454
Settings29inputs
LicenceMIT
Last updateyesterday27 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

454 lines of Pine Script v6 from indicators/trend_direction/vein/vein_exhaustion.pine, published under MIT. Keep the author's credit and licence lines when you reuse it.

vein_exhaustion.pine
//@version=6
// ============================================================================
// Vein Exhaustion [WavesUnchained]
// Version: 0.3.3
// Author: WavesUnchained
// Build: 2026-10-12
// ============================================================================
// Detects when a directional move is exhausted — before structure confirms it.
// Complements vein_trend (which detects reversals from structure events) by
// adding a second perspective: emotional/temporal overextension.
//
// Score layers (0–10, computed separately for bull and bear direction):
//   A  Time Exhaustion  (0–3) — consecutive directional bars, move duration
//   B  Overextension    (0–3) — EMA distance in ATR units + RSI/MFI extremes
//   C  Orderflow Proxy  (0–2) — absorption, capitulation/blow-off, exhaustion bars
//   D  Volatility State (0–2) — directional ATR spike + climax candle
//
// Markers:
//   ▼ score  Bull Exhaustion  — upward move overextended, potential top
//   ▲ score  Bear Exhaustion  — downward move overextended, potential bottom
//   ●        Absorption       — heavy selling/buying absorbed (cyan/orange)
//   ★        Capitulation     — panic low, potential exhaustion bottom (yellow)
//   ★        Blow-off         — buying climax, potential exhaustion top (yellow)
//
// Data Contract:
//   Price:     REQUIRED   chart symbol
//   Volume:    OPTIONAL   real trade volume only — Orderflow Proxy layer degrades to 0
//   OI:        NO
//   Reference: NO
//   Verdict:   CFD-degraded
//
// ============================================================================
indicator("Vein: Exhaustion [WavesUnchained]", "Vein Exhaust", overlay=true, max_labels_count=300)

// ============================================================================
// INPUTS
// ============================================================================

var gBase = "Base Indicators"
bool  autoTF      = input.bool(true,  "Auto EMA (adapt to timeframe)",  group=gBase, tooltip="Daily→EMA50  4H→EMA34  1H→EMA21  <1H→EMA20")
int   emaLen      = input.int(20,   "EMA Length (manual only)",         minval=1,   group=gBase)
int   rsiLen      = input.int(14,   "RSI Length",                       minval=1,   group=gBase)
int   mfiLen      = input.int(14,   "MFI Length",                       minval=1,   group=gBase)
int   atrLen      = input.int(14,   "ATR Length",                       minval=1,   group=gBase)
int   atrSmaLen   = input.int(20,   "ATR SMA Length",                   minval=5,   group=gBase, tooltip="Average ATR over this many bars — used to detect ATR spikes")
int   volLen      = input.int(20,   "Volume MA Length",                 minval=5,   group=gBase)

var gTime = "Time Exhaustion"
int   consWarn    = input.int(6,    "Consecutive Bars: Warning",  minval=3, maxval=20, group=gTime, tooltip="Signals start appearing after this many consecutive directional bars")
int   consHigh    = input.int(10,   "Consecutive Bars: High",     minval=5, maxval=30, group=gTime, tooltip="Full time exhaustion score after this many bars")

var gOverext = "Overextension"
float emaDistMed  = input.float(1.5, "EMA Distance Medium (ATR)", minval=0.5, maxval=3.0, step=0.1, group=gOverext, tooltip="Price distance from EMA in ATR multiples — medium overextension")
float emaDistHigh = input.float(2.5, "EMA Distance High (ATR)",   minval=1.0, maxval=5.0, step=0.1, group=gOverext, tooltip="Price distance from EMA in ATR multiples — high overextension")
float rsiExtreme  = input.float(72.0,"RSI Extreme Level",         minval=60,  maxval=85,  step=1,   group=gOverext, tooltip="RSI above this = bull extreme, below 100-this = bear extreme")
float mfiExtreme  = input.float(75.0,"MFI Extreme Level",         minval=60,  maxval=90,  step=1,   group=gOverext, tooltip="MFI above this = overbought, below 100-this = oversold")

var gOrderflow = "Orderflow Proxy"
float absVolMult  = input.float(1.5, "Absorption: Min Volume Multiple",  minval=1.0, maxval=4.0, step=0.1, group=gOrderflow, tooltip="Volume must exceed volAvg × this to detect absorption")
float absRangeMult= input.float(0.7, "Absorption: Max Range (ATR)",      minval=0.2, maxval=1.5, step=0.1, group=gOrderflow, tooltip="Candle range must be below ATR × this — absorption = high vol, small range")
float capVolMult  = input.float(2.2, "Capitulation: Min Volume Multiple", minval=1.5, maxval=5.0, step=0.1, group=gOrderflow, tooltip="Capitulation / blow-off bar requires volume > volAvg × this")
float capRangeMult= input.float(1.5, "Capitulation: Min Range (ATR)",    minval=1.0, maxval=3.0, step=0.1, group=gOrderflow, tooltip="Capitulation / blow-off bar requires range > ATR × this")

var gVol = "Volatility"
float atrSpikeMult= input.float(1.6, "ATR Spike Multiple",       minval=1.1, maxval=3.0, step=0.1, group=gVol, tooltip="ATR spike when current ATR > ATR_SMA × this")
float climaxRange = input.float(2.0, "Climax Bar Range (ATR)",   minval=1.0, maxval=4.0, step=0.1, group=gVol, tooltip="Climax bar requires range > ATR × this")
float climaxVol   = input.float(2.0, "Climax Bar Volume Multiple",minval=1.0, maxval=5.0, step=0.1, group=gVol, tooltip="Climax bar requires volume > volAvg × this")

var gSignal = "Signals"
float scoreThresh = input.float(5.5, "Min Score for Signal",  minval=3.0, maxval=10.0, step=0.5, group=gSignal)
int   cdBars      = input.int(5,     "Signal Cooldown (bars)", minval=1, maxval=30,              group=gSignal)

var gDisplay = "Display"
bool  showTable   = input.bool(true,  "Show Score Table",               group=gDisplay)
string tablePos   = input.string("bottom_right", "Table Position", options=["top_left","top_center","top_right","middle_left","middle_center","middle_right","bottom_left","bottom_center","bottom_right"], group=gDisplay)
bool  showExhaust = input.bool(true,  "Show Exhaustion Signals ▲▼",    group=gDisplay)
bool  showAbsorb  = input.bool(true,  "Show Absorption ●",              group=gDisplay)
bool  showCap     = input.bool(true,  "Show Capitulation ★",            group=gDisplay, tooltip="Shows capitulation (below bar) and blow-off (above bar) markers.")
bool  showBg      = input.bool(true,  "Show Background when Exhausted", group=gDisplay)

// ============================================================================
// BASE CALCULATIONS
// ============================================================================

int   tfSec     = timeframe.in_seconds()
int   effEmaLen = autoTF ? (tfSec >= 86400 ? 50 : tfSec >= 14400 ? 34 : tfSec >= 3600 ? 21 : 20) : emaLen

float ema20   = ta.ema(close, effEmaLen)
float rsi14   = ta.rsi(close, rsiLen)
float atr14   = ta.atr(atrLen)
float atrSma  = ta.sma(atr14, atrSmaLen)
float range_  = high - low

// Echtes Handelsvolumen? Nur "tick" schließt aus; "n/a" zählt, wenn der Feed Volumen liefert (DATA_VALIDITY §2).
bool volumeIsReal = (ta.cum(nz(volume)) > 0 and syminfo.volumetype != "tick") and not na(volume)

float mfi14   = volumeIsReal ? ta.mfi(hlc3, mfiLen) : na
float volAvg  = volumeIsReal ? ta.sma(volume, volLen) : na

// EMA distance in ATR units (signed: positive = above, negative = below)
float emaDist = atr14 > 0 ? (close - ema20) / atr14 : 0.0

// Candle anatomy
float bodySize  = math.abs(close - open)
float bodyRatio = range_ > 0 ? bodySize / range_ : 0.0
bool  isBullBar = close > open
bool  isBearBar = close < open

// Upper / lower wick ratio
float bodyTop   = math.max(open, close)
float bodyBot   = math.min(open, close)
float upperWick = range_ > 0 ? (high - bodyTop) / range_ : 0.0
float lowerWick = range_ > 0 ? (bodyBot - low)  / range_ : 0.0

// Score bar utility (10-char unicode progress bar)
scoreBar(float s) =>
    int n = int(math.round(math.min(s, 10.0)))
    string r = ""
    for i = 0 to 9
        r += i < n ? "█" : "░"
    r

// ============================================================================
// A — TIME EXHAUSTION
// ============================================================================

// Count consecutive directional bars with a 1-bar grace period: a single
// non-directional close keeps the counter, a second one resets it. The grace
// re-arms only after ≥ 2 directional bars since the last grace or reset, so a
// zig-zag (up/down/up/down) cannot keep both counters growing.
var int consBull       = 0
var int consBear       = 0
var int bullSinceGrace = 0
var int bearSinceGrace = 0
if close > close[1]
    consBull       += 1
    bullSinceGrace += 1
else if close[1] > close[2] and bullSinceGrace >= 2
    bullSinceGrace := 0
else
    consBull       := 0
    bullSinceGrace := 0
if close < close[1]
    consBear       += 1
    bearSinceGrace += 1
else if close[1] < close[2] and bearSinceGrace >= 2
    bearSinceGrace := 0
else
    consBear       := 0
    bearSinceGrace := 0

// Bull time score: how many consecutive up-closes
float bullTimeScore = 0.0
if consBull >= consHigh
    bullTimeScore := 3.0
else if consBull >= consWarn + 2
    bullTimeScore := 2.0
else if consBull >= consWarn
    bullTimeScore := 1.0

// Bear time score
float bearTimeScore = 0.0
if consBear >= consHigh
    bearTimeScore := 3.0
else if consBear >= consWarn + 2
    bearTimeScore := 2.0
else if consBear >= consWarn
    bearTimeScore := 1.0

// ============================================================================
// B — OVEREXTENSION
// ============================================================================

// Bull overextension: price far above EMA
// MFI weight is reassigned to RSI when volume is not real — keeps the 3.0
// ceiling reachable instead of quietly shrinking it on tick-volume symbols.
float bullEmaScore = emaDist >= emaDistHigh ? 2.0 : emaDist >= emaDistMed ? 1.0 : 0.0
float bullRsiScore = rsi14 >= rsiExtreme ? (volumeIsReal ? 0.5 : 1.0) : 0.0
float bullMfiScore = volumeIsReal and mfi14 >= mfiExtreme ? 0.5 : 0.0
float bullOverextScore = math.min(bullEmaScore + bullRsiScore + bullMfiScore, 3.0)

// Bear overextension: price far below EMA
float bearEmaScore = emaDist <= -emaDistHigh ? 2.0 : emaDist <= -emaDistMed ? 1.0 : 0.0
float bearRsiScore = rsi14 <= (100 - rsiExtreme) ? (volumeIsReal ? 0.5 : 1.0) : 0.0
float bearMfiScore = volumeIsReal and mfi14 <= (100 - mfiExtreme) ? 0.5 : 0.0
float bearOverextScore = math.min(bearEmaScore + bearRsiScore + bearMfiScore, 3.0)

// ============================================================================
// C — ORDERFLOW PROXY
// ============================================================================

// Orderflow Proxy (absorption/capitulation/blow-off/exhaustion bars) has no price-only
// substitute — it is defined BY the volume spike, not merely confirmed by it.
// All six conditions fail closed (never fire) when volume is not real,
// rather than reinterpreting a plain range/wick pattern as an orderflow event.

// Bear-side absorption: heavy selling is being absorbed
// → bearish bar + high volume + small range = buyers stepping in at lows
bool isBearAbsorption = volumeIsReal and isBearBar and
     volume >= volAvg * absVolMult and
     range_ <= atr14 * absRangeMult

// Bull-side absorption: heavy buying being absorbed at tops
// → bullish bar + high volume + small range = sellers stepping in at highs
bool isBullAbsorption = volumeIsReal and isBullBar and
     volume >= volAvg * absVolMult and
     range_ <= atr14 * absRangeMult

// Capitulation / Panic selling: aggressive selling exhausts sellers
// → bearish bar + very high volume + large range + closes near low
bool isCapitulation = volumeIsReal and isBearBar and
     volume >= volAvg * capVolMult and
     range_ >= atr14 * capRangeMult and
     lowerWick <= 0.2

// Blow-off / Panic buying: aggressive buying exhausts buyers (mirror of capitulation)
// → bullish bar + very high volume + large range + closes near high
bool isBlowOff = volumeIsReal and isBullBar and
     volume >= volAvg * capVolMult and
     range_ >= atr14 * capRangeMult and
     upperWick <= 0.2

// Distribution bar at top: price pushed up but sellers dominate the close
// → bearish bar + upper wick dominates + high volume + overextended
bool isBullExhaustionBar = volumeIsReal and isBearBar and
     upperWick >= 0.4 and
     volume >= volAvg * 1.5 and
     emaDist >= emaDistMed

// Accumulation bar at bottom: price pushed down but buyers dominate the close (mirror of distribution)
// → bullish bar + lower wick dominates + high volume + overextended below
bool isBearExhaustionBar = volumeIsReal and isBullBar and
     lowerWick >= 0.4 and
     volume >= volAvg * 1.5 and
     emaDist <= -emaDistMed

// Bear exhaustion orderflow: absorption, accumulation bar or capitulation signal potential bottom
float bearAccumScore  = isBearExhaustionBar ? 1.5 : 0.0
float bearAbsorbScore = isBearAbsorption    ? 1.5 : 0.0
float bearCapScore    = isCapitulation      ? 2.0 : 0.0
float bearFlowScore   = math.min(bearAccumScore + bearAbsorbScore + bearCapScore, 2.0)

// Bull exhaustion orderflow: distribution, bull-side absorption or blow-off signal potential top
float bullDistScore    = isBullExhaustionBar ? 1.5 : 0.0
float bullAbsorbScore  = isBullAbsorption    ? 1.5 : 0.0
float bullBlowOffScore = isBlowOff           ? 2.0 : 0.0
float bullFlowFinal    = math.min(bullDistScore + bullAbsorbScore + bullBlowOffScore, 2.0)

// ============================================================================
// D — VOLATILITY STATE (directional)
// ============================================================================

// ATR spike is only relevant in the direction of the exhausted move
bool isAtrSpike = atr14 >= atrSma * atrSpikeMult

// Climax bar's volume confirmation degrades to pass-through (range-only
// extreme bar) when volume is not real, so the Volatility layer keeps
// functioning on price data alone instead of losing its climax component.
bool isBullClimaxBar = range_ >= atr14 * climaxRange and (volumeIsReal ? volume >= volAvg * climaxVol : true) and isBullBar
bool isBearClimaxBar = range_ >= atr14 * climaxRange and (volumeIsReal ? volume >= volAvg * climaxVol : true) and isBearBar

// Bull exhaustion volatility: spike/climax in an upward move
float bullVolScore = (isAtrSpike and isBullBar ? 1.0 : 0.0) + (isBullClimaxBar ? 1.0 : 0.0)

// Bear exhaustion volatility: spike/climax in a downward move
float bearVolScore = (isAtrSpike and isBearBar ? 1.0 : 0.0) + (isBearClimaxBar ? 1.0 : 0.0)

// ============================================================================
// COMPOSITE SCORES
// ============================================================================

// Orderflow Proxy (max 2.0) has no price-only substitute and fails closed
// without real volume — rescale onto the reduced max so the 0-10 scale (and
// scoreThresh) stays comparable instead of the composite silently sinking.
float bullExhaustMax = 3.0 + 3.0 + (volumeIsReal ? 2.0 : 0.0) + 2.0
float bearExhaustMax = 3.0 + 3.0 + (volumeIsReal ? 2.0 : 0.0) + 2.0

float bullExhaustSum = bullTimeScore + bullOverextScore + bullFlowFinal + bullVolScore
float bearExhaustSum = bearTimeScore + bearOverextScore + bearFlowScore + bearVolScore

float bullExhaustScore = bullExhaustMax > 0 ? bullExhaustSum / bullExhaustMax * 10.0 : 0.0
float bearExhaustScore = bearExhaustMax > 0 ? bearExhaustSum / bearExhaustMax * 10.0 : 0.0

// Cap at 10
bullExhaustScore := math.min(bullExhaustScore, 10.0)
bearExhaustScore := math.min(bearExhaustScore, 10.0)

// ============================================================================
// SIGNALS
// ============================================================================

var int lastBullExBar = na
var int lastBearExBar = na
bool bullCdOk = na(lastBullExBar) or (bar_index - lastBullExBar) >= cdBars
bool bearCdOk = na(lastBearExBar) or (bar_index - lastBearExBar) >= cdBars

// Fire when score crosses threshold (edge detection)
bool bullExhaustRaw = bullExhaustScore >= scoreThresh and bullCdOk
bool bearExhaustRaw = bearExhaustScore >= scoreThresh and bearCdOk

bool bullExhaustSignal = bullExhaustRaw and not bullExhaustRaw[1]
bool bearExhaustSignal = bearExhaustRaw and not bearExhaustRaw[1]

if bullExhaustSignal
    lastBullExBar := bar_index
if bearExhaustSignal
    lastBearExBar := bar_index

// ============================================================================
// DISPLAY
// ============================================================================

// Background — two-tier: subtle when stretched, stronger when extreme
color bullBgCol = na
if showBg and bullExhaustScore >= 8.0
    bullBgCol := color.new(#d50000, 82)
else if showBg and bullExhaustScore >= scoreThresh
    bullBgCol := color.new(#d50000, 92)
color bearBgCol = na
if showBg and bearExhaustScore >= 8.0
    bearBgCol := color.new(#00c853, 82)
else if showBg and bearExhaustScore >= scoreThresh
    bearBgCol := color.new(#00c853, 92)
bgcolor(bullBgCol)
bgcolor(bearBgCol)

// Exhaustion signal labels with score value
if showExhaust and bullExhaustSignal
    label.new(bar_index, na, "▼ " + str.tostring(bullExhaustScore, "#.#"),
              yloc=yloc.abovebar, style=label.style_label_down,
              color=color.new(#d50000, 10), textcolor=color.white, size=size.small)

if showExhaust and bearExhaustSignal
    label.new(bar_index, na, "▲ " + str.tostring(bearExhaustScore, "#.#"),
              yloc=yloc.belowbar, style=label.style_label_up,
              color=color.new(#00c853, 10), textcolor=color.white, size=size.small)

// Bear-side absorption (buyers stepping in during selling)
plotshape(showAbsorb and isBearAbsorption, "Bear Absorption ●", shape.circle, location.belowbar, color.new(#00bcd4, 20), size=size.tiny)

// Bull-side absorption (sellers stepping in during buying)
plotshape(showAbsorb and isBullAbsorption, "Bull Absorption ●", shape.circle, location.abovebar, color.new(#ff6f00, 20), size=size.tiny)

// Capitulation (panic low — potential exhaustion bottom)
plotshape(showCap and isCapitulation, "Capitulation ★", shape.cross, location.belowbar, color.new(#ffd600, 0), size=size.small)

// Blow-off (buying climax — potential exhaustion top)
plotshape(showCap and isBlowOff, "Blow-off ★", shape.cross, location.abovebar, color.new(#ffd600, 0), size=size.small)

// EMA reference — labeled with effective length
plot(ema20, "EMA", color.new(#2196f3, 50), linewidth=1)

// ============================================================================
// TABLE
// ============================================================================

if showTable and barstate.islast
    string bullState = "NORMAL"
    if bullExhaustScore >= 8.0
        bullState := "EXTREME"
    else if bullExhaustScore >= scoreThresh
        bullState := "EXHAUSTED"
    else if bullExhaustScore >= 3.0
        bullState := "STRETCHED"
    string bearState = "NORMAL"
    if bearExhaustScore >= 8.0
        bearState := "EXTREME"
    else if bearExhaustScore >= scoreThresh
        bearState := "EXHAUSTED"
    else if bearExhaustScore >= 3.0
        bearState := "STRETCHED"

    color bullStateBg = bullExhaustScore >= 8.0 ? color.new(#d50000, 10) : bullExhaustScore >= scoreThresh ? color.new(#d50000, 40) : bullExhaustScore >= 3.0 ? color.new(#ff6f00, 50) : color.new(color.gray, 70)
    color bearStateBg = bearExhaustScore >= 8.0 ? color.new(#00c853, 10) : bearExhaustScore >= scoreThresh ? color.new(#00c853, 40) : bearExhaustScore >= 3.0 ? color.new(#ffd600, 50) : color.new(color.gray, 70)

    color tc = color.new(color.gray, 10)
    color lc = color.new(color.gray, 20)

    var table t = table.new(tablePos, 3, 9,
         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)

    // Header
    table.cell(t, 0, 0, "Vein Exhaustion", text_color=lc, text_size=size.small, bgcolor=color.new(color.gray, 90))
    table.cell(t, 1, 0, "Bull ▼",          text_color=#d50000, text_size=size.small, bgcolor=color.new(color.gray, 90))
    table.cell(t, 2, 0, "Bear ▲",          text_color=#00c853, text_size=size.small, bgcolor=color.new(color.gray, 90))

    // Status — white on red/green/orange, dark on yellow/gray
    color bullStateTextCol = bullExhaustScore >= 3.0 ? color.white : color.new(color.gray, 20)
    color bearStateTextCol = bearExhaustScore >= scoreThresh ? color.white : color.new(color.gray, 20)
    table.cell(t, 0, 1, "Status",    text_color=lc,             text_size=size.small)
    table.cell(t, 1, 1, bullState,   text_color=bullStateTextCol, text_size=size.small, bgcolor=bullStateBg)
    table.cell(t, 2, 1, bearState,   text_color=bearStateTextCol, text_size=size.small, bgcolor=bearStateBg)

    // Total score with bar
    table.cell(t, 0, 2, "Score",     text_color=lc, text_size=size.small)
    table.cell(t, 1, 2, str.tostring(bullExhaustScore, "#.#") + "  " + scoreBar(bullExhaustScore), text_color=#d50000, text_size=size.small)
    table.cell(t, 2, 2, str.tostring(bearExhaustScore, "#.#") + "  " + scoreBar(bearExhaustScore), text_color=#00c853, text_size=size.small)

    // Time
    table.cell(t, 0, 3, "Time",      text_color=lc, text_size=size.small)
    table.cell(t, 1, 3, str.tostring(bullTimeScore, "#.#") + "  " + scoreBar(bullTimeScore * 10 / 3), text_color=lc, text_size=size.small)
    table.cell(t, 2, 3, str.tostring(bearTimeScore, "#.#") + "  " + scoreBar(bearTimeScore * 10 / 3), text_color=lc, text_size=size.small)

    // Overextension
    table.cell(t, 0, 4, "Overext",   text_color=lc, text_size=size.small)
    table.cell(t, 1, 4, str.tostring(bullOverextScore, "#.#") + "  " + scoreBar(bullOverextScore * 10 / 3), text_color=lc, text_size=size.small)
    table.cell(t, 2, 4, str.tostring(bearOverextScore, "#.#") + "  " + scoreBar(bearOverextScore * 10 / 3), text_color=lc, text_size=size.small)

    // Orderflow
    table.cell(t, 0, 5, "Orderflow", text_color=lc, text_size=size.small)
    table.cell(t, 1, 5, str.tostring(bullFlowFinal, "#.#") + "  " + scoreBar(bullFlowFinal * 5), text_color=lc, text_size=size.small)
    table.cell(t, 2, 5, str.tostring(bearFlowScore, "#.#") + "  " + scoreBar(bearFlowScore * 5), text_color=lc, text_size=size.small)

    // Volatility (now directional)
    table.cell(t, 0, 6, "Volatility", text_color=lc, text_size=size.small)
    table.cell(t, 1, 6, str.tostring(bullVolScore, "#.#") + "  " + scoreBar(bullVolScore * 5), text_color=lc, text_size=size.small)
    table.cell(t, 2, 6, str.tostring(bearVolScore, "#.#") + "  " + scoreBar(bearVolScore * 5), text_color=lc, text_size=size.small)

    // Orderflow events — separate bull / bear
    string ofBullStr = isBlowOff        ? "BLOW-OFF"     : isBullAbsorption ? "ABSORPTION" : isBullExhaustionBar ? "DISTRIBUTION" : isBullClimaxBar ? "CLIMAX" : "—"
    string ofBearStr = isCapitulation   ? "CAPITULATION" : isBearAbsorption ? "ABSORPTION" : isBearExhaustionBar ? "ACCUMULATION" : isBearClimaxBar ? "CLIMAX" : "—"
    color  ofBullBg      = isBlowOff      ? color.new(#ffd600, 30) : isBullAbsorption ? color.new(#ff6f00, 40) : color.new(color.gray, 70)
    color  ofBearBg      = isCapitulation ? color.new(#ffd600, 30) : isBearAbsorption ? color.new(#00bcd4, 40) : color.new(color.gray, 70)
    color  ofBullTextCol = isBlowOff      ? lc : isBullAbsorption ? color.white : lc
    color  ofBearTextCol = isCapitulation ? lc : isBearAbsorption ? color.white : lc
    table.cell(t, 0, 7, "Event",     text_color=lc,          text_size=size.small)
    table.cell(t, 1, 7, ofBullStr,   text_color=ofBullTextCol, text_size=size.small, bgcolor=ofBullBg)
    table.cell(t, 2, 7, ofBearStr,   text_color=ofBearTextCol, text_size=size.small, bgcolor=ofBearBg)

    // Volume validity — Orderflow Proxy layer degrades to 0 when not real
    string volValidStr = volumeIsReal ? "real" : syminfo.volumetype + " — degraded"
    color  volValidBg   = volumeIsReal ? color.new(color.gray, 80) : color.new(#ff6f00, 70)
    color  volValidTc   = volumeIsReal ? lc : color.white
    table.cell(t, 0, 8, "Volume",    text_color=lc,        text_size=size.small)
    table.cell(t, 1, 8, volValidStr, text_color=volValidTc, bgcolor=volValidBg, text_size=size.small)
    table.cell(t, 2, 8, "",          text_color=volValidTc, bgcolor=volValidBg, text_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 (bullExhaustSignal),  "Vein Exhaustion: Bull Exhausted ▼",  "VEIN-EXH · EXHAUSTION UP · {{ticker}} {{interval}}")
alertcondition(alertOk and (bearExhaustSignal),  "Vein Exhaustion: Bear Exhausted ▲",  "VEIN-EXH · EXHAUSTION DOWN · {{ticker}} {{interval}}")
alertcondition(alertOk and (isBearAbsorption),   "Vein Exhaustion: Bear Absorption ●", "VEIN-EXH · BEAR ABSORPTION · {{ticker}} {{interval}}")
alertcondition(alertOk and (isBullAbsorption),   "Vein Exhaustion: Bull Absorption ●", "VEIN-EXH · BULL ABSORPTION · {{ticker}} {{interval}}")
alertcondition(alertOk and (isCapitulation),     "Vein Exhaustion: Capitulation ★",    "VEIN-EXH · CAPITULATION · {{ticker}} {{interval}}")
alertcondition(alertOk and (isBlowOff),          "Vein Exhaustion: Blow-off ★",        "VEIN-EXH · BLOW-OFF · {{ticker}} {{interval}}")

Overview

Vein: Exhaustion [WavesUnchained] builds separate bull and bear scores from consecutive directional bars, EMA distance in ATR units, RSI and MFI extremes, volume-based absorption or capitulation, and ATR spikes with climax bars. A marker appears when the score clears a threshold, after a cooldown. Users can change lengths, extremes, volume multiples, and whether the score table, markers, and background show. Volume-dependent layers drop to zero when volume is not real trade volume. It does not place trades.

Details

Published by

casoon on GitHub

Type

Indicator, Pine Script v6

Draws

On the price chart

Category

Trend, Oscillators & momentum, Volatility & bands, Volume

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
Auto EMA (adapt to timeframe)booltrueautoTF
EMA Length (manual only)int20emaLen
RSI Lengthint14rsiLen
MFI Lengthint14mfiLen
ATR Lengthint14atrLen
ATR SMA Lengthint20atrSmaLen
Volume MA Lengthint20volLen
Consecutive Bars: Warningint6consWarn
Consecutive Bars: Highint10consHigh
EMA Distance Medium (ATR)float1.5emaDistMed
EMA Distance High (ATR)float2.5emaDistHigh
RSI Extreme Levelfloat72.0rsiExtreme
Show all 29 settings
SettingTypeDefaultVariable
MFI Extreme Levelfloat75.0mfiExtreme
Absorption: Min Volume Multiplefloat1.5absVolMult
Absorption: Max Range (ATR)float0.7absRangeMult
Capitulation: Min Volume Multiplefloat2.2capVolMult
Capitulation: Min Range (ATR)float1.5capRangeMult
ATR Spike Multiplefloat1.6atrSpikeMult
Climax Bar Range (ATR)float2.0climaxRange
Climax Bar Volume Multiplefloat2.0climaxVol
Min Score for Signalfloat5.5scoreThresh
Signal Cooldown (bars)int5cdBars
Show Score TablebooltrueshowTable
Table Positionstringbottom_righttablePos
Show Exhaustion Signals ▲▼booltrueshowExhaust
Show Absorption ●booltrueshowAbsorb
Show Capitulation ★booltrueshowCap
Show Background when ExhaustedbooltrueshowBg
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

The Momentum Setup is a multi-indicator visual tool designed for trend and momentum analysis directly on the price chart. It combines Relative Strength Index (RSI), Money Flow Index (MFI), Commodity Channel Index (CCI), Supertrend, and a 250-bar percentage cha

Indicator v5 Oscillators & momentumTrend
Updated 1 year ago · MPL-2.0 GitHub repository of Momentum Setup - Ankush Bajaj Momentum Investing Setup

Aroon-based Strategy

by capissimo
114 stars

This indicator overlay generates buy and sell signals based on Aroon extreme levels combined with optional volume or volatility filters. It calculates Aroon Up and Aroon Down lines using localized high and low lookbacks, generating trade entries when Aroon rea

Indicator v4 TrendOscillators & momentum
Updated 1 year ago · None stated GitHub repository of Aroon-based Strategy
92 stars

This indicator plots a volume-weighted average price of the typical price on the chart and colors that line by ADX strength. It draws a standard-deviation envelope around VWAP and tints the fill green when plus DI leads and red when minus DI leads. Triangle ma

Indicator v5 TrendOscillators & momentum
Updated 8 weeks ago · Custom licence GitHub repository of Moon Dev VWAP x ADX Explosion
7 stars

The MACD RSI EMA Aggressive indicator generates momentum and trend-following signals on the price chart. It combines a MACD crossover or crossunder with an RSI threshold filter, price location relative to an Exponential Moving Average, volume expansion above a

Indicator v5 TrendOscillators & momentum
Updated 3 months ago · None stated GitHub repository of MACD RSI EMA Aggressive + ATR SLTP

Capital41 Fusion Scalp

by damianpitt
6 stars

Capital41 Fusion Scalp is an overlay indicator that identifies high-confluence setups by combining four core conditions: EMA trend direction, RSI momentum, Williams %R levels, and ATR with volume expansion. Bullish and bearish signals are plotted on the chart

Indicator v6 TrendOscillators & momentum
Updated 8 weeks ago · MIT GitHub repository of Capital41 Fusion Scalp

RitstaR-Algo

by ritstar
3 stars

This overlay indicator scores each confirmed bar from zero to six using fast, slow, and trend EMAs, higher-timeframe bias, RSI, MACD, ATR, and volume. It plots buy and sell labels when the score meets the minimum entry threshold and exit labels when the opposi

Indicator v5 TrendOscillators & momentum
Updated 8 months ago · None stated GitHub repository of RitstaR-Algo

More indicators

Trade Your Charts With a Regulated Broker

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

Compare brokers