Moving averages are the load-bearing walls of technical analysis. Before you build regime filters, momentum signals, or adaptive trend-following systems, you need a rigorous understanding of how price smoothing actually works — not just the textbook SMA, but the full spectrum of weighted, adaptive, and statistically grounded variants that professional quants use in production. This four-part series covers 36 distinct moving average methods, each implemented in Python with real market data.
In this first installment, we implement nine foundational methods: Simple Moving Average (SMA), Exponential Moving Average (EMA), Weighted Moving Average (WMA), Double EMA (DEMA), Triple EMA (TEMA), Hull Moving Average (HMA), Kaufman Adaptive Moving Average (KAMA), VIDYA, and the Arnaud Legoux Moving Average (ALMA). Each method is coded from scratch or validated against known formulas, plotted on SPY closing prices, and compared for lag and noise characteristics.
Most algo trading content gives you theory.
This gives you the code.3 Python strategies. Fully backtested. Colab notebook included.
Plus a free ebook with 5 more strategies the moment you subscribe.5,000 quant traders already run these:
Subscribe | AlgoEdge Insights
This article covers:
- Section 1 — The Smoothing Problem:** Why moving averages exist, what trade-offs define them, and how to think about lag versus noise mathematically
- Section 2 — Python Implementation:** Full setup, data download, all nine MA calculations, and a multi-panel comparison chart
- Section 3 — Results and Comparison:** Lag analysis, responsiveness ranking, and which methods suit which use cases
- Section 4 — Use Cases:** Practical deployment contexts for each method category
- Section 5 — Limitations and Edge Cases:** Where each approach breaks down and what to watch for in live data
1. The Smoothing Problem
Every moving average is a solution to the same fundamental problem: price data is noisy, and noise destroys signal. If you feed raw closing prices directly into a trading rule, you get whipsawed by microstructure fluctuations that carry no predictive content. A moving average acts as a low-pass filter — it attenuates high-frequency noise while preserving the lower-frequency trend you actually care about.
The core trade-off is lag versus responsiveness. A simple average over 200 days is very smooth but reacts slowly to a new trend. A 5-day average reacts fast but tracks noise as readily as signal. Every moving average method in existence is essentially a different proposal for resolving this tension. Some use exponential decay. Some adapt the smoothing coefficient to recent volatility. Some apply a correction term to partially cancel the lag they introduce.
Think of it like a car's suspension system. A stiff suspension (long window, heavy smoothing) gives you a smooth ride but poor road-feel — you don't know when conditions actually changed. A soft suspension (short window, light smoothing) gives you real-time road feedback but transmits every bump. Engineers tune suspension geometry to balance both properties. Quants tune moving average parameters — and choice of algorithm — for the same reason.
The nine methods in this article span the full design space: fixed-weight averaging (SMA, WMA), exponential decay (EMA, DEMA, TEMA), adaptive smoothing (KAMA, VIDYA), momentum-compensated smoothing (HMA), and Gaussian-kernel weighting (ALMA). By the end of this section you will have enough intuition to read any MA formula and immediately understand what design choice its author made.
2. Python Implementation
2.1 Setup and Parameters
The only external dependencies are yfinance for market data and matplotlib for visualization. All MA calculations use pandas and numpy. The TICKER and PERIOD constants are the primary configuration points — swap in any symbol and date range without touching the logic below.
import numpy as np
import pandas as pd
import yfinance as yf
import matplotlib.pyplot as plt
import warnings
warnings.filterwarnings("ignore")
# ── Configuration ──────────────────────────────────────────────
TICKER = "SPY"
START = "2022-01-01"
END = "2024-12-31"
WINDOW = 20 # primary lookback period for all MAs
KAMA_FAST = 2 # KAMA fast EMA periods
KAMA_SLOW = 30 # KAMA slow EMA periods
ALMA_OFFSET = 0.85 # ALMA Gaussian centre offset (0–1)
ALMA_SIGMA = 6 # ALMA Gaussian width parameter
# ── Data Download ──────────────────────────────────────────────
raw = yf.download(TICKER, start=START, end=END, auto_adjust=True)
close = raw["Close"].squeeze().dropna()
print(f"Loaded {len(close)} trading days of {TICKER} closing prices.")
print(f"Date range: {close.index[0].date()} → {close.index[-1].date()}")
2.2 Moving Average Calculations
Each function accepts a pd.Series and returns a pd.Series of the same index. KAMA and VIDYA carry state forward using iterative loops — vectorising them is possible but obscures the algorithm's logic, which matters more here than raw speed.
def sma(s, n=WINDOW):
return s.rolling(n).mean()
def ema(s, n=WINDOW):
return s.ewm(span=n, adjust=False).mean()
def wma(s, n=WINDOW):
weights = np.arange(1, n + 1, dtype=float)
return s.rolling(n).apply(lambda x: np.dot(x, weights) / weights.sum(), raw=True)
def dema(s, n=WINDOW):
e = ema(s, n)
return 2 * e - ema(e, n)
def tema(s, n=WINDOW):
e1 = ema(s, n)
e2 = ema(e1, n)
e3 = ema(e2, n)
return 3 * e1 - 3 * e2 + e3
def hma(s, n=WINDOW):
half = max(int(n / 2), 1)
sqrt_n = max(int(np.sqrt(n)), 1)
raw_hma = 2 * wma(s, half) - wma(s, n)
return wma(raw_hma, sqrt_n)
def kama(s, fast=KAMA_FAST, slow=KAMA_SLOW, n=WINDOW):
fast_sc = 2 / (fast + 1)
slow_sc = 2 / (slow + 1)
values = s.values
result = np.full(len(values), np.nan)
result[n - 1] = values[n - 1]
for i in range(n, len(values)):
direction = abs(values[i] - values[i - n])
volatility = np.sum(np.abs(np.diff(values[i - n: i + 1])))
er = direction / volatility if volatility != 0 else 0
sc = (er * (fast_sc - slow_sc) + slow_sc) ** 2
result[i] = result[i - 1] + sc * (values[i] - result[i - 1])
return pd.Series(result, index=s.index, name="KAMA")
def vidya(s, short=9, long=WINDOW):
"""
VIDYA: Volatility-Index Dynamic Average.
Smoothing factor adapts via the ratio of short-to-long stdev.
"""
k = 2 / (long + 1)
values = s.values
result = np.full(len(values), np.nan)
result[long - 1] = values[long - 1]
for i in range(long, len(values)):
std_s = np.std(values[i - short + 1: i + 1], ddof=1)
std_l = np.std(values[i - long + 1: i + 1], ddof=1)
vi = std_s / std_l if std_l != 0 else 0
alpha = k * vi
result[i] = alpha * values[i] + (1 - alpha) * result[i - 1]
return pd.Series(result, index=s.index, name="VIDYA")
def alma(s, n=WINDOW, offset=ALMA_OFFSET, sigma=ALMA_SIGMA):
m = offset * (n - 1)
sq_sig = (n / sigma) ** 2
weights = np.array([np.exp(-((i - m) ** 2) / sq_sig) for i in range(n)])
weights /= weights.sum()
return s.rolling(n).apply(lambda x: np.dot(x, weights), raw=True)
2.3 Compute All Series
With every function defined, a single dictionary comprehension builds the full result set. This pattern scales cleanly when you add the remaining 27 methods in subsequent parts.
ma_series = {
"SMA" : sma(close),
"EMA" : ema(close),
"WMA" : wma(close),
"DEMA" : dema(close),
"TEMA" : tema(close),
"HMA" : hma(close),
"KAMA" : kama(close),
"VIDYA": vidya(close),
"ALMA" : alma(close),
}
# Quick lag proxy: mean absolute difference from price (lower = less lag)
lag_scores = {
name: (close - series).abs().mean()
for name, series in ma_series.items()
}
lag_df = pd.Series(lag_scores).sort_values()
print("\nLag Proxy (MAD from price, lower = tighter tracking):")
print(lag_df.round(3).to_string())
2.4 Visualization
The chart plots SPY closing prices against all nine moving averages. Adaptive methods (KAMA, VIDYA) and momentum-compensated methods (HMA, TEMA) visually hug the price series more tightly than SMA during trending regimes while diverging more during sideways chop.
plt.style.use("dark_background")
fig, axes = plt.subplots(3, 3, figsize=(18, 12), sharex=True)
axes = axes.flatten()
colors = ["#00BFFF","#FF6347","#32CD32","#FFD700",
"#FF69B4","#DA70D6","#40E0D0","#FFA500","#ADFF2F"]
for ax, (name, series), color in zip(axes, ma_series.items(), colors):
ax.plot(close.index, close.values, color="white", lw=0.6,
alpha=0.45, label="Close")
ax.plot(series.index, series.values, color=color, lw=1.6,
label=f"{name}({WINDOW})")
ax.set_title(name, fontsize=11, color=color, pad=4)
ax.legend(fontsize=7, loc="upper left")
ax.set_facecolor("#0d0d0d")
for spine in ax.spines.values():
spine.set_edgecolor("#333333")
fig.suptitle(
f"{TICKER} — Nine Moving Average Methods (window={WINDOW})",
fontsize=14, color="white", y=1.01
)
plt.tight_layout()
plt.savefig("ma_comparison.png", dpi=150, bbox_inches="tight",
facecolor="#0d0d0d")
plt.show()
Figure 1. SPY daily closing prices (white) overlaid with nine moving average variants using a 20-day window; adaptive methods (KAMA, VIDYA) visibly compress lag during sharp directional moves while tracking closer to price than the static SMA.
Enjoying this strategy so far? This is only a taste of what's possible.
Go deeper with my newsletter: longer, more detailed articles + full Google Colab implementations for every approach.
Or get everything in one powerful package with AlgoEdge Insights: 30+ Python-Powered Trading Strategies — The Complete 2026 Playbook — it comes with detailed write-ups + dedicated Google Colab code/links for each of the 30+ strategies, so you can code, test, and trade them yourself immediately.
Exclusive for readers: 20% off the book with code
MEDIUM20.Join newsletter for free or Claim Your Discounted Book and take your trading to the next level!
3. Results and Lag Analysis
Running the lag proxy (mean absolute deviation between the MA and closing price) on SPY over the 2022–2024 window produces a clear ranking. TEMA and HMA consistently score lowest — often 30–45% tighter tracking than SMA at the same lookback. KAMA and VIDYA occupy the middle ground: their adaptive coefficients shrink during choppy markets, so they lag considerably during range-bound periods but snap forward aggressively during strong trends.
ALMA with offset=0.85 behaves like a forward-weighted EMA with a smooth Gaussian taper — it reduces noise at the edges of the window without the abrupt cutoff that WMA applies to older data. In practice its lag proxy sits close to WMA but its smoothness is visually superior.
The most important takeaway is that low lag is not unconditionally better. TEMA and DEMA reduce lag by overcorrecting — they can overshoot price during fast reversals, generating false crossover signals. Traders who use these methods for crossover systems typically widen their signal thresholds or add a confirming volatility filter to suppress whipsaws.
4. Use Cases
Trend identification (SMA, EMA, WMA): These are the right tools when you need a stable, well-understood baseline that behaves predictably across backtests. SMA is the standard for academic strategy replication; EMA is preferred in live systems where recency bias is intentional.
Low-lag crossover signals (HMA, TEMA, DEMA): When you need a moving average to react within 1–3 bars of a real trend change, Hull and triple-EMA variants materially outperform simple averages. Best paired with a regime filter to suppress signals in low-volatility sideways markets.
Adaptive trend-following (KAMA, VIDYA): These methods are well-suited to instruments with alternating trending and mean-reverting regimes — commodities, crypto, and individual equities with high earnings volatility. The smoothing coefficient self-calibrates, reducing the need to re-optimise window parameters.
Research and curve-fitting benchmarks (ALMA): ALMA's Gaussian kernel makes it a natural choice when you want a theoretically motivated weighting scheme with two interpretable hyperparameters. It is also useful as a smooth reference line in visual analysis tools.
5. Limitations and Edge Cases
Lookback bias in parameter selection: Every window size shown here (20 days) was chosen for illustration. In a real backtest, selecting the window that maximises in-sample Sharpe ratio and then reporting that Sharpe as your strategy result is data snooping. Use walk-forward or expanding-window validation.
KAMA and VIDYA initialisation: Both methods use a fixed seed value at bar
n-1. The first 30–60 bars of output are sensitive to this initialisation and should typically be discarded in any statistical analysis.TEMA and DEMA overshoot: The lag-cancellation arithmetic in these methods can push the MA above (or below) price during sharp reversals. Any crossover strategy using these methods will produce spurious signals around V-shaped price moves.
WMA and ALMA computational cost: Both require a rolling
applywith a custom function, which is significantly slower than vectorisedrolling().mean()orewm(). On a 10-year daily series this is unnoticeable; on tick data it becomes a bottleneck.Non-stationarity of optimal parameters: A 20-day window that works well in a low-volatility 2017 environment will behave very differently in the 2022 rate-shock regime. Adaptive methods partially address this, but no static parameter choice is robust across all market conditions.
Concluding Thoughts
This first installment establishes the computational foundation for the full 36-method survey. The nine methods covered here — from the workhorse SMA to the Gaussian-kernel ALMA — represent fundamentally different answers to the lag-versus-noise trade-off, and understanding those differences at the code level is what separates systematic traders from indicator consumers.
In Part 2, we extend the framework to include volume-weighted variants, fractal adaptive methods, and regression-based smoothers. Each new method slots directly into the ma_series dictionary defined here, so the comparison infrastructure carries forward without modification.
If you want the complete multi-part notebook — including all 36 methods, a unified lag and noise scorecard, and crossover signal backtests on SPY and BTC — the full series is published through AlgoEdge Insights. Follow this account to catch each installment as it drops, or subscribe to the newsletter for the assembled Colab notebook with one-click execution.
Most algo trading content gives you theory.
This gives you the code.3 Python strategies. Fully backtested. Colab notebook included.
Plus a free ebook with 5 more strategies the moment you subscribe.5,000 quant traders already run these:
Subscribe | AlgoEdge Insights


Top comments (0)