SMA in Python — Coding It from Scratch (pandas)

The one-liner, the from-scratch loop, and a crossover signal

SMA computed in Python
SMA computed in Python

The Simple Moving Average is the “hello world” of indicators, and Python makes it almost too easy. But easy is the point — as the explainer showed, there’s genuinely nothing to it but summing and dividing a sliding window. Let’s build it, then turn it into the crossover signal from the chart above.

What you’ll need

  • Python 3.9+, pandas (pip install pandas)
  • A close price series.

The one-liner

pandas has a rolling window built in, so the honest answer is:

import pandas as pd

df["sma20"] = df["close"].rolling(window=20).mean()
df["sma50"] = df["close"].rolling(window=50).mean()

rolling(20).mean() computes exactly (P₁ + … + P₂₀) / 20 at every bar, leaving NaN for the first 19 bars where the window isn’t full yet. That’s correct behaviour — don’t backfill it.

From scratch (so you know there’s no magic)

If you want to see the sum-and-divide explicitly:

def sma(values, period):
    out = [float("nan")] * len(values)
    running = 0.0
    for i, v in enumerate(values):
        running += v
        if i >= period:
            running -= values[i - period]   # drop the price leaving the window
        if i >= period - 1:
            out[i] = running / period
    return out

This is the same running-sum trick every fast implementation uses: instead of re-adding N prices each bar, you add the newcomer and subtract the departer. O(1) per bar. It matches rolling().mean() to the last decimal.

The crossover signal

The chart above marks golden and death crosses. Here’s how to detect them:

fast, slow = df["close"].rolling(20).mean(), df["close"].rolling(50).mean()
above = fast > slow
df["golden_cross"] = above & ~above.shift(1, fill_value=False)   # crossed up
df["death_cross"]  = ~above & above.shift(1, fill_value=False)    # crossed down

golden_cross is True on the exact bar the fast SMA crosses above the slow one; death_cross on the bar it crosses back below. That boolean is the seed of a mechanical strategy — which you should backtest in AlgoGen before believing.

Gotchas

  • min_periods. By default rolling(20) needs a full 20 values. Set min_periods=1 only if you deliberately want a partial average early on — most of the time you don’t.
  • Simple vs exponential. This weights every price in the window equally. If you want recent prices to matter more, that’s the EMA, not the SMA.

Same indicator elsewhere: MQL5, Pine Script, EasyLanguage, NinjaScript.


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.

Sources and further reading

  1. Gartley and the early use of moving averages (CMT Association)
  2. Windowing operations (pandas documentation)

Historical research from the Algogen archive. Not investment advice.

Blog

Have a strategy idea?

Describe it in plain English, preview where your rules fire, then decide whether the evidence is worth a backtest.

Keep me postedHow it works