Files
QuanTAlib/lib/trends_FIR/lanczos/lanczos.pine
T

75 lines
3.3 KiB
Plaintext

// 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<float> 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<float> 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)