HEMA is a Hull-style moving average built entirely from **exponential smoothers**. It preserves the classic HMA pipeline (fast minus slow, then smooth) but replaces WMA sub-filters with EMAs whose alphas are tuned to produce **identical lag** to the WMA stages they replace. At period $N$: HEMA($N$) and HMA($N$) have the same theoretical group delay, but HEMA has infinite memory and smoother transient behavior.
The Hull Moving Average was designed around weighted moving averages (WMA), which have **finite memory** and are parameterized by a **window length**. EMA-family filters have **infinite memory** and are parameterized by a **decay rate**. Mapping HMA to an EMA world is not "replace WMA with EMA and hope." You need a clear definition of *what the period means* in EMA terms, and a de-lag combiner that stays consistent when the underlying smoother is exponential.
Early implementations used a half-life mapping ($\alpha = 1 - e^{-\ln 2 / N}$), but this produces EMA lag $\approx 1.44N$ instead of WMA lag $(N-1)/3$. The mismatch made HEMA(10) behave like HMA(30) in practice: roughly 4.6x more sluggish at every period. The current implementation uses a **WMA-lag-matched alpha** ($\alpha = 3/(N+2)$) that produces exactly the same lag as WMA($N$), making period comparisons between HMA and HEMA meaningful.
This makes "WMA-equivalent period" the primitive, and $\alpha$ derived. At $N=10$: $\alpha = 3/12 = 0.25$, lag $= 0.75/0.25 = 3.0$ bars, exactly matching WMA(10) lag.
Raw EMA recursion assumes the filter has run forever. Early outputs are biased toward zero (or the initial state). HEMA uses **exact bias compensation** during warmup by tracking each stage's decay:
If $y_t$ is the raw EMA state and $\beta = 1-\alpha$, the bias-corrected output is:
$$y_t^{*} = \frac{y_t}{1-\beta^{t}}$$
HEMA performs this independently for slow stage, fast stage, and smooth stage, and exits warmup only when **all three** decays are negligible.
**Practical implication:** early samples converge fast to a meaningful value. Use `IsHot` (or `WarmupPeriod`) if you need "fully settled" behavior for signal generation.
`Period = N` means "same lag as WMA(N)." HEMA(10) and HMA(10) have the same theoretical group delay. Earlier versions used half-life semantics where HEMA(10) was roughly equivalent to HMA(30). If you are upgrading from the half-life version, expect HEMA to now be noticeably more responsive at the same period.
Early values are bias-corrected, but "fully settled" still takes time. Use `IsHot` / `WarmupPeriod` before acting on signals. Expect roughly $3\sqrt{N}$ bars for all three stages to stabilize.
De-lag can overshoot. This is the price of reduced lag, same tradeoff as the DEMA/ZLEMA family. If overshoot is unacceptable, prefer a slower final smoother or reduce de-lag strength (requires custom variant).
Non-finite values are substituted with last valid value. Before the first valid input, output is `NaN`. If your upstream data source produces frequent gaps, consider pre-filtering or using a different indicator.
5.**Bar correction discipline**
Use `isNew=false` when correcting the last bar (same timestamp, revised OHLC). Failing to do so causes state drift and inconsistent results across runs.
Sub-periods use integer floor division (`period / 2`, `(int)Math.Sqrt(period)`) to match HMA behavior exactly. This means HEMA(5) uses halfPeriod=2 and sqrtPeriod=2, not 2.5 and 2.236.