Indicator · Pine Script v6 · Own pane

Market Tail Risk Detector (Quiet → Explosive)

by major

The Market Tail Risk Detector evaluates market fragility and compression to identify environments preceding volatile price moves.

View on GitHub

Repaints

GitHub stars2on the repository
Pine versionv6current
Lines of code380
Settings10inputs
LicenceApache-2.0
Last update3 months ago29 Jul 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

380 lines of Pine Script v6 from market_fragility/market_fragility.pine, published under Apache-2.0. Keep the author's credit and licence lines when you reuse it.

market_fragility.pine
//@version=6
indicator('Market Tail Risk Detector (Quiet → Explosive)', overlay = false, max_bars_back = 600)

// ═══════════════════════════════════════════════════════════════
// MARKET TAIL RISK DETECTOR - QUIET PERIODS → EXPLOSIVE MOVES
// ═══════════════════════════════════════════════════════════════
//
// PURPOSE: Identify stocks likely to have BIGGER moves than most people expect
// Detects tail risk building during quiet periods - when complacency is high
// and the market is vulnerable to regime changes and outsized volatility.
//
// CORE CONCEPT - THE VOLATILITY PARADOX:
// When volatility is low AND stable for extended periods:
// - Market participants become complacent (VIX low, options cheap)
// - Liquidity providers assume predictable ranges
// - Position sizing increases (leverage builds)
// - Hedges are removed (insurance seems unnecessary)
// → This creates FRAGILITY: Small shocks cause disproportionate moves
//
// WHAT THIS INDICATOR DOES:
// 🎯 PRIMARY SIGNALS (70% of score):
// 1. Volatility of Volatility (VoV) - Detects stable/predictable volatility (complacency)
// 2. Realized Volatility Percentile - Identifies extremely quiet periods
// 3. Compression Duration - Tracks HOW LONG volatility has been suppressed
// 4. Volume Decline - Detects liquidity drain (amplifies tail moves)
//
// 📊 SUPPORTING SIGNALS (30% of score):
// 5. Technical compression (TTM Squeeze, Bollinger Bands, ATR)
// 6. Classic breakout patterns (NR7, inside days)
// 7. Momentum indicators (RSI, MACD, ADX flatness)
//
// WHAT THIS INDICATOR DOES NOT DO:
// ❌ Predict direction of breakout (up or down)
// ❌ Generate buy/sell signals
// ❌ Identify trend direction
// ❌ Work as a standalone trading system
// ❌ Predict TIMING of the move (only that tail risk is elevated)
//
// USAGE (Tail Risk Score: 0-100):
// - Low scores (0-40): Normal market, typical volatility expectations
// - High scores (60+): Elevated tail risk, expect bigger moves than consensus
// - Extreme scores (80+): Maximum tail risk, complacency extreme, regime change likely
//
// 💡 USE CASES:
// 1. Position sizing: REDUCE size when tail risk is high (protect from gaps/shocks)
// 2. Options strategy: Buy volatility (straddles/strangles) when VoV is low
// 3. Risk management: Widen stops or use options hedges instead of tight stops
// 4. Opportunity identification: Watch for catalysts when score is high (earnings, news)
// 5. Avoid bad entries: Don't initiate new positions during extreme compression
//
// 🔬 KEY INSIGHT:
// This isn't about predicting normal 2-3% moves. It's about identifying when
// conditions are ripe for 5-10%+ moves that catch the market off-guard.
// Think: Surprise earnings gaps, sudden sector rotations, flash crashes, etc.
//
// ═══════════════════════════════════════════════════════════════

// INPUTS - Timeframe selection
useCustomTimeframe = input.bool(false, 'Use Custom Timeframe', group = 'Timeframe')
customTimeframe = input.timeframe('W', 'Timeframe', group = 'Timeframe')

// INPUTS - Display settings
tableTextSize = input.string('Small', 'Info Table Text Size', options = ['Auto', 'Tiny', 'Small', 'Normal', 'Large', 'Huge'], group = 'Display')
verboseTable = input.bool(false, 'Verbose Table (Show All Metrics)', group = 'Display')

