KhueApps
Home/Python/Calculate Moving Averages and SMAs in Python for Trading

Calculate Moving Averages and SMAs in Python for Trading

Last updated: October 04, 2025

Overview

Moving Averages (MAs) smooth price data to reveal trend direction. The Simple Moving Average (SMA) is the arithmetic mean of the last N closing prices. In trading, SMAs help with trend detection, filter noise, and drive rules like moving-average crossovers.

Common MA types:

TypeSmoothingLagTypical Use
SMAUniform weightsHigherBaseline trend filter, crossovers
EMAExponential weightsLowerFaster response to new prices
WMALinearly weightedMediumEmphasize recent data without EMA curve

This guide shows practical, concise ways to compute SMAs (and briefly EMAs) for algorithmic trading in Python.

Minimal working example (SMA with pandas)

import pandas as pd

# Sample daily closing prices
close = pd.Series(
    [100, 102, 101, 103, 104, 106, 105, 107, 108, 110],
    index=pd.date_range("2024-01-01", periods=10, freq="D"),
    name="Close",
)

# Compute SMAs
sma_3 = close.rolling(window=3, min_periods=3).mean()
sma_5 = close.rolling(window=5, min_periods=5).mean()

# Simple crossover signal: 1 when fast SMA > slow SMA, else 0
fast = sma_3
slow = sma_5
position = (fast > slow).astype(int)

# Identify buy/sell crossovers
buy = (fast > slow) & (fast.shift(1) <= slow.shift(1))
sell = (fast < slow) & (fast.shift(1) >= slow.shift(1))

print("SMA(3):\n", sma_3)
print("SMA(5):\n", sma_5)
print("Buy cross dates:", list(buy[buy].index))
print("Sell cross dates:", list(sell[sell].index))

What it does:

  • Computes SMA(3) and SMA(5)
  • Generates a simple trend-following position
  • Reports crossover dates

Quickstart (real prices)

  1. Install packages
pip install pandas numpy yfinance
  1. Load historical prices
import pandas as pd
import numpy as np
import yfinance as yf

df = yf.download("SPY", start="2022-01-01", progress=False)[["Close"]]
  1. Compute SMAs and a crossover strategy
df["SMA_20"] = df["Close"].rolling(20).mean()
df["SMA_50"] = df["Close"].rolling(50).mean()

# Position: long when fast >= slow, flat otherwise
df["position"] = (df["SMA_20"] >= df["SMA_50"]).astype(int)

# Next-day execution to avoid lookahead
df["ret"] = df["Close"].pct_change()
df["strategy_ret"] = df["position"].shift(1) * df["ret"]

equity_curve = (1 + df["strategy_ret"]).cumprod()
print(equity_curve.tail())
  1. Inspect signals
cross_up = (df["SMA_20"] > df["SMA_50"]) & (df["SMA_20"].shift(1) <= df["SMA_50"].shift(1))
print("Recent buy signals:", list(cross_up[cross_up].index[-5:]))
  1. Persist results
df.to_csv("spy_sma_signals.csv")

Computing SMAs efficiently

  • pandas rolling (recommended):

    • series.rolling(window).mean() is C-optimized.
    • Use min_periods=window to avoid partial-window bias.
  • numpy convolution (fast, explicit control):

import numpy as np
import pandas as pd

# Assume Series `close`
window = 20
weights = np.ones(window, dtype=float) / window
sma_vals = np.convolve(close.values, weights, mode="valid")
sma = pd.Series(sma_vals, index=close.index[window-1:], name=f"SMA_{window}")
  • Exponential MA (EMA) in pandas:
ema_20 = close.ewm(span=20, adjust=False).mean()

Choosing windows (practical)

  • Intraday futures/FX: 20–50 bars (fast), 100–200 bars (slow)
  • Daily equities: 20/50 (swing), 50/200 (trend), 10 (very fast)
  • Crypto 24/7: consider 24, 72, 168 for hourly bars
  • Use volatility-weighted sizing if mixing assets with different ATR

Rule of thumb: longer windows reduce whipsaws but increase lag.

Using SMAs in strategies

  • Single-SMA filter: trade long only when price > SMA(N)
  • Two-SMA crossover: long when SMA(fast) > SMA(slow)
  • Breakout confirmation: require price cross and SMA slope > 0
  • Risk control: exit when price closes below SMA or when fast < slow

Example: SMA slope filter

sma_50 = df["Close"].rolling(50).mean()
slope_up = sma_50 > sma_50.shift(5)  # 5-bar slope proxy
signal = (df["Close"] > sma_50) & slope_up

Pitfalls and how to avoid them

  • Lookahead bias: never use today’s signal to trade today’s close. Shift positions by 1 bar.
  • NaNs at start: the first window-1 values are NaN. Handle with min_periods or drop them.
  • Missing days/timezones: align calendars when merging assets. Resample consistently.
  • Split/dividend adjustments: ensure close prices are adjusted; unadjusted data distorts MAs.
  • Parameter fishing: windows tuned on past data may overfit. Use walk-forward or nested CV.
  • Data snooping via price source: mixing data vendors can introduce micro-differences; stick to one.
  • Execution slippage: backtests ignoring fees/slippage inflate results; simulate costs.

Performance notes

  • Vectorize: avoid Python loops. Use pandas rolling or numpy convolution.
  • Memory: prefer float32 for very large datasets to halve memory footprint.
  • Large windows: for very long windows or many assets, convolution can be faster; consider FFT-based convolution (e.g., SciPy) for huge kernels.
  • Batch compute: process arrays shape (time, assets) with numpy for multi-asset universes.
  • I/O bottlenecks: store OHLCV as Parquet; read selectively (columns=["Close"]).
  • Parallelism: compute per-symbol in parallel processes when I/O bound; rolling mean itself is fast and often CPU-light.

Testing correctness

  • Known-sequence check: for prices 1..N, SMA(N) at the last bar equals the average of 1..N.
  • Equivalence: pandas rolling mean should match numpy convolution aligned to the right edge.
  • Edge cases: constant series returns constant SMA; alternating series will lag by half-period.

Tiny FAQ

  • Q: SMA vs EMA for crossovers?

    • A: EMA reacts faster (less lag), but may whipsaw more. SMA is smoother and simpler.
  • Q: How to avoid delayed entries?

    • A: Use shorter windows, EMAs, or confirm with momentum; trade on next bar’s open to avoid lookahead.
  • Q: Can I use volume-weighted price for SMA?

    • A: Yes. Compute typical price (H+L+C)/3 or VWAP-like inputs before averaging if it fits your logic.
  • Q: How many bars do I need before using an SMA?

    • A: At least window bars. Many practitioners also require an additional buffer (e.g., 2×window) before trusting signals.

Summary

  • Use pandas rolling(...).mean() for SMAs; it’s fast and reliable.
  • Shift signals by one bar to avoid lookahead.
  • Choose windows based on timeframe and tolerance for lag/whipsaws.
  • Validate with simple tests and track performance with an equity curve.

Next Article: Generating Real-Time Trading Signals with yfinance and Python

Series: Algorithmic Trading with Python

Python