// The MIT License (MIT) // © mihakralj //@version=6 indicator("Lanczos (Sinc) Window Moving Average (LANCZOS)", "LANCZOS", overlay=true) //@function Computes Lanczos Window Moving Average — a symmetric FIR filter using the // normalized sinc function as the window shape. The Lanczos window is the // central lobe of a sinc function, providing excellent frequency-domain // characteristics with minimal Gibbs phenomenon ringing. //@param source Series to smooth //@param period Lookback window (>= 2) //@returns Lanczos-windowed moving average //@reference Lanczos, C. (1956). "Applied Analysis." Prentice-Hall. //@reference The Lanczos window w(k) = sinc(2k/(N-1) - 1) where sinc(x) = sin(πx)/(πx). // This is the simplest sinc-based window; higher-order Lanczos kernels use // sinc(x) * sinc(x/a) for Lanczos-a resampling (a=2 or 3 typical). //@optimized O(period) per bar for convolution; weights precomputed once lanczos(series float source, simple int period) => if period < 2 runtime.error("Period must be at least 2") float price = nz(source) // --- Circular buffer for rolling window --- var array buffer = array.new_float(period, na) var int head = 0 array.set(buffer, head, price) head := (head + 1) % period // --- Precompute Lanczos (sinc) window weights once --- // Lanczos window: w(k) = sinc(2k/(N-1) - 1) // sinc(x) = sin(π·x) / (π·x) for x != 0, sinc(0) = 1 var array weights = array.new_float(0) if barstate.isfirst float N = period - 1 float wsum = 0.0 for k = 0 to period - 1 float x = N > 0 ? (2.0 * k / N) - 1.0 : 0.0 float w = 0.0 if math.abs(x) < 1e-10 w := 1.0 // sinc(0) = 1 else float pi_x = math.pi * x w := math.sin(pi_x) / pi_x // Clamp negative weights to 0 for pure Lanczos window // (sinc sidelobes are negative but we keep them for fidelity) array.push(weights, w) wsum += w // Normalize weights to sum exactly 1.0 if wsum > 0 for j = 0 to period - 1 array.set(weights, j, array.get(weights, j) / wsum) int count = math.min(bar_index + 1, period) if count < period price else // --- Apply Lanczos convolution via circular buffer --- // Buffer: head points to next-write = oldest entry // Weight[0] = oldest bar, Weight[period-1] = newest float result = 0.0 for j = 0 to period - 1 int idx = (head + j) % period float val = nz(array.get(buffer, idx)) result += val * array.get(weights, j) result // ── Inputs ────────────────────────────────────────────────────────────── src = input.source(close, "Source") per = input.int(14, "Period", minval=2) // ── Plot ──────────────────────────────────────────────────────────────── plot(lanczos(src, per), "LANCZOS", color.new(color.yellow, 0), 2)