// INPUTS - Focused on compression and volatility
rvLen = input.int(20, 'Realized Vol Period', minval = 10, group = 'Volatility')
atrLen = input.int(14, 'ATR Period', minval = 5, group = 'Volatility')
compressionLen = input.int(30, 'Compression Detection Period', group = 'Compression')
bbLen = input.int(20, 'Bollinger Band Length', group = 'Compression')
kcLen = input.int(20, 'Keltner Channel Length', group = 'Compression')
kcMult = input.float(1.5, 'Keltner Multiplier', group = 'Compression')

// Select the appropriate timeframe
selectedTimeframe = useCustomTimeframe ? customTimeframe : timeframe.period

// Convert table text size string to size constant
getTextSize(sizeStr) =>
    sizeStr == 'Auto' ? size.auto : sizeStr == 'Tiny' ? size.tiny : sizeStr == 'Small' ? size.small : sizeStr == 'Normal' ? size.normal : sizeStr == 'Large' ? size.large : size.huge

textSize = getTextSize(tableTextSize)

// ═══════════════════════════════════════════════════════════════
// CALCULATION FUNCTION - Called in selected timeframe context
// ═══════════════════════════════════════════════════════════════

calculateFragility() =>
    // ═══════════════════════════════════════════════════════════════
    // 📊 SECTION 1: REALIZED VOLATILITY - THE GROUND TRUTH
    // ═══════════════════════════════════════════════════════════════
    // 📐 Calculate actual market volatility using log returns (more accurate than simple returns)
    // 📈 Annualize it to get a percentage comparable across timeframes
    logReturns = math.log(close / close[1])
    realizedVol = ta.stdev(logReturns, rvLen) * math.sqrt(252) * 100

    // 📍 Where does current volatility rank in the past year? (252 trading days)
    // ⚠️ RV Percentile below 20% = volatility in bottom quintile = tail risk!
    lookback = 252
    rvHigh = ta.highest(realizedVol, lookback)
    rvLow = ta.lowest(realizedVol, lookback)
    rvRange = math.max(rvHigh - rvLow, 1e-6)
    rvPercentileRaw = (realizedVol - rvLow) / rvRange * 100
    rvPercentile = math.max(math.min(rvPercentileRaw, 100), 0)

    // 🎯 Volatility of volatility - the "second derivative of chaos"
    // ⭐ MOST IMPORTANT: When vol is stable AND low, market is vulnerable to regime change
    volOfVol = ta.stdev(realizedVol, 20)
    vovHigh = ta.highest(volOfVol, lookback)
    vovLow = ta.lowest(volOfVol, lookback)
    vovRange = math.max(vovHigh - vovLow, 1e-6)
    vovPercentileRaw = (volOfVol - vovLow) / vovRange * 100
    vovPercentile = math.max(math.min(vovPercentileRaw, 100), 0)

    // ═══════════════════════════════════════════════════════════════
    // 🗜️ SECTION 2: COMPRESSION METRICS - PRESSURE BUILDING
    // ═══════════════════════════════════════════════════════════════
    // ⚡ Pre-calculate common values to avoid redundant calculations
    closeInv = 100.0 / close  // Inverse to convert divisions to multiplications

    // 📏 ATR Compression: When daily range drops 30% below its 50-day average
    // 💤 Markets can't stay "too quiet" indefinitely
    atr = ta.atr(atrLen)
    atrPercent = atr * closeInv
    atrMA = ta.sma(atrPercent, 50)
    atrCompression = atrPercent < atrMA * 0.7

    // 📉 Bollinger Band Squeeze: Classic John Bollinger compression indicator
    // 🎈 When BB width is less than half its historical average, expect expansion
    basis = ta.sma(close, bbLen)
    bbDev = ta.stdev(close, bbLen) * 2
    bbUpper = basis + bbDev
    bbLower = basis - bbDev
    bbWidth = (bbUpper - bbLower) / basis * 100
    bbHistoricalWidth = ta.sma(bbWidth, 100)
    bbSqueeze = bbWidth < bbHistoricalWidth * 0.5

    // 💎 TTM Squeeze: Most powerful compression signal
    // 🔥 When Bollinger Bands squeeze inside Keltner Channels = extreme compression
    kcBasis = ta.ema(close, kcLen)
    // Reuse ATR if periods match, otherwise calculate
    kcRange = (kcLen == atrLen ? atr : ta.atr(kcLen)) * kcMult
    kcUpper = kcBasis + kcRange
    kcLower = kcBasis - kcRange
    ttmSqueeze = bbLower > kcLower and bbUpper < kcUpper

    // Multi-timeframe range compression
    // When ranges compress across multiple timeframes = systematic compression
    rangeDaily = (high - low) * closeInv
    highLow5 = ta.highest(high, 5) - ta.lowest(low, 5)
    highLow20 = ta.highest(high, 20) - ta.lowest(low, 20)
    range5Day = highLow5 * closeInv
    range20Day = highLow20 * closeInv
    rangeCompression = rangeDaily < ta.sma(rangeDaily, 30) * 0.6
    multiCompression = range5Day < ta.sma(range5Day, 50) * 0.7 and range20Day < ta.sma(range20Day, 50) * 0.7

    // ═══════════════════════════════════════════════════════════════
    // 📐 SECTION 3: PATTERN RECOGNITION - SETUPS THAT EXPLODE
    // ═══════════════════════════════════════════════════════════════
    // 🕯️ Doji at trend extremes: Indecision after directional move = reversal or continuation setup
    isDoji = math.abs(close - open) < (high - low) * 0.1
    sma50 = ta.sma(close, 50)  // Cache SMA for reuse
    trendUp = close > sma50 and close[1] > close[2]
    trendDown = close < sma50 and close[1] < close[2]
    dojiReversal = isDoji and (trendUp or trendDown)

    // 📦 Inside days: Price action contained within previous bar = coiling
    // 🔒 Multiple inside days = extreme coiling, big move coming
    insideDay = high < high[1] and low > low[1]
    multipleInsideDays = insideDay and insideDay[1]

    // 📊 NR7/NR4: Narrowest Range patterns (Linda Raschke)
    // 🎯 NR7 = current bar has smallest range in 7 bars = highest compression
    // ⚡ NR4 = same but 4 bars = moderate compression
    nr7 = rangeDaily == ta.lowest(rangeDaily, 7)
    nr4 = rangeDaily == ta.lowest(rangeDaily, 4)

    // ═══════════════════════════════════════════════════════════════
    // 💤 SECTION 4: MOMENTUM DEATH - WHEN MOVEMENT STOPS
    // ═══════════════════════════════════════════════════════════════
    // 🎚️ RSI stuck in dead center (45-55) and not moving = no momentum
    // 🚗 Like a car engine idling - eventually has to shift gears
    rsi = ta.rsi(close, 14)
    rsiCompressed = rsi > 45 and rsi < 55
    rsiFlat = math.abs(rsi - rsi[5]) < 5

    // 📉 MACD convergence: When MACD and signal line merge = momentum pause
    // 🔄 Often precedes momentum resurrection in either direction
    [macdLine, signalLine, _] = ta.macd(close, 12, 26, 9)
    macdFlat = math.abs(macdLine - signalLine) < atr * 0.1

    // 📊 ADX below 20 with balanced +DI/-DI = no trend, no momentum
    // ⚖️ Market in equilibrium, coiling for next directional move
    adxLen = 14
    [diPlus, diMinus, adx] = ta.dmi(adxLen, adxLen)
    noTrend = adx < 20 and math.abs(diPlus - diMinus) < 5

    // ═══════════════════════════════════════════════════════════════
    // 🕐 SECTION 4B: DURATION OF COMPRESSION - TAIL RISK BUILDS OVER TIME
    // ═══════════════════════════════════════════════════════════════
    // 💡 Key insight: The LONGER volatility stays compressed, the MORE explosive the eventual move
    // 🌀 A spring coiled for 3 weeks > a spring coiled for 2 days
    // ⭐ This is the MISSING PIECE for true tail risk detection

    // 🎯 Define what "compressed" means - we need multiple signals to align
    isCompressed = (rvPercentile < 30 or atrCompression or bbSqueeze) and vovPercentile < 40

    // 📊 Count consecutive compressed periods - critical for tail risk
    // 🔄 Using a loop to count backwards until compression breaks
    var int compressionDuration = 0
    if isCompressed
        // Check how many consecutive bars were compressed
        // Must match isCompressed logic: (rvPercentile < 30 or atrCompression or bbSqueeze) and vovPercentile < 40
        consecCompressed = 0
        for i = 0 to 50
            wasCompressed = (rvPercentile[i] < 30 or atrPercent[i] < atrMA[i] * 0.7 or bbWidth[i] < bbHistoricalWidth[i] * 0.5) and vovPercentile[i] < 40
            if wasCompressed
                consecCompressed += 1
            else
                break
        compressionDuration := consecCompressed
    else
        compressionDuration := 0

    // 🎚️ Duration scoring: Longer compression = higher tail risk
    // 🔥 > 20 periods of quiet = maximum danger (spring maximally coiled)
    // ⚠️ > 10 periods = high danger
    // 💡 > 5 periods = moderate danger
    durationScore = compressionDuration > 20 ? 31 : compressionDuration > 10 ? 20 : compressionDuration > 5 ? 10 : 0

    // ═══════════════════════════════════════════════════════════════
    // 💧 SECTION 4C: VOLUME DECLINE - LIQUIDITY DRAIN AMPLIFIES TAIL RISK
    // ═══════════════════════════════════════════════════════════════
    // 🌊 When volume declines during quiet periods:
    // 👥 - Fewer participants = less liquidity
    // 💥 - Less liquidity = bigger price impact when catalyst hits
    // 🚨 - Classic setup for outsized moves (tail events)

    volumeMA50 = ta.sma(volume, 50)
    volumeMA10 = ta.sma(volume, 10)

    // 📉 Volume declining significantly (20%+ below 50-day average)
    volumeDeclining = volumeMA10 < volumeMA50 * 0.8

    // 🎯 Extra points if volume is in bottom quartile of recent range
    volumeLow = ta.lowest(volume, lookback)
    volumeHigh = ta.highest(volume, lookback)
    volumeRange = math.max(volumeHigh - volumeLow, 1)
    volumePercentile = (volume - volumeLow) / volumeRange * 100
    volumeVeryLow = volumePercentile < 25

    // 💰 Volume score: Low + declining volume = liquidity drain = tail risk
    volumeScore = (volumeDeclining ? 15 : 0) + (volumeVeryLow ? 6 : 0)

    // ═══════════════════════════════════════════════════════════════
    // SECTION 5: TAIL RISK SCORE - OPTIMIZED FOR QUIET → EXPLOSIVE DETECTION
    // ═══════════════════════════════════════════════════════════════
    // This score predicts: "Stocks likely to have bigger moves than most people expect"
    // Focus: TAIL RISK from quiet periods, not just technical breakouts
    //
    // NEW WEIGHTING (optimized for tail risk):
    // 1. Volatility Regime: 30% (was 17%) - MOST IMPORTANT for complacency detection
    // 2. Compression: 28% (was 33%) - Still important but not #1
    // 3. Duration: 15% (NEW) - How long has it been quiet?
    // 4. Volume Decline: 10% (NEW) - Liquidity drain amplifies moves
    // 5. Patterns: 12% (was 24%) - Less relevant for tail risk
    // 6. Momentum: 10% (was 17%) - Less relevant for tail risk
    // 7. Bonus: 5% (was 9%) - Multi-signal alignment

    // VOLATILITY REGIME SCORE (max 63 points = 30% of total)
    // This is now the PRIMARY signal - when vol is low AND stable = maximum complacency
    // VoV (Volatility of Volatility) is the STAR - 38 points (18% of total score)
    volScore = (rvPercentile < 20 ? 25 : rvPercentile < 40 ? 13 : 0) + (vovPercentile < 20 ? 38 : 0)

    // COMPRESSION SCORE (max 59 points = 28% of total)
    // TTM Squeeze elevated to 25 points as it's the best compression metric
    // Other compression metrics reduced since they overlap with vol regime
    compressionScore = (ttmSqueeze ? 25 : 0) + (bbSqueeze ? 13 : 0) + (atrCompression ? 8 : 0) + (rangeCompression ? 8 : 0) + (multiCompression ? 5 : 0)

    // PATTERN RECOGNITION SCORE (max 25 points = 12% of total)
    // Reduced weight - patterns are nice but don't specifically predict tail risk
    // Removed triangle pattern - too noisy for tail risk detection
    patternScore = (nr7 ? 10 : nr4 ? 6 : 0) + (multipleInsideDays ? 10 : insideDay ? 5 : 0) + (dojiReversal ? 5 : 0)

    // MOMENTUM DEATH SCORE (max 21 points = 10% of total)
    // Reduced weight - flat momentum doesn't necessarily mean tail risk
    momentumScore = (rsiCompressed and rsiFlat ? 10 : 0) + (macdFlat ? 6 : 0) + (noTrend ? 5 : 0)

    // CALCULATE MASTER TAIL RISK SCORE (raw max = 230 points)
    // Multi-signal bonus: When multiple HIGH-IMPORTANCE categories align = extreme tail risk
    // Bonus reduced from 20 to 10 points (5% vs 9%)
    multiSignal = volScore > 30 and compressionScore > 20 and durationScore > 10
    rawScore = volScore + compressionScore + patternScore + momentumScore + durationScore + volumeScore + (multiSignal ? 10 : 0)

    // Normalize to 0-100 scale for easier interpretation
    fragScore = rawScore / 230 * 100

    // Return all values needed for visualization and table display
    [fragScore, rvPercentile, vovPercentile, atrPercent, bbWidth, ttmSqueeze, nr7, nr4, multipleInsideDays, insideDay, dojiReversal, atrCompression, bbSqueeze, compressionDuration, volumeDeclining]

