Files

87 lines
3.3 KiB
Plaintext
Raw Permalink Normal View History

// Licensed under the Apache License, Version 2.0
// © mihakralj
//@version=6
indicator("Non-Lag Moving Average (NLMA)", "NLMA", overlay=true)
2026-02-23 17:27:35 -08:00
//@function Calculates NLMA using the original Igorad (TrendLaboratory) kernel
//@param source Series to calculate NLMA from
2026-02-23 17:27:35 -08:00
//@param period Length parameter (conceptual averaging period)
//@returns NLMA value, returns price during warmup
//@description NonLagMA by Igorad (TrendLaboratory). Original two-phase kernel:
// Phase 1 (focus zone, i=0..Length-2): t = i/(Length-2), beta=cos(pi*t), g=1 for t<=0.5 else 1/(Coeff*t+1)
// Phase 2 (cycle zone, i=Length-1..Len-2): oscillating cosine with 1/(Coeff*t+1) decay
// Coeff = 3*pi. Kernel length Len = 4*Length + (Length-1) = 5*Length-1.
// alfa[i] = g * beta. Normalized by signed sum preserves DC gain = 1.
nlma(series float source, simple int period) =>
2026-02-23 17:27:35 -08:00
if period < 2
runtime.error("Period must be at least 2")
float price = nz(source)
// Original Igorad parameters
int phase = period - 1 // Phase zone length
int cycle = 4 // Default cycle parameter
int flen = period * 4 + phase // Full kernel length = 5*period - 1
// --- Circular buffer for rolling window ---
var array<float> buffer = array.new_float(flen, na)
var int head = 0
array.set(buffer, head, price)
head := (head + 1) % flen
// --- Precompute Igorad kernel weights once ---
var array<float> weights = array.new_float(0)
var float weightSum = 0.0
if barstate.isfirst
float coeff = 3.0 * math.pi
float wsum = 0.0
for i = 0 to flen - 2
float t = 0.0
if i <= phase - 1
// Phase zone: t ramps from 0 to 1
t := phase > 1 ? float(i) / float(phase - 1) : 0.0
else
// Cycle zone: t continues from 1 upward
float numer = float(i - phase + 1) * float(2 * cycle - 1)
2026-02-23 17:27:35 -08:00
float denom = float(cycle * period - 1)
t := 1.0 + (denom > 0 ? numer / denom : 0.0)
float beta = math.cos(math.pi * t)
float g = t <= 0.5 ? 1.0 : 1.0 / (coeff * t + 1.0)
float w = g * beta
array.push(weights, w)
wsum += w
// Last tap (i = flen-1) has weight 0 (original loop goes to Len-2)
array.push(weights, 0.0)
weightSum := wsum
int count = math.min(bar_index + 1, flen)
if count < flen
price
else
// --- Convolution via circular buffer ---
// head = next write = oldest | weights[0] maps to oldest bar
// Original MQL4: alfa[0] = newest price, alfa[Len-1] = oldest
// Our buffer: (head+0) = oldest, (head+flen-1) = newest
// So we need to reverse: oldest bar × weights[flen-1], newest bar × weights[0]
float nlma_sum = 0.0
for j = 0 to flen - 1
int idx = (head + j) % flen
float val = nz(array.get(buffer, idx))
nlma_sum += val * array.get(weights, flen - 1 - j)
math.abs(weightSum) > 1e-15 ? nlma_sum / weightSum : price
// ---------- Main loop ----------
// Inputs
2026-02-23 17:27:35 -08:00
i_period = input.int(14, "Period", minval=2)
i_source = input.source(close, "Source")
// Calculation
nlma_value = nlma(i_source, i_period)
// Plot
plot(nlma_value, "NLMA", color=color.yellow, linewidth=2)