mirror of
https://github.com/mihakralj/QuanTAlib.git
synced 2026-08-04 12:07:44 +00:00
87 lines
3.3 KiB
Plaintext
87 lines
3.3 KiB
Plaintext
// 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)
|