// Get all calculated values from the selected timeframe
// Using lookahead_on to ensure we only use completed period data, eliminating false compression signals
// from incomplete periods (e.g., early in the week on weekly charts)
[fragScore, rvPercentile, vovPercentile, atrPercent, bbWidth, ttmSqueeze, nr7, nr4, multipleInsideDays, insideDay, dojiReversal, atrCompression, bbSqueeze, compressionDuration, volumeDeclining] = request.security(syminfo.tickerid, selectedTimeframe, calculateFragility(), lookahead = barmerge.lookahead_on)

// ═══════════════════════════════════════════════════════════════
// SECTION 6: VISUALIZATION - SEE THE TAIL RISK
// ═══════════════════════════════════════════════════════════════

// Dynamic color based on tail risk score
// Color coding: Red (80+) = Extreme, Orange (60+) = High, Yellow (40+) = Moderate, Green (20+) = Low
scoreColor = fragScore > 80 ? color.rgb(255, 0, 0) : fragScore > 60 ? color.rgb(255, 128, 0) : fragScore > 40 ? color.rgb(255, 255, 0) : fragScore > 20 ? color.rgb(128, 255, 128) : color.gray

// Plot the master tail risk score as columns (cleaner than lines)
plot(fragScore, 'Tail Risk Score', color = scoreColor, style = plot.style_columns)

