Skip to content

Trading Strategies

🎯 Learning Objectives

  • Understand mean reversion trading strategies
  • Learn momentum-based strategies
  • Master breakout trading strategies
  • Develop complete trading strategy systems

This chapter covers fundamental trading strategies you can implement and backtest. Each strategy has different market conditions where it works best.

Mean Reversion Strategies

Concept

Mean reversion assumes prices will return to their average after deviating. This works well in ranging markets.

Z-Score Mean Reversion

import pandas as pd
import numpy as np
import yfinance as yf
import matplotlib.pyplot as plt

# Get data
data = yf.download("AAPL", period="2y")

# Calculate z-score
window = 20
data['SMA'] = data['Close'].rolling(window=window).mean()
data['Std'] = data['Close'].rolling(window=window).std()
data['Z_Score'] = (data['Close'] - data['SMA']) / data['Std']

# Trading signals
data['Signal'] = 0
data.loc[data['Z_Score'] < -2, 'Signal'] = 1   # Buy when oversold
data.loc[data['Z_Score'] > 2, 'Signal'] = -1   # Sell when overbought
data.loc[abs(data['Z_Score']) < 0.5, 'Signal'] = 0  # Exit when near mean

# Calculate positions (hold until opposite signal)
data['Position'] = data['Signal'].replace(to_replace=0, method='ffill').fillna(0)

# Calculate returns
data['Returns'] = data['Close'].pct_change()
data['Strategy_Returns'] = data['Position'].shift(1) * data['Returns']
data['Cumulative_Returns'] = (1 + data['Strategy_Returns']).cumprod()

# Performance
total_return = data['Cumulative_Returns'].iloc[-1] - 1
sharpe = data['Strategy_Returns'].mean() / data['Strategy_Returns'].std() * np.sqrt(252)
print(f"Total Return: {total_return:.2%}")
print(f"Sharpe Ratio: {sharpe:.2f}")

Bollinger Bands Mean Reversion

# Bollinger Bands
data['BB_Middle'] = data['Close'].rolling(window=20).mean()
bb_std = data['Close'].rolling(window=20).std()
data['BB_Upper'] = data['BB_Middle'] + (bb_std * 2)
data['BB_Lower'] = data['BB_Middle'] - (bb_std * 2)

# Signals
data['BB_Signal'] = 0
data.loc[data['Close'] < data['BB_Lower'], 'BB_Signal'] = 1   # Buy at lower band
data.loc[data['Close'] > data['BB_Upper'], 'BB_Signal'] = -1  # Sell at upper band
data.loc[(data['Close'] >= data['BB_Lower']) & (data['Close'] <= data['BB_Upper']), 'BB_Signal'] = 0

RSI Mean Reversion

def calculate_rsi(prices, period=14):
    delta = prices.diff()
    gain = (delta.where(delta > 0, 0)).rolling(window=period).mean()
    loss = (-delta.where(delta < 0, 0)).rolling(window=period).mean()
    rs = gain / loss
    rsi = 100 - (100 / (1 + rs))
    return rsi

data['RSI'] = calculate_rsi(data['Close'])

# Mean reversion signals
data['RSI_Signal'] = 0
data.loc[data['RSI'] < 30, 'RSI_Signal'] = 1   # Oversold - buy
data.loc[data['RSI'] > 70, 'RSI_Signal'] = -1  # Overbought - sell

Momentum Strategies

Concept

Momentum strategies assume trends continue - "the trend is your friend."

Price Momentum

# Calculate momentum
momentum_period = 10
data['Momentum'] = data['Close'].pct_change(periods=momentum_period)

# Signals
data['Momentum_Signal'] = 0
data.loc[data['Momentum'] > 0.05, 'Momentum_Signal'] = 1   # Strong upward momentum
data.loc[data['Momentum'] < -0.05, 'Momentum_Signal'] = -1  # Strong downward momentum

Moving Average Crossover

# Calculate moving averages
data['SMA_Short'] = data['Close'].rolling(window=20).mean()
data['SMA_Long'] = data['Close'].rolling(window=50).mean()

