CodeForFinance
Digital Product

5 Python Trading
Strategies

Five proven algorithmic trading strategies, fully implemented in Python. From beginner-friendly moving averages to advanced mean reversion. Complete with code, explanations, and backtesting.

5 strategiesFull Python codeBacktesting included

Get the full toolkit with backtesting code

£14.99

All 5 strategies as runnable .py files with backtesting framework. One-time purchase.

Buy the Full Toolkit

Secure payment via Stripe. Instant download.

1

Moving Average Crossover

Beginner

The classic trend-following strategy. When a short-term moving average crosses above a long-term moving average, buy. When it crosses below, sell. Simple, effective, and the foundation of most systematic strategies.

When to Use

Best in trending markets. Performs poorly in sideways/choppy markets. Works well on daily timeframes for swing trading.

Python Implementation

import pandas as pd
import yfinance as yf

# Download data
df = yf.download("AAPL", start="2020-01-01", end="2024-01-01")

# Calculate moving averages
df["SMA_20"] = df["Close"].rolling(window=20).mean()
df["SMA_50"] = df["Close"].rolling(window=50).mean()

# Generate signals
df["signal"] = 0
df.loc[df["SMA_20"] > df["SMA_50"], "signal"] = 1   # Buy
df.loc[df["SMA_20"] < df["SMA_50"], "signal"] = -1   # Sell

# Calculate returns
df["strategy_return"] = df["signal"].shift(1) * df["Close"].pct_change()
cumulative = (1 + df["strategy_return"]).cumprod()

print(f"Total return: {(cumulative.iloc[-1] - 1) * 100:.2f}%")

Key Points

  • -SMA 20/50 is the most common combination for daily charts
  • -EMA (exponential) reacts faster but gives more false signals
  • -Add a minimum holding period to reduce whipsaws
  • -Always shift signals by 1 day to avoid look-ahead bias
2

RSI (Relative Strength Index)

Beginner

A momentum oscillator that measures the speed and magnitude of price changes. RSI ranges from 0-100. Below 30 is considered oversold (buy signal), above 70 is overbought (sell signal). Created by J. Welles Wilder in 1978.

When to Use

Best in range-bound markets. Use as a mean-reversion signal. Combine with trend filters for higher accuracy.

Python Implementation

import pandas as pd
import yfinance as yf

df = yf.download("MSFT", start="2020-01-01", end="2024-01-01")

# Calculate RSI
delta = df["Close"].diff()
gain = delta.where(delta > 0, 0)
loss = -delta.where(delta < 0, 0)

avg_gain = gain.rolling(window=14).mean()
avg_loss = loss.rolling(window=14).mean()

rs = avg_gain / avg_loss
df["RSI"] = 100 - (100 / (1 + rs))

# Generate signals
df["signal"] = 0
df.loc[df["RSI"] < 30, "signal"] = 1    # Oversold = buy
df.loc[df["RSI"] > 70, "signal"] = -1   # Overbought = sell

# Forward fill signals (hold position until opposite signal)
df["position"] = df["signal"].replace(0, float("nan")).ffill().fillna(0)

# Calculate returns
df["strategy_return"] = df["position"].shift(1) * df["Close"].pct_change()
cumulative = (1 + df["strategy_return"]).cumprod()

print(f"Total return: {(cumulative.iloc[-1] - 1) * 100:.2f}%")

Key Points

  • -14-period RSI is the standard but you can adjust for sensitivity
  • -RSI divergence (price makes new high but RSI does not) is a powerful signal
  • -In strong trends, RSI can stay above 70 for weeks — add a trend filter
  • -Combine with volume for confirmation
3

Bollinger Bands

Intermediate

Uses standard deviation bands around a moving average to identify volatility and potential reversals. Price touching the lower band suggests oversold conditions; touching the upper band suggests overbought. The bands widen in volatile markets and contract in calm ones.

When to Use

Excellent for identifying breakouts and mean-reversion trades. The 'squeeze' (narrow bands) often precedes a big move.

Python Implementation

import pandas as pd
import yfinance as yf

df = yf.download("GOOGL", start="2020-01-01", end="2024-01-01")

# Calculate Bollinger Bands
window = 20
df["SMA"] = df["Close"].rolling(window=window).mean()
df["STD"] = df["Close"].rolling(window=window).std()
df["upper_band"] = df["SMA"] + (df["STD"] * 2)
df["lower_band"] = df["SMA"] - (df["STD"] * 2)

# Bandwidth (squeeze detection)
df["bandwidth"] = (df["upper_band"] - df["lower_band"]) / df["SMA"]

# Mean reversion signals
df["signal"] = 0
df.loc[df["Close"] < df["lower_band"], "signal"] = 1    # Buy at lower band
df.loc[df["Close"] > df["upper_band"], "signal"] = -1   # Sell at upper band

df["position"] = df["signal"].replace(0, float("nan")).ffill().fillna(0)
df["strategy_return"] = df["position"].shift(1) * df["Close"].pct_change()
cumulative = (1 + df["strategy_return"]).cumprod()

print(f"Total return: {(cumulative.iloc[-1] - 1) * 100:.2f}%")
print(f"Current bandwidth: {df['bandwidth'].iloc[-1]:.4f}")