// Key indicators (drawn in order of importance - last is on top)
plotshape(multipleInsideDays, 'Multiple Inside Days', shape.square, location.bottom, color = color.orange, size = size.tiny)
plotshape(nr7, 'NR7 (Narrowest Range 7 Bars)', shape.triangleup, location.bottom, color = color.blue, size = size.tiny)
plotshape(ttmSqueeze, 'TTM Squeeze', shape.diamond, location.bottom, color = color.purple, size = size.tiny)

// ═══════════════════════════════════════════════════════════════
// SECTION 7: ALERTS - NOTIFY WHEN TAIL RISK PEAKS
// ═══════════════════════════════════════════════════════════════

alertcondition(fragScore > 80, 'EXTREME TAIL RISK', '🚨 EXTREME tail risk on {{ticker}}! Expect bigger moves than consensus. Score: {{plot_0}}')
alertcondition(fragScore > 60 and fragScore[1] <= 60, 'HIGH TAIL RISK TRIGGERED', '⚠️ HIGH tail risk on {{ticker}}! Complacency detected. Score: {{plot_0}}')
alertcondition(ttmSqueeze and not ttmSqueeze[1], 'TTM SQUEEZE FIRED', 'TTM Squeeze started on {{ticker}} - compression building!')
alertcondition(compressionDuration > 20, 'EXTENDED QUIET PERIOD', '🕐 {{ticker}} has been quiet for 20+ periods - tail risk building!')

