// Licensed under the Apache License, Version 2.0
// © mihakralj
//@version=6
indicator("Least Mean Squares Adaptive Filter (LMS)", "LMS", overlay=true)
//@function Applies Widrow-Hoff LMS adaptive FIR filter to input series
//@param src Input series to filter
//@param order Number of FIR filter taps (adaptive weights)
//@param mu Step size (learning rate) controlling adaptation speed
//@returns Adaptively filtered series
//@optimized Uses normalized LMS weight update with O(order) complexity per bar
lms(series float src, simple int order, simple float mu) =>
if order < 2
runtime.error("Filter order must be >= 2")
var array<float> w = array.new_float(order, 0.0)
// predict src[0] from src[1]..src[order]
float y = 0.0
float norm_sq = 0.0
for i = 0 to order - 1
float xi = nz(src[i + 1], 0.0)
y += array.get(w, i) * xi
norm_sq += xi * xi
float e = nz(src, 0.0) - y
// normalized step size for input-power-independent convergence
float mu_eff = mu / (1e-10 + norm_sq)
for i = 0 to order - 1
float xi = nz(src[i + 1], 0.0)
array.set(w, i, array.get(w, i) + mu_eff * e * xi)
y
// ---------- Main loop ----------
// Inputs
i_order = input.int(16, "Filter Order (taps)", minval=2, maxval=128)
i_mu = input.float(0.5, "Learning Rate (mu)", minval=0.01, maxval=1.99, step=0.05)
i_source = input.source(close, "Source")
// Calculation
lms_val = lms(i_source, i_order, i_mu)
// Plot
plot(lms_val, "LMS", color=color.yellow, linewidth=2)