Here’s the unpopular take: the SMA-vs-EMA debate is mostly noise. Both are lag filters applied to a noisy signal. If you understand how much weight each one puts on ‘right now’, you already understand the difference – and you’ll know when either one is quietly lying to you.
Most tutorials treat this as a trading question. It isn’t. It’s a signal-processing question that happens to show up in trading charts. Let’s treat it that way.
The one-line difference between SMA and EMA
SMA takes N prices and divides by N. Every price counts the same. EMA takes today’s price, gives it a small weight, and mixes it into yesterday’s EMA. Older prices don’t disappear – they fade exponentially.
That’s it. Everything else – “faster,” “smoother,” “better for day trading” – is a downstream consequence of that single design choice.
The math you actually need
Skip the SMA formula. You know it. EMA:
α = 2 / (N + 1)
EMA_today = (Price_today × α) + (EMA_yesterday × (1 - α))
Plug in N=20 and you get α ≈ 0.0952 – today’s closing price gets roughly 9.52% weight, the remaining 90.48% comes from the previous EMA. That’s the standard recursive EMA formula documented by VT Markets.
Now the number nobody quotes. The exact weight on the most recent bar by period length (via Steema’s technical reference): 18.8% for a 10-period EMA, 9.52% for a 20-EMA, and 3.92% for a 50-EMA.
Read that again. A “50-day EMA” gives today less than 4% influence. It’s barely more responsive than the 50-day SMA. The reactivity everyone talks about is a short-N phenomenon – at high N values, the two filters converge in behavior.
Computing both in pandas (and the trap)
Five lines. Save this – most tutorials get one line wrong.
import pandas as pd
prices = pd.Series([100, 102, 101, 105, 107, 110, 108, 109, 112, 115])
sma_5 = prices.rolling(window=5).mean()
ema_5 = prices.ewm(span=5, adjust=False).mean() # ← adjust=False matters
Turns out the default adjust=True in pandas uses a normalized weighted-average formula – mathematically valid, but it does NOT match the recursive EMA that TradingView, MetaTrader, and every broker platform use. The pandas docs confirm this (as of 2024): adjust=False is required for the classical recursive form.
If you compute an EMA in pandas and compare it to your broker’s chart, they won’t agree. You’ll spend an afternoon debugging your data pipeline before realizing pandas was doing what you asked – just not what you meant.
Watch out: Always pass
adjust=Falsewhen you want the recursive EMA. If you’re doing exploratory analysis and don’t care about matching an external chart,adjust=Trueis actually more statistically sound early in the series – but nobody in finance uses it.
Common pitfalls that tutorials skip
- The seeding problem. The formula needs an EMA_yesterday on day one. There isn’t one. Standard practice seeds it with an SMA of the first N periods (VT Markets documents this convention). If you start mid-stream – say, you only have data from March onward – your EMA will be biased for many periods before it converges to the “true” value. Charts that look weird in the first few weeks aren’t broken. They’re warming up.
- Irregular timestamps. Gaps in your data (weekends, missing bars, tick data) break the
halflifeparameter when used with thetimes=argument in pandas. There’s a documented bug (as of mid-2023 – check the issue for current status) where it silently returns wrong values instead of raising an error. Resample to a fixed frequency first. - “EMA has less lag” is only half true. It has less lag at the front of the series, but a longer memory tail. A 20-SMA forgets a price from 21 days ago completely. A 20-EMA still holds a whisper of a shock from 60 days back.
Which one actually catches the trend first
Everyone claims EMA is faster. Here’s what the numbers actually show, using the price series from the code above with N=5 (α=1/3, EMA seeded from first price of 100):
| Day | Price | SMA(5) | EMA(5) |
|---|---|---|---|
| 5 | 107 | 103.0 | 103.8 |
| 6 | 110 | 105.0 | 105.9 |
| 7 | 108 | 106.2 | 106.6 |
| 8 | 109 | 107.8 | 107.4 |
| 9 | 112 | 109.2 | 108.9 |
| 10 | 115 | 110.8 | 110.9 |
The EMA leads on the way up – but notice day 8: price stalls at 109, and the SMA actually sits higher (107.8 vs 107.4). EMA’s responsiveness cuts both ways. That tiny lead is why EMA is used in MACD and PPO – indicators that need fast response. Bollinger Bands use SMA because the center line needs to be stable, not jumpy.
When NOT to use EMA
- Sparse or event-driven data. Illiquid stocks, small-cap crypto, tick data with gaps – EMA over-weights whatever noise arrived last. SMA handles gaps more gracefully. And if you’re using pandas with irregular timestamps, see the
halflifebug above before you trust any output. - You want a support/resistance line. Traders use 50-SMA and 200-SMA as psychological levels precisely because they’re stable and widely watched. An EMA drifts too much.
On daily charts, Schwab’s education team points out that EMA “often gets whipsawed, making it less than ideal for triggering entries and exits.” None of this makes EMA bad – it just has a personality, and pretending it’s “SMA but better” will burn you.
The honest bottom line
SMA is a boxcar filter – flat weights, hard cutoff at N. EMA is a first-order IIR filter – soft weights, infinite tail. That’s the whole story. Everything downstream – MACD, ribbon strategies, crossover signals – follows from that one structural difference.
Pick SMA when you want stability and a clean cutoff. Pick EMA when you want responsiveness at short N and don’t mind the noise. At large N, the two converge anyway.
FAQ
Is a 20-EMA equivalent to a 20-SMA?
No. Same label, completely different weighting. The 20-SMA gives each of the last 20 prices exactly 5%. The 20-EMA gives today ~9.52% and includes prices from before day 20 – they just fade toward zero.
Why does my pandas EMA not match TradingView?
You called .ewm(span=N).mean() without adjust=False. Pandas defaults to a normalized weighting scheme that gives different values than the recursive EMA every trading platform uses. Fix: .ewm(span=N, adjust=False).mean(). That gets you to the right formula – though if your seed value differs from the platform’s, you’ll still see a small divergence in the first N bars while it warms up. That’s normal, not a bug.
Can I use SMA and EMA together?
Yes, and for regime detection it’s often smarter than picking one. A common pattern: long SMA as a slow filter (tells you the tide), short EMA as the fast signal (tells you the wave). The risk is combining two filters with different memory characteristics and assuming the result inherits the strengths of both – it doesn’t automatically. Backtest the combination; don’t assume it.
Next step: Open a Jupyter notebook, paste the pandas code above, and plot both sma_5 and ema_5 against your favorite dataset. Then flip adjust=False to adjust=True and plot the difference. Once you can see the mismatch with your own eyes, you’ll never forget which one to use.