// ═══════════════════════════════════════════════════════════════
// SECTION 8: INFO TABLE - TAIL RISK METRICS
// ═══════════════════════════════════════════════════════════════

// Dynamic table size based on verbose mode
tableRows = verboseTable ? 11 : 2
var table infoTable = table.new(position.top_right, 2, tableRows, bgcolor = color.black, border_color = color.white, border_width = 1)

if barstate.islast
    // Row 0: Total Score (always visible)
    table.cell(infoTable, 0, 0, 'Tail Risk Score', text_color = color.white, text_size = textSize)
    table.cell(infoTable, 1, 0, str.tostring(fragScore, '##.#'), text_color = scoreColor, text_size = textSize)

    // Row 1: Signal (always visible)
    signalText = fragScore > 80 ? '🚨 EXTREME!' : fragScore > 60 ? '⚠️ HIGH' : fragScore > 40 ? 'MODERATE' : fragScore > 20 ? 'Low' : 'Wait'
    table.cell(infoTable, 0, 1, 'Signal', text_color = color.white, text_size = textSize)
    table.cell(infoTable, 1, 1, signalText, text_color = fragScore > 60 ? color.red : color.white, text_size = textSize)

    // Additional rows (only in verbose mode) - Ordered by importance for tail risk
    if verboseTable
        // Row 2: VoV Percentile ⭐ MOST IMPORTANT - complacency indicator
        table.cell(infoTable, 0, 2, 'VoV Percentile', text_color = color.white, text_size = textSize)
        table.cell(infoTable, 1, 2, str.tostring(vovPercentile, '##') + '%', text_color = vovPercentile < 20 ? color.rgb(255, 100, 0) : color.white, text_size = textSize)
        // Row 3: Compression Duration 🕐 NEW - how long quiet?
        table.cell(infoTable, 0, 3, 'Quiet Duration', text_color = color.white, text_size = textSize)
        durationText = compressionDuration > 20 ? str.tostring(compressionDuration) + ' 🔥' : str.tostring(compressionDuration)
        table.cell(infoTable, 1, 3, durationText, text_color = compressionDuration > 20 ? color.red : compressionDuration > 10 ? color.orange : color.white, text_size = textSize)
        // Row 4: Volume Status 💧 NEW - liquidity drain
        table.cell(infoTable, 0, 4, 'Volume', text_color = color.white, text_size = textSize)
        volumeText = volumeDeclining ? '⬇️ Declining' : 'Normal'
        table.cell(infoTable, 1, 4, volumeText, text_color = volumeDeclining ? color.yellow : color.white, text_size = textSize)
        // Row 5: RV Percentile
        table.cell(infoTable, 0, 5, 'RV Percentile', text_color = color.white, text_size = textSize)
        table.cell(infoTable, 1, 5, str.tostring(rvPercentile, '##') + '%', text_color = rvPercentile < 20 ? color.green : color.white, text_size = textSize)
        // Row 6: TTM Squeeze
        table.cell(infoTable, 0, 6, 'TTM Squeeze', text_color = color.white, text_size = textSize)
        table.cell(infoTable, 1, 6, ttmSqueeze ? '🔥 ACTIVE' : 'No', text_color = ttmSqueeze ? color.red : color.gray, text_size = textSize)
        // Row 7: ATR %
        table.cell(infoTable, 0, 7, 'ATR %', text_color = color.white, text_size = textSize)
        table.cell(infoTable, 1, 7, str.tostring(atrPercent, '#.##') + '%', text_color = atrCompression ? color.yellow : color.white, text_size = textSize)
        // Row 8: BB Width
        table.cell(infoTable, 0, 8, 'BB Width', text_color = color.white, text_size = textSize)
        table.cell(infoTable, 1, 8, str.tostring(bbWidth, '#.#') + '%', text_color = bbSqueeze ? color.orange : color.white, text_size = textSize)
        // Row 9: Range Status
        table.cell(infoTable, 0, 9, 'Range Status', text_color = color.white, text_size = textSize)
        rangeStatus = nr7 ? 'NR7!' : nr4 ? 'NR4' : 'Normal'
        table.cell(infoTable, 1, 9, rangeStatus, text_color = nr7 ? color.yellow : color.white, text_size = textSize)
        // Row 10: Pattern
        table.cell(infoTable, 0, 10, 'Pattern', text_color = color.white, text_size = textSize)
        patternStatus = multipleInsideDays ? 'Multi-Inside' : insideDay ? 'Inside Day' : dojiReversal ? 'Doji' : 'None'
        table.cell(infoTable, 1, 10, patternStatus, text_color = patternStatus != 'None' ? color.rgb(0, 255, 255) : color.gray, text_size = textSize)

