by damianpitt· code credited to Capital41 | MIT License
This indicator detects market stress in cryptocurrency perpetual futures by measuring the spot-to-perp basis, open interest impulses, and liquidation wicks.
// Copyright (c) 2026 Capital41 | MIT License
// CAPITAL41 CRYPTO PERP STRESS
// What it does:
// - Detects crypto-specific stress using perp basis, OI impulse, and
// liquidation wick behavior.
// - Flags long-squeeze risk, short-squeeze risk, cascade starts, and reset
// completion.
// - Includes weekend sensitivity controls for 24/7 markets.
//
// TradingView setup:
// 1) Open TradingView -> Pine Editor.
// 2) Paste this script, Save, and Add to chart.
// 3) Use intraday or 4h on liquid perp pairs.
// 4) Set Spot/Perp/OI symbols for your exchange.
//
// Usage guide:
// - Long-squeeze risk: crowded longs + OI build + downside liquidation wick.
// - Short-squeeze risk: crowded shorts + OI build + upside liquidation wick.
// - Cascade tags: stress + break of prior bar extreme.
// - Reset complete: basis normalizes, OI flushes, and volatility compresses.
//@version=6
indicator("Capital41 Crypto Perp Stress", overlay = true, max_labels_count = 200)
// Symbol inputs
spotSymbol = input.symbol(syminfo.tickerid, "Spot symbol")
perpSymbol = input.symbol(syminfo.tickerid, "Perp symbol")
oiSymbol = input.string("", "OI symbol (optional)")
useVolumeFallback = input.bool(true, "Use volume fallback when OI missing")
// Core thresholds
basisPosPct = input.float(0.25, "Positive basis threshold %", minval = 0.01, maxval = 5.0, step = 0.01)
basisNegPct = input.float(-0.25, "Negative basis threshold %", minval = -5.0, maxval = -0.01, step = 0.01)
basisNeutralPct = input.float(0.10, "Neutral basis absolute %", minval = 0.01, maxval = 3.0, step = 0.01)
oiImpulsePct = input.float(1.00, "OI impulse threshold %", minval = 0.10, maxval = 20.0, step = 0.05)
oiFlushPct = input.float(1.20, "OI flush threshold %", minval = 0.10, maxval = 20.0, step = 0.05)
oiSmooth = input.int(3, "OI change smoothing", minval = 1, maxval = 30)
// Wick and volatility
atrLen = input.int(14, "ATR length", minval = 1)
atrAvgLen = input.int(20, "ATR average length", minval = 5)
wickAtrMult = input.float(0.8, "Wick ATR multiplier", minval = 0.1, maxval = 5.0)
wickBodyRecovery = input.float(0.55, "Wick recovery ratio", minval = 0.10, maxval = 0.95)
volLen = input.int(50, "Volume SMA length", minval = 5)
volMult = input.float(1.2, "Volume spike multiplier", minval = 0.1, maxval = 5.0)
compAtrMult = input.float(0.9, "Compression ATR multiplier", minval = 0.1, maxval = 2.0)
// Weekend behavior
useWeekendAdjust = input.bool(true, "Enable weekend sensitivity")
weekendBasisFactor = input.float(0.85, "Weekend basis factor", minval = 0.5, maxval = 1.5)
weekendOiFactor = input.float(0.85, "Weekend OI factor", minval = 0.5, maxval = 1.5)
weekendWickFactor = input.float(0.90, "Weekend wick factor", minval = 0.5, maxval = 1.5)
// Display
showSignals = input.bool(true, "Show signal markers")
showBg = input.bool(true, "Show stress background")
showBadge = input.bool(true, "Show status badge")
showEMA = input.bool(false, "Show trend EMA")
emaLen = input.int(50, "Trend EMA length", minval = 5)
colorBull = color.new(color.lime, 0)
colorBear = color.new(color.red, 0)
colorWarn = color.new(color.orange, 0)
colorNeutral = color.new(color.gray, 80)
f_clamp(_x, _lo, _hi) =>
math.max(_lo, math.min(_hi, _x))
// Market data
spotClose = request.security(spotSymbol, timeframe.period, close, lookahead = barmerge.lookahead_off)
perpClose = request.security(perpSymbol, timeframe.period, close, lookahead = barmerge.lookahead_off)
basisPct = spotClose > 0 ? (perpClose - spotClose) / spotClose * 100.0 : 0.0
oiRaw = oiSymbol != "" ? request.security(oiSymbol, timeframe.period, close, lookahead = barmerge.lookahead_off) : na
oiOk = not na(oiRaw)
oiSeries = oiOk ? oiRaw : volume
oiValid = oiOk or useVolumeFallback
oiChgRaw = oiSeries[1] > 0 ? (oiSeries - oiSeries[1]) / oiSeries[1] * 100.0 : 0.0
oiChg = ta.ema(oiChgRaw, oiSmooth)
isWeekend = dayofweek == dayofweek.saturday or dayofweek == dayofweek.sunday
basisAdj = useWeekendAdjust and isWeekend ? weekendBasisFactor : 1.0
oiAdj = useWeekendAdjust and isWeekend ? weekendOiFactor : 1.0
wickAdj = useWeekendAdjust and isWeekend ? weekendWickFactor : 1.0
posBasisThr = basisPosPct * basisAdj
negBasisThr = basisNegPct * basisAdj
neutralBasisThr = basisNeutralPct * basisAdj
oiImpulseThr = oiImpulsePct * oiAdj
oiFlushThr = oiFlushPct * oiAdj
wickMultAdj = wickAtrMult * wickAdj
atr = ta.atr(atrLen)
atrAvg = ta.sma(atr, atrAvgLen)
ema = ta.ema(close, emaLen)
trendUp = close > ema
trendDn = close < ema
plot(showEMA ? ema : na, title = "Trend EMA", color = trendUp ? colorBull : trendDn ? colorBear : color.new(color.gray, 0), linewidth = 2)
volSma = ta.sma(volume, volLen)
volSpike = volSma > 0 and volume > volSma * volMult
barRange = high - low
upWick = high - math.max(open, close)
downWick = math.min(open, close) - low
recoverUp = barRange > 0 and close <= high - barRange * wickBodyRecovery
recoverDown = barRange > 0 and close >= low + barRange * wickBodyRecovery
liqUp = upWick > atr * wickMultAdj and volSpike and recoverUp and barstate.isconfirmed
liqDown = downWick > atr * wickMultAdj and volSpike and recoverDown and barstate.isconfirmed
crowdedLong = basisPct >= posBasisThr
crowdedShort = basisPct <= negBasisThr
oiBuild = oiValid and oiChg >= oiImpulseThr
oiFlush = oiValid and oiChg <= -oiFlushThr
longSqueezeRisk = crowdedLong and oiBuild and liqDown
shortSqueezeRisk = crowdedShort and oiBuild and liqUp
cascadeDown = longSqueezeRisk and close < low[1]
cascadeUp = shortSqueezeRisk and close > high[1]
basisNeutral = math.abs(basisPct) <= neutralBasisThr
volCompression = atrAvg > 0 and atr < atrAvg * compAtrMult
resetComplete = basisNeutral and oiFlush and volCompression and barstate.isconfirmed
crowdedTrendUp = trendUp and crowdedLong and oiBuild and not liqDown
crowdedTrendDn = trendDn and crowdedShort and oiBuild and not liqUp
// Stress meter (0-100)
basisStress = f_clamp(math.abs(basisPct) / math.abs(posBasisThr) * 35.0, 0.0, 35.0)
oiStress = f_clamp(math.max(oiChg, 0.0) / oiImpulseThr * 30.0, 0.0, 30.0)
wickStress = (liqUp or liqDown) ? 25.0 : 0.0
trendStress = (crowdedTrendUp or crowdedTrendDn) ? 10.0 : 0.0
stress = f_clamp(basisStress + oiStress + wickStress + trendStress, 0.0, 100.0)
// Visuals
plotshape(showSignals and longSqueezeRisk, title = "Long Squeeze Risk", style = shape.triangledown,
location = location.abovebar, color = colorBear, size = size.tiny, text = "LSQ")
plotshape(showSignals and shortSqueezeRisk, title = "Short Squeeze Risk", style = shape.triangleup,
location = location.belowbar, color = colorBull, size = size.tiny, text = "SSQ")
plotshape(showSignals and cascadeDown, title = "Cascade Down", style = shape.xcross,
location = location.abovebar, color = color.new(colorBear, 0), size = size.tiny, text = "CD")
plotshape(showSignals and cascadeUp, title = "Cascade Up", style = shape.xcross,
location = location.belowbar, color = color.new(colorBull, 0), size = size.tiny, text = "CU")
plotshape(showSignals and resetComplete, title = "Reset Complete", style = shape.circle,
location = location.belowbar, color = color.new(colorWarn, 0), size = size.tiny, text = "R")
bgcolor(showBg ? (longSqueezeRisk ? color.new(colorBear, 88) :
shortSqueezeRisk ? color.new(colorBull, 88) :
resetComplete ? color.new(colorWarn, 90) :
crowdedTrendUp ? color.new(colorBull, 94) :
crowdedTrendDn ? color.new(colorBear, 94) : na) : na)
// Status badge
var table status = table.new(position.top_right, 6, 1, border_width = 1)
if barstate.islast
if showBadge
bText = " BAS " + str.tostring(basisPct, "#.###") + "% "
oiText = " OI " + str.tostring(oiChg, "#.##") + "% "
wkText = isWeekend ? " WKND " : " WKDY "
liqText = liqUp ? " LIQ U " : liqDown ? " LIQ D " : " LIQ 0 "
stText = " ST " + str.tostring(stress, "#.0") + " "
sigText = longSqueezeRisk ? " SQ D " : shortSqueezeRisk ? " SQ U " : resetComplete ? " RESET " : " - "
table.cell(status, 0, 0, bText, bgcolor = crowdedLong ? color.new(colorWarn, 65) : crowdedShort ? color.new(colorBear, 65) : colorNeutral, text_color = color.white, text_size = size.tiny)
table.cell(status, 1, 0, oiText, bgcolor = oiBuild ? color.new(colorWarn, 65) : oiFlush ? color.new(colorBull, 70) : colorNeutral, text_color = color.white, text_size = size.tiny)
table.cell(status, 2, 0, wkText, bgcolor = isWeekend ? color.new(color.blue, 72) : color.new(color.gray, 75), text_color = color.white, text_size = size.tiny)
table.cell(status, 3, 0, liqText, bgcolor = liqUp ? color.new(colorBull, 65) : liqDown ? color.new(colorBear, 65) : colorNeutral, text_color = color.white, text_size = size.tiny)
table.cell(status, 4, 0, stText, bgcolor = stress >= 70 ? color.new(colorBear, 65) : stress >= 45 ? color.new(colorWarn, 65) : color.new(colorBull, 70), text_color = color.white, text_size = size.tiny)
table.cell(status, 5, 0, sigText, bgcolor = longSqueezeRisk ? color.new(colorBear, 60) : shortSqueezeRisk ? color.new(colorBull, 60) : resetComplete ? color.new(colorWarn, 65) : colorNeutral, text_color = color.white, text_size = size.tiny)
else
for i = 0 to 5
table.cell(status, i, 0, "", bgcolor = color.new(color.black, 100), text_color = color.new(color.black, 100))
// Alerts
alertcondition(longSqueezeRisk, title = "Long Squeeze Risk",
message = "Crypto Perp Stress: long-squeeze risk on {{ticker}} ({{interval}})")
alertcondition(shortSqueezeRisk, title = "Short Squeeze Risk",
message = "Crypto Perp Stress: short-squeeze risk on {{ticker}} ({{interval}})")
alertcondition(cascadeDown, title = "Cascade Down Start",
message = "Crypto Perp Stress: downside cascade start on {{ticker}} ({{interval}})")
alertcondition(cascadeUp, title = "Cascade Up Start",
message = "Crypto Perp Stress: upside cascade start on {{ticker}} ({{interval}})")
alertcondition(resetComplete, title = "Reset Complete",
message = "Crypto Perp Stress: reset complete on {{ticker}} ({{interval}})")
Overview
Capital41 Crypto Perp Stress signals long-squeeze risks, short-squeeze risks, cascade starts, and market reset completions when basis and volatility normalize. User settings customize spot, perp, and open interest tickers, basis thresholds, ATR multipliers, volume fallback conditions, and weekend sensitivity dampening. The tool overlays signal markers directly onto the price chart, supported by stress background tinting, an optional trend exponential moving average, and a status badge.
Perp-to-spot basis spread monitoring
Open interest impulse and flush detection with volume fallback
Liquidation wick identification using ATR multipliers
The inputs you can change from the gear icon on the chart. Defaults are the author's.
Setting
Type
Default
Variable
Spot symbol
symbol
syminfo.tickerid
spotSymbol
Perp symbol
symbol
syminfo.tickerid
perpSymbol
OI symbol (optional)
string
oiSymbol
Use volume fallback when OI missing
bool
true
useVolumeFallback
Positive basis threshold %
float
0.25
basisPosPct
Negative basis threshold %
float
-0.25
basisNegPct
Neutral basis absolute %
float
0.10
basisNeutralPct
OI impulse threshold %
float
1.00
oiImpulsePct
OI flush threshold %
float
1.20
oiFlushPct
OI change smoothing
int
3
oiSmooth
ATR length
int
14
atrLen
ATR average length
int
20
atrAvgLen
Show all 26 settings
Setting
Type
Default
Variable
Wick ATR multiplier
float
0.8
wickAtrMult
Wick recovery ratio
float
0.55
wickBodyRecovery
Volume SMA length
int
50
volLen
Volume spike multiplier
float
1.2
volMult
Compression ATR multiplier
float
0.9
compAtrMult
Enable weekend sensitivity
bool
true
useWeekendAdjust
Weekend basis factor
float
0.85
weekendBasisFactor
Weekend OI factor
float
0.85
weekendOiFactor
Weekend wick factor
float
0.90
weekendWickFactor
Show signal markers
bool
true
showSignals
Show stress background
bool
true
showBg
Show status badge
bool
true
showBadge
Show trend EMA
bool
false
showEMA
Trend EMA length
int
50
emaLen
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.
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
This indicator plots a custom Anchored Volume-Weighted Average Price (VWAP) directly on the price chart along with optional standard deviation bands. Users can anchor the VWAP to standard time periods like sessions, weeks, or months, or to corporate events suc
This indicator plots a volume-weighted average price and three standard-deviation bands on the price chart. It resets the running volume-weighted totals at the selected anchor, which can be the session, week, month, quarter, or year. The source price, band mul
LiqSpikeBounceLong Feb2026 is an indicator designed to identify long bounce entries following high-volume liquidation cascades in bearish market contexts. It checks if price is below an exponential moving average, then detects large single-bar drops exceeding
This indicator calculates Kaufman's Adaptive Moving Average (KAMA) on a chosen price source and determines trend direction based on the slope across a configurable lookback window. Signals can be filtered by short-term volatility expansion using ATR comparison
This indicator plots a volume-weighted average price on the price chart and resets the calculation when the selected session changes. It builds first and second standard-deviation bands from a volume-weighted variance of the chosen source, which defaults to th