Files

87 lines
3.3 KiB
Plaintext
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// Licensed under the Apache License, Version 2.0
// © mihakralj
//@version=6
indicator("Non-Lag Moving Average (NLMA)", "NLMA", overlay=true)
//@function Calculates NLMA using the original Igorad (TrendLaboratory) kernel
//@param source Series to calculate NLMA from
//@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) =>
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)
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
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)