Simple Moving Average Strategy

A classic trend-following strategy using moving average crossovers.

Strategy Overview

This strategy uses two simple moving averages (SMAs) to generate buy and sell signals:

  • Short SMA: 10-day moving average
  • Long SMA: 30-day moving average
  • Buy Signal: Short SMA crosses above Long SMA
  • Sell Signal: Short SMA crosses below Long SMA

Complete Agent Code

sma_crossover_agent.pypython
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
from podium_sdk import Agent, Context

class SmaCrossoverAgent(Agent):
    """
    A simple moving average crossover strategy.
    Buys when the 10-day SMA crosses above the 30-day SMA.
    Sells when the 10-day SMA crosses below the 30-day SMA.
    """

    # Define universe of stocks to trade
    universe = [
        "AAPL", "MSFT", "GOOGL", "AMZN", "META",
        "NVDA", "TSLA", "JPM", "V", "WMT"
    ]

    # Risk limits
    risk_limits = {
        "max_position_size": 0.1,    # Max 10% per position
        "max_drawdown": 0.2,          # 20% max drawdown
    }

    # Run daily
    schedule = "daily"

    def initialize(self, context: Context) -> None:
        """Called once at agent startup."""
        context.log.info("Initializing SMA Crossover Agent")
        context.log.info(f"Universe: {', '.join(self.universe)}")

    def analyze(self, context: Context) -> None:
        """Main analysis method called on each tick."""

        # Get historical price data
        prices = context.data.prices(
            symbols=self.universe,
            lookback="40d"  # Get enough data for 30-day SMA
        )

        # Calculate moving averages
        short_window = 10
        long_window = 30

        short_sma = prices["close"].rolling(short_window).mean()
        long_sma = prices["close"].rolling(long_window).mean()

        # Get current positions
        current_positions = context.portfolio.positions()

        # Generate signals for each symbol
        for symbol in self.universe:
            # Skip if we don't have enough data
            if prices["close"][symbol].isna().any():
                continue

            # Current SMAs
            current_short = short_sma[symbol].iloc[-1]
            current_long = long_sma[symbol].iloc[-1]
            previous_short = short_sma[symbol].iloc[-2]
            previous_long = long_sma[symbol].iloc[-2]

            # Check for crossover
            bullish_cross = (
                previous_short <= previous_long and
                current_short > current_long
            )
            bearish_cross = (
                previous_short >= previous_long and
                current_short < current_long
            )

            # Current position
            position = current_positions.get(symbol, 0)

            # Execute trades
            if bullish_cross and position <= 0:
                # Buy signal - allocate equal weight
                weight = 1.0 / len(self.universe)
                context.orders.buy(symbol, weight=weight)
                context.log.info(f"BUY {symbol}: Short SMA ({current_short:.2f}) "
                               f"> Long SMA ({current_long:.2f})")

            elif bearish_cross and position > 0:
                # Sell signal - close position
                context.orders.sell(symbol, weight=1.0)
                context.log.info(f"SELL {symbol}: Short SMA ({current_short:.2f}) "
                               f"< Long SMA ({current_long:.2f})")

            elif position == 0 and not bullish_cross:
                # No position and no signal - ensure we're flat
                context.orders.sell(symbol, weight=1.0)

# Run the agent
if __name__ == "__main__":
    agent = SmaCrossoverAgent()
    agent.run()

How It Works

1

Fetch Data

Retrieve 40 days of historical price data for all symbols in the universe.

2

Calculate Indicators

Compute 10-day and 30-day simple moving averages using pandas rolling windows.

3

Detect Crossovers

Compare current and previous SMA values to identify crossover events.

4

Execute Trades

Buy on bullish crossovers, sell on bearish crossovers, using equal weight allocation.

Performance Note: Simple moving average strategies can underperform in choppy or range-bound markets. Consider adding filters like volume confirmation or trend strength indicators to improve performance.

Strategy Variations

Exponential Moving Average (EMA)

Use EMA instead of SMA for faster reaction to price changes:prices["close"].ewm(span=10).mean()

Triple Crossover

Add a third SMA for additional confirmation. Buy only when all three are aligned bullish.

Adaptive Windows

Adjust SMA periods based on market volatility or trend strength.

Next Steps