# Golden Cross / Death Cross
data['MA_Signal'] = 0
data.loc[data['SMA_Short'] > data['SMA_Long'], 'MA_Signal'] = 1   # Golden Cross - buy
data.loc[data['SMA_Short'] < data['SMA_Long'], 'MA_Signal'] = -1  # Death Cross - sell

# Entry/exit signals (only on crossovers)
data['MA_Cross'] = (data['SMA_Short'] > data['SMA_Long']).astype(int)
data['MA_Entry'] = data['MA_Cross'].diff()
data['MA_Trade_Signal'] = 0
data.loc[data['MA_Entry'] == 1, 'MA_Trade_Signal'] = 1   # Enter long
data.loc[data['MA_Entry'] == -1, 'MA_Trade_Signal'] = -1  # Enter short

MACD Momentum

# MACD
exp1 = data['Close'].ewm(span=12, adjust=False).mean()
exp2 = data['Close'].ewm(span=26, adjust=False).mean()
data['MACD'] = exp1 - exp2
data['Signal_Line'] = data['MACD'].ewm(span=9, adjust=False).mean()
data['Histogram'] = data['MACD'] - data['Signal_Line']

# Momentum signals
data['MACD_Signal'] = 0
data.loc[data['MACD'] > data['Signal_Line'], 'MACD_Signal'] = 1   # Bullish
data.loc[data['MACD'] < data['Signal_Line'], 'MACD_Signal'] = -1  # Bearish

# Crossover signals
data['MACD_Cross'] = (data['MACD'] > data['Signal_Line']).astype(int)
data['MACD_Entry'] = data['MACD_Cross'].diff()
data['MACD_Trade'] = 0
data.loc[data['MACD_Entry'] == 1, 'MACD_Trade'] = 1
data.loc[data['MACD_Entry'] == -1, 'MACD_Trade'] = -1

Rate of Change (ROC)

# Rate of change
roc_period = 12
data['ROC'] = (data['Close'] - data['Close'].shift(roc_period)) / data['Close'].shift(roc_period) * 100

# Momentum signals
data['ROC_Signal'] = 0
data.loc[data['ROC'] > 5, 'ROC_Signal'] = 1   # Strong positive momentum
data.loc[data['ROC'] < -5, 'ROC_Signal'] = -1  # Strong negative momentum

Breakout Strategies

Concept

Breakout strategies trade when price breaks through support or resistance levels, expecting continuation.

Price Breakout

# Identify breakout levels
lookback = 20
data['High_20'] = data['High'].rolling(window=lookback).max()
data['Low_20'] = data['Low'].rolling(window=lookback).min()

# Breakout signals
data['Breakout_Signal'] = 0
data.loc[data['Close'] > data['High_20'].shift(1), 'Breakout_Signal'] = 1   # Upward breakout
data.loc[data['Close'] < data['Low_20'].shift(1), 'Breakout_Signal'] = -1  # Downward breakout

Volume Confirmation Breakout

# Breakout with volume confirmation
data['Volume_MA'] = data['Volume'].rolling(window=20).mean()
data['High_Volume'] = data['Volume'] > data['Volume_MA'] * 1.5

# Breakout with volume
data['Volume_Breakout'] = 0
data.loc[(data['Close'] > data['High_20'].shift(1)) & data['High_Volume'], 'Volume_Breakout'] = 1
data.loc[(data['Close'] < data['Low_20'].shift(1)) & data['High_Volume'], 'Volume_Breakout'] = -1

Donchian Channel Breakout

# Donchian Channels
period = 20
data['Donchian_High'] = data['High'].rolling(window=period).max()
data['Donchian_Low'] = data['Low'].rolling(window=period).min()
data['Donchian_Mid'] = (data['Donchian_High'] + data['Donchian_Low']) / 2

# Breakout signals
data['Donchian_Signal'] = 0
data.loc[data['Close'] > data['Donchian_High'].shift(1), 'Donchian_Signal'] = 1
data.loc[data['Close'] < data['Donchian_Low'].shift(1), 'Donchian_Signal'] = -1

Combined Strategies

Multi-Signal Strategy

