// Licensed under the Apache License, Version 2.0 // © mihakralj //@version=6 indicator("Dynamic Momentum Index (DYMI)", "DYMI", overlay=false) //@description Dynamic Momentum Index by Tushar Chande and Stanley Kroll (1994). // A three-stage pipeline that produces a volatility-adaptive RSI: // Stage 1: Dual circular-buffer StdDev → volatility ratio V = σ_short / σ_long // Stage 2: dynamic_period = clamp(round(basePeriod / V), minPeriod, maxPeriod) // Stage 3: Wilder RMA RSI with adaptive alpha = 1 / dynamic_period // When price volatility is high V > 1, the period shortens → faster response. // When volatility is low V < 1, the period lengthens → smoother output. //@function Calculates StdDev over a circular buffer of given period //@param source Price series //@param period Window size //@returns Population standard deviation of the window stddev_circ(series float source, simple int period) => var array buf = array.new_float(period, na) var int head = 0 var int count = 0 var float sumV = 0.0 var float sumSq = 0.0 float oldest = array.get(buf, head) if not na(oldest) sumV -= oldest sumSq -= oldest * oldest float val = na(source) ? 0.0 : source array.set(buf, head, val) sumV += val sumSq += val * val head := (head + 1) % period if count < period count += 1 float mean = sumV / count float variance = sumSq / count - mean * mean float sd = variance > 0.0 ? math.sqrt(variance) : 0.0 sd //@function Calculates Wilder's RMA RSI with warmup compensation and adaptive alpha //@param source Close price series //@param dynPeriod Dynamic period (integer, already clamped) //@returns RSI value in [0, 100] rsi_wilder(series float source, series int dynPeriod) => var float prevVal = na var float avgGain = 0.0 var float avgLoss = 0.0 var float e = 1.0 var bool warmup = true float result = 50.0 if not na(source) if na(prevVal) prevVal := source else float alpha = 1.0 / dynPeriod float beta = 1.0 - alpha float change = source - prevVal float gain = change > 0.0 ? change : 0.0 float loss = change < 0.0 ? -change : 0.0 prevVal := source avgGain := alpha * gain + beta * avgGain avgLoss := alpha * loss + beta * avgLoss if warmup e *= beta float c = e > 1e-10 ? 1.0 / (1.0 - e) : 1.0 float aG = avgGain * c float aL = avgLoss * c float total = aG + aL result := total != 0.0 ? 100.0 * aG / total : 50.0 if e <= 1e-10 warmup := false else float total = avgGain + avgLoss result := total != 0.0 ? 100.0 * avgGain / total : 50.0 result //@function Calculates Dynamic Momentum Index //@param source Close price series //@param basePeriod Base RSI period (default 14) //@param shortPeriod Short StdDev window (default 5) //@param longPeriod Long StdDev window (default 10) //@param minPeriod Minimum dynamic period (default 3) //@param maxPeriod Maximum dynamic period (default 30) //@returns DYMI value in [0, 100] //@optimized Uses circular buffers for O(1) StdDev; adaptive Wilder RMA for RSI dymi(series float source, simple int basePeriod, simple int shortPeriod, simple int longPeriod, simple int minPeriod, simple int maxPeriod) => if basePeriod < 2 or shortPeriod < 2 or longPeriod <= shortPeriod or minPeriod < 2 or maxPeriod < minPeriod runtime.error("Invalid DYMI parameters") // Stage 1: dual StdDev volatility ratio float sdShort = stddev_circ(source, shortPeriod) float sdLong = stddev_circ(source, longPeriod) float v = sdLong > 1e-10 ? sdShort / sdLong : 1.0 // Stage 2: dynamic period int rawPeriod = v > 1e-10 ? math.round(basePeriod / v) : maxPeriod int dynPeriod = math.max(minPeriod, math.min(maxPeriod, rawPeriod)) // Stage 3: adaptive Wilder RSI float result = rsi_wilder(source, dynPeriod) math.max(0.0, math.min(100.0, result)) // ---------- Main loop ---------- i_basePeriod = input.int(14, "Base RSI Period", minval=2, maxval=500) i_shortPeriod = input.int(5, "Short StdDev Period", minval=2, maxval=500) i_longPeriod = input.int(10, "Long StdDev Period", minval=2, maxval=500) i_minPeriod = input.int(3, "Min Period", minval=2, maxval=500) i_maxPeriod = input.int(30, "Max Period", minval=2, maxval=500) i_source = input.source(close, "Source") dymi_val = dymi(i_source, i_basePeriod, i_shortPeriod, i_longPeriod, i_minPeriod, i_maxPeriod) plot(dymi_val, "DYMI", color=color.yellow, linewidth=2) hline(70, "Overbought", color=color.gray, linestyle=hline.style_dotted) hline(50, "Midline", color=color.gray, linestyle=hline.style_dotted) hline(30, "Oversold", color=color.gray, linestyle=hline.style_dotted)