Charting platforms and libraries make technical indicators available with a single click, so why write them yourself? Because the details matter. Different tools use different smoothing methods, warm-up periods and standard deviation formulas, and those choices change the numbers. Writing the code yourself shows you exactly what an indicator measures, and it makes your research reproducible.
The examples below use pandas and assume a DataFrame of daily bars with high, low and close columns, sorted by date.
Moving averages
A simple moving average (SMA) gives equal weight to the last n values. An exponential moving average (EMA) gives more weight to recent values, so it reacts faster to changes.
import pandas as pd
def sma(close: pd.Series, n: int) -> pd.Series:
return close.rolling(n).mean()
def ema(close: pd.Series, n: int) -> pd.Series:
# span=n gives a smoothing factor of 2 / (n + 1), the common EMA convention
return close.ewm(span=n, adjust=False).mean()
Note the first n - 1 values of the SMA are missing because there is not yet a full window. The EMA, by contrast, starts producing values immediately, but its early values depend heavily on the starting point. Treat the first few dozen EMA values as a warm-up period rather than meaningful readings.
Relative Strength Index (RSI)
RSI compares the size of recent gains with recent losses and scales the result between 0 and 100. The original definition uses Wilder's smoothing, which is an exponential average with a smoothing factor of 1 / n. Some tools instead use simple averages of gains and losses, which produces noticeably different values.
def rsi(close: pd.Series, n: int = 14) -> pd.Series:
delta = close.diff()
gain = delta.clip(lower=0)
loss = -delta.clip(upper=0)
avg_gain = gain.ewm(alpha=1 / n, adjust=False, min_periods=n).mean()
avg_loss = loss.ewm(alpha=1 / n, adjust=False, min_periods=n).mean()
rs = avg_gain / avg_loss
return 100 - 100 / (1 + rs)
When there are no losses in the window, avg_loss is zero, the ratio becomes infinite and the formula correctly returns 100. Wilder's original method seeds the average with a simple mean of the first n values; the exponential version above converges to the same results after a warm-up period.
Average True Range (ATR)
ATR measures volatility, including gaps between sessions. The true range for each bar is the largest of three distances: high to low, high to the previous close, and low to the previous close.
def atr(high: pd.Series, low: pd.Series, close: pd.Series, n: int = 14) -> pd.Series:
prev_close = close.shift(1)
true_range = pd.concat(
[high - low, (high - prev_close).abs(), (low - prev_close).abs()],
axis=1,
).max(axis=1)
return true_range.ewm(alpha=1 / n, adjust=False, min_periods=n).mean()
Because ATR is expressed in price units, it is often used to size positions or set stop distances relative to recent volatility rather than as a fixed amount.
Bollinger Bands
Bollinger Bands place bands a chosen number of standard deviations above and below a moving average.
def bollinger(close: pd.Series, n: int = 20, k: float = 2.0):
mid = sma(close, n)
sd = close.rolling(n).std(ddof=0) # population standard deviation
return mid - k * sd, mid, mid + k * sd
Pandas uses the sample standard deviation (ddof=1) by default, while many charting tools use the population version (ddof=0). Neither is wrong, but mixing them is a common reason two implementations disagree.
Pitfalls to avoid
- Centered windows.
rolling(n, center=True) uses future values and leaks information into any backtest. - Adjusted versus unadjusted prices. Splits and dividends change price levels. Compute indicators on the same price basis you use for signals.
- Ignoring warm-up. Discard or flag early values until each indicator has enough history.
- Same name, different formula. Document the exact definition you use, including smoothing and parameters.
Test against a reference
Once your functions work, compare them with a trusted implementation on the same data. Small differences in early values are expected; persistent differences usually point to a different smoothing method or price input. Writing a few unit tests with hand-calculated values for short series is a quick way to lock in correct behavior.
Indicators are transformations of past prices. They describe what has happened; they do not predict what comes next.
Quantzivo AI is being designed to explain indicator readings in plain language; see the planned technical analysis features. To practice writing and testing indicators, try the Python Programming for Financial Markets course.