mirror of
https://github.com/mihakralj/QuanTAlib.git
synced 2026-08-03 03:47:42 +00:00
93 lines
2.7 KiB
Plaintext
93 lines
2.7 KiB
Plaintext
// 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)
|