Overview

Market Tail Risk Detector (Quiet → Explosive) calculates a composite tail risk score ranging from 0 to 100 based on realized volatility percentiles, volatility of volatility, compression duration, volume trends, and squeeze metrics like Bollinger Bands and Keltner Channels. Displayed in a separate pane alongside an informational table, the indicator highlights periods of extreme market complacency. Inputs allow users to adjust timeframe selection, ATR and realized volatility lengths, and channel parameters. It does not predict breakout direction or timing.

Details

Published by

major on GitHub

Type

Indicator, Pine Script v6

Draws

Separate pane below the chart

Category

Volatility & bands, Breakout, Volume, Screeners & dashboards

Markets

Any symbol

Timeframes

Any timeframe

Risk Notes

  • Reads higher-timeframe data with lookahead on, so the history can show signals that were not there in real time

Settings

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

SettingTypeDefaultVariable
Use Custom TimeframeboolfalseuseCustomTimeframe
TimeframetimeframeWcustomTimeframe
Info Table Text SizestringSmalltableTextSize
Verbose Table (Show All Metrics)boolfalseverboseTable
Realized Vol Periodint20rvLen
ATR Periodint14atrLen
Compression Detection Periodint30compressionLen
Bollinger Band Lengthint20bbLen
Keltner Channel Lengthint20kcLen
Keltner Multiplierfloat1.5kcMult
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

