Skip to content

Simple Moving Average Crossover

Classic trend-following strategy using two moving averages to generate buy and sell signals.

Strategy Overview

Concept: Buy when fast SMA crosses above slow SMA, sell when it crosses below
Complexity: Beginner
Time Frame: Medium-term (days to weeks)
Best For: Learning technical indicators and trend following

How It Works

  1. Calculate two SMAs: Fast (shorter period) and Slow (longer period)
  2. Buy Signal: Fast SMA crosses above Slow SMA (golden cross)
  3. Sell Signal: Fast SMA crosses below Slow SMA (death cross)
  4. Hold: Stay in position between crossovers

Code Example

python
# Configuration
DEFAULT_SYMBOLS = ["AAPL"]
FAST_PERIOD = 20
SLOW_PERIOD = 50
POSITION_SIZE = 100

def on_strategy_start(portfolio, state):
    """Initialize strategy"""
    print("=== SMA Crossover Strategy ===")
    print(f"Fast SMA: {FAST_PERIOD} periods")
    print(f"Slow SMA: {SLOW_PERIOD} periods")
    print(f"Position Size: {POSITION_SIZE} shares")

def on_bar_close(data_contexts, portfolio, state):
    """Main strategy logic"""
    
    # Get data for primary symbol
    symbol = DEFAULT_SYMBOLS[0]
    data = data_contexts[symbol]
    
    # Calculate moving averages
    sma_fast = data.sma(FAST_PERIOD)
    sma_slow = data.sma(SLOW_PERIOD) 
    
    # Skip if indicators not ready
    if sma_fast != sma_fast or sma_slow != sma_slow:  # Check for NaN
        return None
    
    # Get current position
    current_position = portfolio.position(symbol)
    
    # Buy signal: Fast SMA crosses above Slow SMA
    if sma_fast > sma_slow and current_position == 0:
        print(f"BUY SIGNAL: Fast SMA {sma_fast:.2f} > Slow SMA {sma_slow:.2f}")
        
        return {
            "symbol": symbol,
            "action": "buy", 
            "quantity": POSITION_SIZE
        }
    
    # Sell signal: Fast SMA crosses below Slow SMA
    elif sma_fast < sma_slow and current_position > 0:
        print(f"SELL SIGNAL: Fast SMA {sma_fast:.2f} < Slow SMA {sma_slow:.2f}")
        
        return {
            "symbol": symbol,
            "action": "sell",
            "quantity": "all"
        }
    
    # No signal
    return None

Advanced Version with Crossover Detection

python
def on_bar_close(data_contexts, portfolio, state):
    """Enhanced version with proper crossover detection"""
    
    symbol = 'AAPL'
    data = data_contexts[symbol]
    
    # Current SMAs
    sma_fast = data.sma(20)
    sma_slow = data.sma(50)
    
    # Previous SMAs (from history)
    fast_history = [data.sma(20)]  # In real implementation, track previous values
    slow_history = [data.sma(50)]
    
    # Skip if not enough data
    if len(fast_history) < 2 or any(x != x for x in [sma_fast, sma_slow]):
        return None
    
    # Detect crossovers
    prev_fast = fast_history[-2] if len(fast_history) >= 2 else sma_fast
    prev_slow = slow_history[-2] if len(slow_history) >= 2 else sma_slow
    
    # Golden Cross: Fast crosses above Slow
    golden_cross = (prev_fast <= prev_slow) and (sma_fast > sma_slow)
    
    # Death Cross: Fast crosses below Slow  
    death_cross = (prev_fast >= prev_slow) and (sma_fast < sma_slow)
    
    current_pos = portfolio.position(symbol)
    
    if golden_cross and current_pos == 0:
        print(f"GOLDEN CROSS: Buying at ${data.close:.2f}")
        return {'symbol': symbol, 'action': 'buy', 'quantity': 100}
    
    elif death_cross and current_pos > 0:
        print(f"DEATH CROSS: Selling at ${data.close:.2f}")
        return {'symbol': symbol, 'action': 'sell', 'quantity': 'all'}
    
    return None

Parameter Optimization

Common SMA Combinations

  • Conservative: 50/200 (long-term trends)
  • Balanced: 20/50 (medium-term trends)
  • Aggressive: 10/20 (short-term trends)
  • Very Fast: 5/10 (day trading)

Testing Different Parameters

python
# Test these combinations
PARAMS = [
    (10, 20),   # Fast signals, more trades
    (20, 50),   # Balanced approach
    (50, 200),  # Slow signals, fewer trades
]

FAST_PERIOD, SLOW_PERIOD = PARAMS[1]  # Choose configuration

Expected Results

Typical Performance:

  • Win Rate: 40-60% (depends on market conditions)
  • Profit Factor: 1.1-1.8
  • Best Markets: Trending markets
  • Worst Markets: Choppy, sideways markets

Trade Frequency: Medium (10-50 trades per year)

Pros and Cons

Advantages ✅

  • Simple to understand and implement
  • Works well in trending markets
  • Automatic trend detection
  • No complex parameters

Disadvantages ❌

  • Generates false signals in choppy markets
  • Late entry/exit (lagging indicators)
  • Whipsaws during consolidation periods
  • No risk management built-in

Improvements

Add Volume Filter

python
if sma_fast > sma_slow and current_position == 0:
    # Only buy on high volume
    avg_volume = sum(data.history('volume', 20)) / 20
    if data.volume > avg_volume * 1.5:
        return buy_order

Add RSI Filter

python
if sma_fast > sma_slow and current_position == 0:
    # Only buy when not overbought
    if data.rsi(14) < 70:
        return buy_order

Position Sizing

python
# Risk-based position sizing
def calculate_position_size(portfolio, price, stop_loss_pct=0.05):
    risk_per_trade = portfolio.equity * 0.02  # 2% risk per trade
    risk_per_share = price * stop_loss_pct
    shares = int(risk_per_trade / risk_per_share)
    return min(shares, int(portfolio.cash / price))

Next Steps


Master this strategy before moving to more complex indicators.

Test your trading strategies risk-free with professional backtesting.