mirror of
https://github.com/mihakralj/QuanTAlib.git
synced 2026-08-19 11:08:05 +00:00
- Implemented the TRAMA (Trend Regularity Adaptive Moving Average) class with adaptive EMA logic. - Added unit tests for TRAMA functionality, including constructor validation, basic calculations, state management, and robustness checks. - Created validation tests to ensure consistency across different modes of operation (streaming, batch, and static calculations). - Enhanced documentation for TRAMA, including performance profiles and quality metrics. - Updated workspace configuration by removing unnecessary folder references.
54 lines
2.1 KiB
Plaintext
54 lines
2.1 KiB
Plaintext
// The MIT License (MIT)
|
||
// © mihakralj
|
||
//@version=6
|
||
indicator("Non-Lag Moving Average (NLMA)", "NLMA", overlay=true)
|
||
|
||
//@function Calculates NLMA using damped cosine (fading sinusoid) FIR kernel
|
||
//@param source Series to calculate NLMA from
|
||
//@param period Lookback period - defines the cycle length of the cosine kernel
|
||
//@returns NLMA value, calculates from first bar using available data
|
||
//@description NonLagMA by Igorad (TrendLaboratory). Uses a damped cosine kernel
|
||
// (fading sinusoid) derived from FATL/SATL digital filter coefficient analysis.
|
||
// The cosine creates negative weights that subtract lagged price components,
|
||
// reducing lag while maintaining smoothness. Weight formula:
|
||
// w[i] = cos(2*PI*i / cycle) * (1 - i/cycle)
|
||
// where cycle = period, creating one full cosine oscillation with linear decay.
|
||
// Negative weights in the mid-section cancel lag (analogous to DEMA's 2*EMA-EMA2).
|
||
// Normalization by signed weight sum preserves DC gain = 1.
|
||
nlma(series float source, simple int period) =>
|
||
if period <= 0
|
||
runtime.error("Period must be greater than 0")
|
||
int p = math.min(bar_index + 1, period)
|
||
var int prev_p = 0
|
||
var array<float> cos_weights = array.new_float(period, 0.0)
|
||
// Recompute weights when effective period changes (warmup)
|
||
if p != prev_p
|
||
cos_weights := array.new_float(p, 0.0)
|
||
for j = 0 to p - 1
|
||
// Damped cosine: cosine oscillation × linear decay envelope
|
||
float angle = 2.0 * math.pi * j / p
|
||
float decay = 1.0 - float(j) / float(p)
|
||
array.set(cos_weights, j, math.cos(angle) * decay)
|
||
prev_p := p
|
||
float sum_wv = 0.0
|
||
float sum_w = 0.0
|
||
for i = 0 to p - 1
|
||
float price = source[i]
|
||
if not na(price)
|
||
float w = array.get(cos_weights, i)
|
||
sum_wv += price * w
|
||
sum_w += w
|
||
nz(sum_wv / sum_w, source)
|
||
|
||
// ---------- Main loop ----------
|
||
|
||
// Inputs
|
||
i_period = input.int(14, "Period", minval=1)
|
||
i_source = input.source(close, "Source")
|
||
|
||
// Calculation
|
||
nlma_value = nlma(i_source, i_period)
|
||
|
||
// Plot
|
||
plot(nlma_value, "NLMA", color=color.yellow, linewidth=2)
|