2 stars

Calculates 20-day Average Daily Range percentage, multi-month performance metrics, opening gap size, and relative volume to identify Kristjan Kullamägi style Breakout and Episodic Pivot setups. When conditions are satisfied on the price chart, it flags candles

Indicator v6 BreakoutVolume
Updated 4 months ago · Custom licence GitHub repository of Qullamaggie Breakout / EP Flags

This indicator calculates the Relative Strength Index (RSI) alongside a simple moving average to generate directional momentum signals in a separate pane. It supports Break Flip and Break Continuous scanning modes for long and short setups. A wide array of opt

Indicator v5 Oscillators & momentumVolatility & bands
Updated 1 year ago · None stated GitHub repository of Momentum Setup: RSI Direction Confirmation
4 stars

Meridian - Futures Context is an intraday analysis indicator designed for US index futures. It measures volume participation using relative volume, calculates rolling realized volatility, and evaluates trend context through VWAP slope and EMA structures. Displ

Indicator v6 VolumeVolatility & bands
Updated 7 weeks ago · MPL-2.0 GitHub repository of Meridian - Futures Context
2 stars

Calculates an ATR-based z-score oscillator and normalized volume across multiple assets, screening up to 21 symbols simultaneously. Outlier detection filters extreme standard deviations before normalizing values between 0 and 100. It visualizes the comparative

Indicator v5 Screeners & dashboardsVolatility & bands
Updated 8 months ago · None stated GitHub repository of ATR OSC and Volume Screener
2 stars

This indicator analyzes volume character and market structure in a separate chart pane. It color-codes volume bars to identify institutional accumulation, distribution, climax volume, and volume dry-up conditions during volatility contraction patterns. The scr

Indicator v5 VolumeTrend
Updated 4 months ago · None stated GitHub repository of TJ Essentials - Daily Swing

DEPV2 Debug Siga

by YuceSezar89
1 stars

DEPV2 is a multi-symbol crypto indicator that generates trading signals across 20 Binance assets including BTC, ETH, and SOL. It analyzes four method classes—trend, momentum, volatility, and volume—using modes such as simultaneous confirmation requiring at lea

Indicator v6 Screeners & dashboardsOscillators & momentum Repaints
Updated 11 days ago · None stated GitHub repository of DEPV2 Debug Siga

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