// Licensed under the Apache License, Version 2.0 // © mihakralj //@version=6 indicator("Volatility Adjusted Moving Average (VAMA)", "VAMA", overlay=true) //@function Calculates VAMA by adjusting MA length based on volatility ratio using ATR //@param source Series to calculate VAMA from //@param base_length Base lookback period for the moving average //@param short_atr_period Short-term ATR period for current volatility measurement //@param long_atr_period Long-term ATR period for reference volatility measurement //@param min_length Minimum allowed adjusted length //@param max_length Maximum allowed adjusted length //@returns VAMA value //@optimized Uses RMA compensator for ATR and circular buffer for O(1) sum updates vama(series float source, simple int base_length, simple int short_atr_period=10, simple int long_atr_period=50, simple int min_length=5, simple int max_length=100) => var float prevClose = na float tr1 = high - low float tr2 = math.abs(high - prevClose) float tr3 = math.abs(low - prevClose) float trueRange = math.max(tr1, tr2, tr3) prevClose := close float EPSILON = 1e-10 var float raw_rma_short = 0.0 var float e_short = 1.0 var float raw_rma_long = 0.0 var float e_long = 1.0 float short_atr = na float long_atr = na if not na(trueRange) float alpha_short = 1.0 / float(short_atr_period) float beta_short = 1.0 - alpha_short raw_rma_short := (raw_rma_short * (short_atr_period - 1) + trueRange) / short_atr_period e_short := beta_short * e_short short_atr := e_short > EPSILON ? raw_rma_short / (1.0 - e_short) : raw_rma_short float alpha_long = 1.0 / float(long_atr_period) float beta_long = 1.0 - alpha_long raw_rma_long := (raw_rma_long * (long_atr_period - 1) + trueRange) / long_atr_period e_long := beta_long * e_long long_atr := e_long > EPSILON ? raw_rma_long / (1.0 - e_long) : raw_rma_long float volatility_ratio = not na(short_atr) and not na(long_atr) and short_atr != 0.0 ? long_atr / short_atr : 1.0 float calc_length = base_length * volatility_ratio int adjusted_length = int(math.max(min_length, math.min(max_length, calc_length))) var array buffer = array.new_float(max_length, na) var int head = 0 var float sum = 0.0 var int valid_count = 0 if array.size(buffer) != max_length buffer := array.new_float(max_length, na) head := 0 sum := 0.0 valid_count := 0 float oldest = array.get(buffer, head) if not na(oldest) sum -= oldest valid_count -= 1 if not na(source) sum += source valid_count += 1 array.set(buffer, head, source) head := (head + 1) % max_length float avg = valid_count > 0 ? sum / valid_count : source int actual_count = math.min(valid_count, adjusted_length) float partial_sum = 0.0 int partial_count = 0 for i = 0 to actual_count - 1 int idx = (head - 1 - i + max_length) % max_length float val = array.get(buffer, idx) if not na(val) partial_sum += val partial_count += 1 partial_count > 0 ? partial_sum / partial_count : nz(avg, source) // ---------- Main loop ---------- // Inputs i_base_length = input.int(20, "Base Length", minval=1) i_source = input.source(close, "Source") i_short_atr = input.int(10, "Short ATR Period", minval=1) i_long_atr = input.int(50, "Long ATR Period", minval=1) i_min_length = input.int(5, "Minimum Length", minval=1) i_max_length = input.int(100, "Maximum Length", minval=1) // Calculation vama_value = vama(i_source, i_base_length, i_short_atr, i_long_atr, i_min_length, i_max_length) // Plot plot(vama_value, "VAMA", color=color.yellow, linewidth=2)