The Pine script (take this TACO with a pinch of salt):
Any idea to improve it is welcome!
//
@version=6
indicator("TACO Detector", overlay=false, max_labels_count=500)
//----------------------------------------------------
// TACO DETECTOR:THE ULTIMATE TACO FRAGILITY INDEX
//----------------------------------------------------
//
// This indicator measures how vulnerable the market is to a sudden headline, tariff, political, or tweet-driven shock.
//
// Logic:
// High risk appears when market volume, realised volatility, range, and optional VIX/VXN are compressed.
//
// Labels:
// HF = High Fragility
// XF = Extreme Fragility
// SB = Shock Bar
// ER = Escalation Risk
//
//----------------------------------------------------
//----------------------------------------------------
// Inputs
//----------------------------------------------------
lookback =
input.int(100, "Percentile Lookback", minval=20)
rvLen =
input.int(20, "Realized Vol Length", minval=5)
atrLen =
input.int(14, "ATR Length", minval=5)
volLen =
input.int(20, "Volume MA Length", minval=5)
useVix = input.bool(true, "Use VIX / VXN Compression Filter?")
vixSymbol = input.symbol("CBOE:VXN", "Volatility Index Symbol")
vixWeight = input.float(0.25, "VIX/VXN Compression Weight", minval=0, maxval=1)
volumeWeight = input.float(0.30, "Low Volume Weight", minval=0, maxval=1)
rvWeight = input.float(0.30, "Low Realized Vol Weight", minval=0, maxval=1)
rangeWeight = input.float(0.15, "Low Range Weight", minval=0, maxval=1)
riskHigh = input.float(70, "High Risk Threshold", minval=0, maxval=100)
riskExtreme = input.float(85, "Extreme Risk Threshold", minval=0, maxval=100)
shockMoveMult = input.float(1.75, "Shock Move Multiple", minval=0.5, step=0.25)
shockVolMult = input.float(1.50, "Shock Volume Multiple", minval=0.5, step=0.25)
showDashboard = input.bool(true, "Show Dashboard")
showLegend = input.bool(true, "Show Legend")
//----------------------------------------------------
// Volume Compression
//----------------------------------------------------
volMA = ta.sma(volume, volLen)
volRatio = volume / volMA
volRank = ta.percentrank(volume, lookback)
lowVolScore = 100 - volRank
//----------------------------------------------------
// Realized Volatility Compression
//----------------------------------------------------
logRet = math.log(close / close[1])
realizedVol = ta.stdev(logRet, rvLen) * 100
rvRank = ta.percentrank(realizedVol, lookback)
lowRvScore = 100 - rvRank
//----------------------------------------------------
// Range Compression
//----------------------------------------------------
atrPct = ta.atr(atrLen) / close * 100
atrRank = ta.percentrank(atrPct, lookback)
lowRangeScore = 100 - atrRank
//----------------------------------------------------
// Optional VIX / VXN Compression
//----------------------------------------------------
vixClose =
request.security(vixSymbol, timeframe.period, close, ignore_invalid_symbol=true)
vixRank = ta.percentrank(vixClose, lookback)
lowVixScore = useVix and not na(vixClose) ? 100 - vixRank : 0
//----------------------------------------------------
// Fragility Score
//----------------------------------------------------
baseWeight = volumeWeight rvWeight rangeWeight (useVix ? vixWeight : 0)
fragilityRaw = (lowVolScore * volumeWeight lowRvScore * rvWeight lowRangeScore * rangeWeight lowVixScore * (useVix ? vixWeight : 0)) / baseWeight
fragility = math.max(0, math.min(100, fragilityRaw))
//----------------------------------------------------
// Shock Detection
//----------------------------------------------------
absMovePct = math.abs(close / close[1] - 1) * 100
shockMove = absMovePct > realizedVol * shockMoveMult
shockVol = volume > volMA * shockVolMult
shockBar = shockMove and shockVol
//----------------------------------------------------
// Escalation Detection
//----------------------------------------------------
rangeExpansion = atrPct > ta.sma(atrPct, atrLen)
volExpansion = volRatio > 1.2
preShockRisk = fragility > riskHigh
extremeRisk = fragility > riskExtreme
escalationRisk = preShockRisk and (rangeExpansion or volExpansion)
//----------------------------------------------------
// Main Plot
//----------------------------------------------------
plot(fragility, title="TACO Fragility", linewidth=2)
hline(50, "Neutral", linestyle=
hline.style_dotted)
hline(70, "High Risk", linestyle=
hline.style_dashed)
hline(85, "Extreme Risk", linestyle=
hline.style_dashed)
//----------------------------------------------------
// Background Risk Highlight
//----------------------------------------------------
bgcolor(extremeRisk ?
color.new(
color.red, 82) : preShockRisk ?
color.new(
color.orange, 86) : na)
//----------------------------------------------------
// Label Markers
//----------------------------------------------------
//
// HF = High Fragility
// XF = Extreme Fragility
// SB = Shock Bar
// ER = Escalation Risk
//
// These are not buy or sell signals.
// They are market risk-state signals.
//----------------------------------------------------
plotshape(preShockRisk and not extremeRisk, title="HF - High Fragility", text="HF", style=shape.labeldown, location=
location.top, size=size.tiny, color=
color.orange, textcolor=color.white)
plotshape(extremeRisk, title="XF - Extreme Fragility", text="XF", style=shape.labeldown, location=
location.top, size=size.tiny, color=
color.red, textcolor=color.white)
plotshape(shockBar, title="SB - Shock Bar", text="SB", style=shape.labeldown, location=
location.top, size=size.tiny, color=color.fuchsia, textcolor=color.white)
plotshape(escalationRisk, title="ER - Escalation Risk", text="ER", style=shape.labelup, location=location.bottom, size=size.tiny, color=color.yellow, textcolor=
color.black)
//----------------------------------------------------
// Alerts
//----------------------------------------------------
alertcondition(preShockRisk, title="HF - TACO Risk High", message="HF: TACO Fragility is HIGH. Market is quiet, thin, and vulnerable to a headline shock.")
alertcondition(extremeRisk, title="XF - TACO Risk Extreme", message="XF: TACO Fragility is EXTREME. Compressed market conditions may amplify sudden headline or tweet-driven moves.")
alertcondition(shockBar, title="SB - Shock Bar Detected", message="SB: Shock Bar detected. Abnormal price move with volume expansion.")
alertcondition(escalationRisk, title="ER - TACO Escalation Risk", message="ER: High fragility plus range or volume expansion. Market may be transitioning from calm into headline-driven movement.")
//----------------------------------------------------
// Dashboard Table
//----------------------------------------------------
var table dash =
table.new(
position.top_right, 2, 6, border_width=1)
if barstate.islast
if showDashboard
riskText = fragility >= riskExtreme ? "EXTREME" : fragility >= riskHigh ? "HIGH" : fragility >= 50 ? "ELEVATED" : "LOW"
riskBg = fragility >= riskExtreme ?
color.new(
color.red, 0) : fragility >= riskHigh ?
color.new(
color.orange, 0) : fragility >= 50 ?
color.new(color.yellow, 0) :
color.new(color.gray, 70)
riskTxt = fragility >= 50 ?
color.black : color.white
table.cell(dash, 0, 0, "TACO Fragility", text_color=color.white)
table.cell(dash, 1, 0, str.tostring(fragility, "#.0") " / 100", text_color=color.white)
table.cell(dash, 0, 1, "Risk State", text_color=color.white)
table.cell(dash, 1, 1, riskText, bgcolor=riskBg, text_color=riskTxt)
table.cell(dash, 0, 2, "Low Volume", text_color=color.white)
table.cell(dash, 1, 2, str.tostring(lowVolScore, "#.0"), text_color=color.white)
table.cell(dash, 0, 3, "Low Realized Vol", text_color=color.white)
table.cell(dash, 1, 3, str.tostring(lowRvScore, "#.0"), text_color=color.white)
table.cell(dash, 0, 4, "Low Range", text_color=color.white)
table.cell(dash, 1, 4, str.tostring(lowRangeScore, "#.0"), text_color=color.white)
table.cell(dash, 0, 5, "Low VIX/VXN", text_color=color.white)
table.cell(dash, 1, 5, useVix ? str.tostring(lowVixScore, "#.0") : "Off", text_color=color.white)
else
table.clear(dash, 0, 0)
//----------------------------------------------------
// Legend
//----------------------------------------------------
var table legend =
table.new(position.bottom_right, 2, 5, border_width=1)
if barstate.islast
if showLegend
table.cell(legend, 0, 0, "Code", bgcolor=
color.new(color.gray, 40), text_color=color.white)
table.cell(legend, 1, 0, "Meaning", bgcolor=
color.new(color.gray, 40), text_color=color.white)
table.cell(legend, 0, 1, "HF", bgcolor=
color.orange, text_color=color.white)
table.cell(legend, 1, 1, "High Fragility", bgcolor=
color.new(
color.orange, 75), text_color=color.white)
table.cell(legend, 0, 2, "XF", bgcolor=
color.red, text_color=color.white)
table.cell(legend, 1, 2, "Extreme Fragility", bgcolor=
color.new(
color.red, 75), text_color=color.white)
table.cell(legend, 0, 3, "SB", bgcolor=color.fuchsia, text_color=color.white)
table.cell(legend, 1, 3, "Shock Bar", bgcolor=
color.new(color.fuchsia, 75), text_color=color.white)
table.cell(legend, 0, 4, "ER", bgcolor=color.yellow, text_color=
color.black)
table.cell(legend, 1, 4, "Escalation Risk", bgcolor=
color.new(color.yellow, 70), text_color=
color.black)
else
table.clear(legend, 0, 0)