// Licensed under the Apache License, Version 2.0 // © mihakralj //@version=6 indicator("HEMA (Exponential Hull Analog)", "HEMAx", overlay=true) //@function Calculates Hull Exponential Moving Average (EMA-domain HMA analog) //@param src Series to calculate HEMA from //@param N Period (matches HMA lag at same period) //@returns HEMA value with reduced lag //@optimized Uses cascaded EMA de-lag structure with O(1) complexity per bar // WMA-lag-matched alpha: EMA lag = (P-1)/3, same as WMA(P) alphaFromWmaLag(int p) => float fp = math.max(float(p), 1.0) 3.0 / (fp + 2.0) // Exponential Hull Analog (EMA-domain HMA) hema(series float src, simple int N) => // --- guardrails --- int n = math.max(N, 2) // HMA-like structure needs N>=2 // --- sub-periods matching HMA's integer floor divisions --- int halfN = int(n / 2) // floor(N/2), same as HMA int sqrtN = math.max(int(math.sqrt(n)), 1) // floor(sqrt(N)), same as HMA // --- alphas (lag-matched to WMA) --- float aS = alphaFromWmaLag(n) float aF = alphaFromWmaLag(math.max(halfN, 1)) float aM = alphaFromWmaLag(math.max(sqrtN, 1)) float bS = 1.0 - aS float bF = 1.0 - aF float bM = 1.0 - aM // --- lag-derived ratio for the de-lag combiner --- float lagS = bS / aS float lagF = bF / aF float r = lagF / lagS r := math.min(math.max(r, 0.0), 0.999999) // keep denom sane // --- state (unbiased EMA warmup) --- var bool warmup = true var float dS = 1.0 var float dF = 1.0 var float dM = 1.0 var float eSraw = 0.0 var float eFraw = 0.0 var float eMraw = 0.0 float eS = na float eF = na float out = na // raw EMAs eSraw := aS * (src - eSraw) + eSraw eFraw := aF * (src - eFraw) + eFraw if warmup // update decays for unbiased correction dS *= bS dF *= bF dM *= bM float invS = 1.0 / math.max(1.0 - dS, 1e-12) float invF = 1.0 / math.max(1.0 - dF, 1e-12) float invM = 1.0 / math.max(1.0 - dM, 1e-12) eS := eSraw * invS eF := eFraw * invF float deLag = (eF - r * eS) / (1.0 - r) eMraw := aM * (deLag - eMraw) + eMraw out := eMraw * invM // end warmup only when ALL stages are effectively unbiased warmup := math.max(dS, math.max(dF, dM)) > 1e-10 else eS := eSraw eF := eFraw float deLag = (eF - r * eS) / (1.0 - r) eMraw := aM * (deLag - eMraw) + eMraw out := eMraw out // Inputs i_period = input.int(10, "Period", minval=2) i_source = input.source(close, "Source") hema_value = hema(i_source, i_period) plot(hema_value, "HEMA", color=color.yellow, linewidth=2)