Key Points

  • -2 standard deviations captures ~95% of price action
  • -Bollinger Squeeze: when bandwidth is at a 6-month low, expect a breakout
  • -Do NOT use as a standalone system — combine with RSI or volume
  • -%B indicator: (Price - Lower) / (Upper - Lower) normalises position within bands
4

MACD (Moving Average Convergence Divergence)

Intermediate

Tracks the relationship between two exponential moving averages (12 and 26 period). The MACD line minus the signal line (9 period EMA of MACD) creates a histogram. Crossovers of the MACD and signal line generate buy/sell signals.

When to Use

Best for identifying trend changes and momentum shifts. Works well on daily and weekly charts. Less effective in ranging markets.

Python Implementation

import pandas as pd
import yfinance as yf

df = yf.download("TSLA", start="2020-01-01", end="2024-01-01")

# Calculate MACD
df["EMA_12"] = df["Close"].ewm(span=12, adjust=False).mean()
df["EMA_26"] = df["Close"].ewm(span=26, adjust=False).mean()
df["MACD"] = df["EMA_12"] - df["EMA_26"]
df["signal_line"] = df["MACD"].ewm(span=9, adjust=False).mean()
df["histogram"] = df["MACD"] - df["signal_line"]

# Generate signals on crossover
df["signal"] = 0
df.loc[
    (df["MACD"] > df["signal_line"]) &
    (df["MACD"].shift(1) <= df["signal_line"].shift(1)),
    "signal"
] = 1  # Bullish crossover

df.loc[
    (df["MACD"] < df["signal_line"]) &
    (df["MACD"].shift(1) >= df["signal_line"].shift(1)),
    "signal"
] = -1  # Bearish crossover

df["position"] = df["signal"].replace(0, float("nan")).ffill().fillna(0)
df["strategy_return"] = df["position"].shift(1) * df["Close"].pct_change()
cumulative = (1 + df["strategy_return"]).cumprod()

print(f"Total return: {(cumulative.iloc[-1] - 1) * 100:.2f}%")

Key Points

  • -MACD histogram turning positive/negative often precedes the crossover
  • -MACD divergence from price is one of the strongest reversal signals
  • -Zero-line crossover (MACD crossing above/below 0) confirms trend direction
  • -Combine with volume: crossovers on high volume are more reliable
5

Mean Reversion

Advanced

Based on the statistical principle that prices tend to return to their mean over time. When price deviates significantly from its average (measured by z-score), trade in the direction of the mean. This is the foundation of pairs trading and statistical arbitrage.

When to Use

Best for stocks with high mean-reversion tendencies (utilities, ETFs). Avoid in trending stocks. Works well on intraday and daily timeframes.

Python Implementation

import pandas as pd
import numpy as np
import yfinance as yf

df = yf.download("SPY", start="2020-01-01", end="2024-01-01")

# Calculate z-score
lookback = 30
df["mean"] = df["Close"].rolling(window=lookback).mean()
df["std"] = df["Close"].rolling(window=lookback).std()
df["z_score"] = (df["Close"] - df["mean"]) / df["std"]

# Entry and exit thresholds
entry_threshold = 2.0
exit_threshold = 0.0

df["signal"] = 0
df.loc[df["z_score"] < -entry_threshold, "signal"] = 1   # Buy when very oversold
df.loc[df["z_score"] > entry_threshold, "signal"] = -1    # Short when very overbought
df.loc[abs(df["z_score"]) < exit_threshold, "signal"] = 0  # Exit at mean

df["position"] = df["signal"].replace(0, float("nan")).ffill().fillna(0)
df["strategy_return"] = df["position"].shift(1) * df["Close"].pct_change()

# Risk metrics
returns = df["strategy_return"].dropna()
sharpe = np.sqrt(252) * returns.mean() / returns.std()
max_dd = (returns.cumsum() - returns.cumsum().cummax()).min()
cumulative = (1 + returns).cumprod()

print(f"Total return: {(cumulative.iloc[-1] - 1) * 100:.2f}%")
print(f"Sharpe ratio: {sharpe:.2f}")
print(f"Max drawdown: {max_dd * 100:.2f}%")

Key Points

  • -Z-score of 2 means price is 2 standard deviations from the mean — statistically extreme
  • -Half-life of mean reversion tells you how fast a stock reverts (use Ornstein-Uhlenbeck)
  • -Always set stop losses — mean reversion fails spectacularly in regime changes
  • -Pairs trading applies this concept: find two correlated stocks and trade the spread

Strategy Comparison

StrategyTypeBest MarketDifficulty
MA CrossoverTrend followingTrendingBeginner
RSIMomentumRange-boundBeginner
Bollinger BandsVolatilityAnyIntermediate
MACDTrend + MomentumTrendingIntermediate
Mean ReversionStatisticalRange-boundAdvanced

Disclaimer: This content is for educational purposes only and does not constitute financial advice. Past performance does not indicate future results. Algorithmic trading involves significant risk of loss. Always paper trade first and never risk money you cannot afford to lose.

Get the Full Toolkit

All 5 strategies as clean, runnable Python files. Includes a backtesting framework, performance metrics, and a README with setup instructions.

£14.99

Buy Now — £14.99

Secure checkout via Stripe. Instant download of all code.

Developer Essentials

As an Amazon Associate we may earn from qualifying purchases.