An EMA variant that subtracts an estimate of its own lag from the input — reducing lag, but never eliminating it.
The Zero-Lag Exponential Moving Average (ZLEMA) is a modified EMA introduced by John Ehlers and Ric Way that tries to compensate for the lag inherent in every moving average. It does this by feeding the EMA a 'de-lagged' version of price: the current price plus the difference between the current price and the price roughly half the lookback period ago. Despite the name, ZLEMA is not actually zero lag — no causal moving average can be, because every value is computed only from past data. The name describes an error-correction technique, not a mathematical property, and the reduction in lag is paid for with overshoot: the line can swing past price at turning points and produce extra false direction changes. Like every moving average it is a descriptive summary of past price, not a prediction of future price. This is educational material only, not financial advice, and all trading carries risk of loss.
ZLEMA starts from a simple observation: an EMA of length N lags price by approximately (N − 1) / 2 bars. To offset that, ZLEMA constructs a synthetic input series before smoothing. First it computes the lag estimate, lag = floor((N − 1) / 2). Then it builds the de-lagged input: deLagged = price + (price − price[lag]). The term (price − price[lag]) measures how much price has moved over the lag window; adding it to the current price effectively extrapolates the recent move forward, pre-compensating for the smoothing delay to come. Finally, a standard EMA of length N is applied to this de-lagged series. When price trends steadily, the extrapolation roughly cancels the EMA's delay and the line hugs price closely. But the compensation term is itself a momentum estimate built from past bars: when price reverses sharply, the extrapolation points the wrong way for several bars and the ZLEMA overshoots before correcting. So the answer to 'is it really zero lag?' is no — lag is reduced on average, in exchange for overshoot and extra sensitivity to noise. The trade-off is structural, not a settings problem.
Typical lengths mirror EMA conventions: around 20 for short-term reads, 50 for an intermediate view, with the close as the usual source. The internal lag offset is derived from the length as floor((length − 1) / 2), so it is not a separate setting in most implementations. There is no 'correct' length — shorter settings amplify both the responsiveness and the overshoot, and any choice should be tested rather than assumed.
//@version=6
indicator("Zero-Lag EMA (ZLEMA)", overlay = true)
length = input.int(20, "Length", minval = 2)
src = input.source(close, "Source")
// Lag estimate for an EMA of this length
lagBars = math.floor((length - 1) / 2)
// De-lagged input: extrapolate the move over the lag window
deLagged = src + (src - src[lagBars])
zlema = ta.ema(deLagged, length)
emaRef = ta.ema(src, length)
plot(zlema, "ZLEMA", color = color.teal, linewidth = 2)
plot(emaRef, "EMA (reference)", color = color.gray)Pro tip: Plot ZLEMA and a standard EMA of the same length together and study the bars around sharp reversals: you will see the ZLEMA turn earlier in trends but overshoot at turns — that is the entire trade-off, made visible. Treat 'zero lag' as a naming claim to verify, not a property to rely on, and pair any moving average with price structure and defined risk. Educational context only, not financial advice; no indicator removes the risk of loss.
Educational only — not financial advice, not a recommendation to trade. No indicator is predictive; trading involves substantial risk of loss.