# Combine multiple signals
data['Combined_Signal'] = 0

# Weighted combination
data['Combined_Signal'] = (
    0.4 * data['MA_Signal'] +
    0.3 * data['MACD_Signal'] +
    0.3 * data['RSI_Signal']
)

# Normalize to -1, 0, 1
data['Combined_Signal'] = np.where(data['Combined_Signal'] > 0.5, 1,
                                   np.where(data['Combined_Signal'] < -0.5, -1, 0))

Strategy with Filters

# Add trend filter to mean reversion
data['Trend'] = np.where(data['SMA_Short'] > data['SMA_Long'], 1, -1)

# Only take mean reversion trades in direction of trend
data['Filtered_Signal'] = 0
data.loc[(data['Z_Score'] < -2) & (data['Trend'] == 1), 'Filtered_Signal'] = 1   # Buy in uptrend
data.loc[(data['Z_Score'] > 2) & (data['Trend'] == -1), 'Filtered_Signal'] = -1  # Sell in downtrend

Strategy Development Process

1. Idea Generation

  • Market observation
  • Research papers
  • Backtesting ideas
  • Combining indicators

2. Hypothesis Formulation

# Example hypothesis
hypothesis = """
Hypothesis: Stocks that are oversold (RSI < 30) in an uptrend 
(SMA_20 > SMA_50) will revert to mean and generate positive returns 
over the next 5 days.
"""

3. Strategy Implementation

def mean_reversion_strategy(data, z_threshold=2, window=20):
    """Mean reversion strategy"""
    data = data.copy()
    data['SMA'] = data['Close'].rolling(window=window).mean()
    data['Std'] = data['Close'].rolling(window=window).std()
    data['Z_Score'] = (data['Close'] - data['SMA']) / data['Std']

    data['Signal'] = 0
    data.loc[data['Z_Score'] < -z_threshold, 'Signal'] = 1
    data.loc[data['Z_Score'] > z_threshold, 'Signal'] = -1

    return data

4. Backtesting

def backtest_strategy(data, signal_col='Signal'):
    """Simple backtest"""
    data = data.copy()
    data['Returns'] = data['Close'].pct_change()
    data['Position'] = data[signal_col].shift(1).fillna(0)
    data['Strategy_Returns'] = data['Position'] * data['Returns']
    data['Cumulative'] = (1 + data['Strategy_Returns']).cumprod()

    # Metrics
    total_return = data['Cumulative'].iloc[-1] - 1
    sharpe = data['Strategy_Returns'].mean() / data['Strategy_Returns'].std() * np.sqrt(252)
    max_dd = (data['Cumulative'] / data['Cumulative'].expanding().max() - 1).min()

    return {
        'Total Return': total_return,
        'Sharpe Ratio': sharpe,
        'Max Drawdown': max_dd
    }

# Test strategy
results = backtest_strategy(data, 'Signal')
print(results)

5. Optimization

# Optimize parameters
best_sharpe = -np.inf
best_params = None

for window in [10, 20, 30, 50]:
    for threshold in [1.5, 2.0, 2.5, 3.0]:
        test_data = mean_reversion_strategy(data, threshold, window)
        results = backtest_strategy(test_data, 'Signal')

        if results['Sharpe Ratio'] > best_sharpe:
            best_sharpe = results['Sharpe Ratio']
            best_params = (window, threshold)

print(f"Best parameters: window={best_params[0]}, threshold={best_params[1]}")
print(f"Best Sharpe: {best_sharpe:.2f}")

6. Validation

  • Out-of-sample testing
  • Walk-forward analysis
  • Parameter stability
  • Market regime testing

Key Takeaways

  • Mean Reversion: Works in ranging markets, buy oversold, sell overbought
  • Momentum: Works in trending markets, follow the trend
  • Breakout: Trade when price breaks key levels with volume confirmation
  • Combination: Combine multiple signals for better performance
  • Filters: Use trend filters to improve signal quality
  • Backtesting: Always test strategies before live trading
  • Optimization: Find optimal parameters but avoid overfitting

Previous: Statistical Concepts | Next: Risk Management