The EMA is a one-line recurrence, as the explainer showed: each bar nudges the previous value toward the new price by a fixed fraction. Here it is in Python, both the explicit loop and the pandas shortcut.
What you’ll need
-
Python 3.9+,
pandas/numpy -
A
closeprice series.
From scratch
import numpy as np
def ema(values, period: int):
values = np.asarray(values, dtype=float)
alpha = 2.0 / (period + 1.0)
out = np.empty_like(values)
out[0] = values[0] # seed with the first price
for i in range(1, len(values)):
out[i] = alpha * values[i] + (1 - alpha) * out[i - 1]
return out
That’s the entire indicator: alpha = 2/(period+1), then each bar is a weighted
blend of the new price and the running value. It’s O(1) per bar and needs no
history buffer — the efficiency that made the EMA popular in the first place.
The pandas one-liner
df["ema20"] = df["close"].ewm(span=20, adjust=False).mean()
ewm(span=N, adjust=False) is exactly the recurrence above with alpha = 2/(N+1). This is the same call the MACD post uses for its
EMAs — no surprise, since the MACD is built from them.
A note on seeding (why versions differ early)
The recurrence needs a starting value, and there are two common choices:
-
Seed with the first price (what the loop above and
ewm(adjust=False)do). - Seed with an SMA of the first N prices (what some platforms do).
Both converge to the same line, but more slowly than you’d guess: for a 20-period
EMA the two seeds still differ by around 1e-4 a hundred bars in, only becoming
negligible after a couple hundred bars (the initial gap decays by a factor of
1−α each bar). If your EMA doesn’t match another platform’s early on but agrees
deep into the series, this seeding convention is almost always why — not a bug.
For long histories it’s irrelevant; for short ones, match your seed to whatever
you’re comparing against.
Gotchas
-
adjust=False. Withadjust=True(pandas’ default) you get a different, bias-corrected weighting that is not the standard trader’s EMA. Keep itFalse. - EMA ≠ SMA. If you want equal weighting, that’s the SMA. The whole point of the EMA is unequal, recency-biased weighting.
The lazy one-liner
import pandas_ta as ta
df["ema20"] = ta.ema(df["close"], length=20)
Same indicator elsewhere: MQL5, Pine Script, EasyLanguage, NinjaScript. Then test an EMA rule in AlgoGen.
This post is educational, not financial advice. Indicators describe the past; they don’t predict the future. Backtest anything before you risk real money on it.