// The MIT License (MIT) // © mihakralj //@version=6 indicator("Non-Lag Moving Average (NLMA)", "NLMA", overlay=true) //@function Calculates NLMA using damped cosine (fading sinusoid) FIR kernel //@param source Series to calculate NLMA from //@param period Lookback period - defines the cycle length of the cosine kernel //@returns NLMA value, calculates from first bar using available data //@description NonLagMA by Igorad (TrendLaboratory). Uses a damped cosine kernel // (fading sinusoid) derived from FATL/SATL digital filter coefficient analysis. // The cosine creates negative weights that subtract lagged price components, // reducing lag while maintaining smoothness. Weight formula: // w[i] = cos(2*PI*i / cycle) * (1 - i/cycle) // where cycle = period, creating one full cosine oscillation with linear decay. // Negative weights in the mid-section cancel lag (analogous to DEMA's 2*EMA-EMA2). // Normalization by signed weight sum preserves DC gain = 1. nlma(series float source, simple int period) => if period <= 0 runtime.error("Period must be greater than 0") int p = math.min(bar_index + 1, period) var int prev_p = 0 var array cos_weights = array.new_float(period, 0.0) // Recompute weights when effective period changes (warmup) if p != prev_p cos_weights := array.new_float(p, 0.0) for j = 0 to p - 1 // Damped cosine: cosine oscillation × linear decay envelope float angle = 2.0 * math.pi * j / p float decay = 1.0 - float(j) / float(p) array.set(cos_weights, j, math.cos(angle) * decay) prev_p := p float sum_wv = 0.0 float sum_w = 0.0 for i = 0 to p - 1 float price = source[i] if not na(price) float w = array.get(cos_weights, i) sum_wv += price * w sum_w += w nz(sum_wv / sum_w, source) // ---------- Main loop ---------- // Inputs i_period = input.int(14, "Period", minval=1) i_source = input.source(close, "Source") // Calculation nlma_value = nlma(i_source, i_period) // Plot plot(nlma_value, "NLMA", color=color.yellow, linewidth=2)