// Licensed under the Apache License, Version 2.0 // © mihakralj //@version=6 indicator("RAVI: Chande Range Action Verification Index", "RAVI", overlay=false) //@function Calculates Range Action Verification Index using short/long SMA divergence //@param shortPeriod Lookback period for fast SMA (default: 7, ~10% of longPeriod) //@param longPeriod Lookback period for slow SMA (default: 65, ~13 weeks daily) //@returns RAVI value as absolute percentage divergence between short and long SMAs //@references Tushar Chande, "Beyond Technical Analysis", Wiley, 2nd ed. (2001), pp. 66-70 //@optimized O(1) per bar via circular buffer running sums for both SMAs ravi(simple int shortPeriod, simple int longPeriod) => if shortPeriod <= 0 runtime.error("Short period must be greater than 0") if longPeriod <= 0 runtime.error("Long period must be greater than 0") if shortPeriod >= longPeriod runtime.error("Short period must be less than long period") // Circular buffer for short SMA (O(1) running sum) var array shortBuf = array.new_float(shortPeriod, na) var int shortHead = 0 var int shortFilled = 0 var float shortSum = 0.0 // Circular buffer for long SMA (O(1) running sum) var array longBuf = array.new_float(longPeriod, na) var int longHead = 0 var int longFilled = 0 var float longSum = 0.0 // Update short SMA buffer float oldShort = array.get(shortBuf, shortHead) if not na(oldShort) shortSum -= oldShort shortSum += close array.set(shortBuf, shortHead, close) shortFilled := math.min(shortFilled + 1, shortPeriod) shortHead := (shortHead + 1) % shortPeriod // Update long SMA buffer float oldLong = array.get(longBuf, longHead) if not na(oldLong) longSum -= oldLong longSum += close array.set(longBuf, longHead, close) longFilled := math.min(longFilled + 1, longPeriod) longHead := (longHead + 1) % longPeriod float result = na if shortFilled >= shortPeriod and longFilled >= longPeriod // Step 1: Compute short-period SMA float smaShort = shortSum / shortPeriod // Step 2: Compute long-period SMA float smaLong = longSum / longPeriod // Step 3: RAVI = |SMA(short) - SMA(long)| / SMA(long) * 100 // Guard against division by zero (long SMA at zero) if math.abs(smaLong) > 1e-10 result := math.abs(smaShort - smaLong) / math.abs(smaLong) * 100.0 result // ---------- Main loop ---------- // Inputs i_short = input.int(7, "Short Period", minval=1, maxval=100, tooltip="Fast SMA period (~10% of long period; Chande default: 7)") i_long = input.int(65, "Long Period", minval=2, maxval=500, tooltip="Slow SMA period (~13 weeks daily; Chande default: 65)") i_threshold = input.float(3.0, "Threshold", minval=0.0, maxval=20.0, step=0.5, tooltip="Trend/range classification level (Chande default: 3%)") // Calculation ravi_value = ravi(i_short, i_long) // Plot plot(ravi_value, "RAVI", color=color.yellow, linewidth=2) hline(i_threshold, "Threshold", color=color.new(color.red, 50), linestyle=hline.style_dashed) hline(0, "Zero Line", color=color.new(color.gray, 70), linestyle=hline.style_dotted)