The following Pine v6 indicator observes a broad equity benchmark on standard one-day candles. It is a teaching diagnostic, not a replication of the paper's forecasting model. At a confirmed close, the stress state requires a drawdown of at least 20% from the highest close in 252 bars and annualized sample volatility of at least 30% over 20 daily log returns. The thresholds are research choices with no claimed predictive accuracy.
An observation-only script. R marks the end of a completed window; it is not a momentum portfolio loss or an advance crash signal. Use a documented benchmark feed and standard daily candles.pine
//@version=6
indicator("Momentum crash: benchmark state observer", overlay = false)
int peakBars = input.int(252, "Peak window (daily bars)", minval = 2, maxval = 1000)
int volBars = input.int(20, "Volatility window", minval = 2, maxval = 500)
int reboundBars = input.int(5, "Rebound window", minval = 1, maxval = 100)
float ddLimit = input.float(20.0, "Drawdown threshold (%)", minval = 0.1, maxval = 99.0) / 100.0
float volLimit = input.float(30.0, "Annual volatility threshold (%)", minval = 0.1) / 100.0
float reboundLimit = input.float(10.0, "Rebound threshold (%)", minval = 0.1) / 100.0
if not timeframe.isdaily or timeframe.multiplier != 1
runtime.error("Use standard one-day equity benchmark candles.")
bool validPrice = not na(close) and close > 0
bool validReturn = validPrice and not na(close[1]) and close[1] > 0
var int validPrices = 0
validPrices := validPrice ? validPrices + 1 : 0
float logReturn = validReturn ? math.log(close / close[1]) : na
float peak = ta.highest(close, peakBars)
float sigma = ta.stdev(logReturn, volBars, false) * math.sqrt(252.0)
bool ready = validPrices >= math.max(peakBars, volBars + 1) and not na(sigma)
float drawdown = ready ? close / peak - 1.0 : na
bool stressState = ready and drawdown <= -ddLimit and sigma >= volLimit
bool reboundReady = ready and ready[reboundBars] and validPrices > reboundBars
float reboundReturn = reboundReady ? close / close[reboundBars] - 1.0 : na
bool realizedRebound = reboundReady and stressState[reboundBars] and reboundReturn >= reboundLimit
bool confirmed = barstate.isconfirmed
plot(confirmed ? drawdown * 100.0 : na, "Drawdown (%)", color = color.blue)
plot(confirmed ? sigma * 100.0 : na, "Annualized volatility (%)", display = display.data_window)
plot(confirmed ? reboundReturn * 100.0 : na, "Rebound return (%)", display = display.data_window)
plot(confirmed and ready ? (stressState ? 1 : 0) : na, "Stress state (1 or 0)", display = display.data_window)
plot(confirmed and reboundReady ? (realizedRebound ? 1 : 0) : na, "Completed rebound (1 or 0)", display = display.data_window)
plotshape(confirmed and realizedRebound, title = "Completed rebound", style = shape.triangleup, location = location.top, color = color.orange, size = size.tiny, text = "R")
alertcondition(confirmed and realizedRebound, "Completed rebound after stress", "A benchmark rebound window has completed after a stress state.")