Skip to content
Back to App

SMA Crossover Template

The SMA Crossover is the most widely taught trend-following strategy. It uses two Simple Moving Averages of different lengths — when the faster one crosses above the slower one, the strategy buys. When it crosses below, the strategy sells.

How the strategy works

A Simple Moving Average (SMA) calculates the average closing price over the last N bars. A short-period SMA reacts quickly to price changes, while a long-period SMA smooths out noise and follows the broader trend.

The crossover logic:

  • Buy signal: The fast SMA rises above the slow SMA. This indicates short-term momentum is turning bullish and price may be entering an uptrend.
  • Sell signal: The fast SMA drops below the slow SMA. This indicates short-term momentum is turning bearish and price may be entering a downtrend.

The strategy maintains a position at all times after the first signal — it is either long, short, or waiting for enough data to compute the moving averages.

Signal flow

  1. Collect closing prices bar by bar
  2. Wait until enough bars exist to calculate the slow SMA (default: 30 bars)
  3. On each new bar, compute both the fast SMA and the slow SMA
  4. If fast SMA > slow SMA and current position is not already long: buy (close any short position first)
  5. If fast SMA < slow SMA and current position is not already short: sell (close any long position first)
  6. Repeat until all bars are processed

Parameters

ParameterDefaultDescription
FAST_PERIOD10Number of bars for the fast (short-term) moving average
SLOW_PERIOD30Number of bars for the slow (long-term) moving average
MAX_BARS0Maximum bars to process. 0 means process all available bars in the date range

How to set parameters

  • FAST_PERIOD must be less than SLOW_PERIOD. If they are equal, the strategy will never generate a signal.
  • Common pairings: 5/20, 10/30, 10/50, 20/50, 50/200.
  • Lower values produce more signals (more trades, more whipsaws). Higher values produce fewer signals (fewer trades, catches larger trends).

The Python code

Here is the complete strategy logic (excluding the shared header, setup, and cleanup code that TestMax auto-generates):

FAST_PERIOD = int(os.environ.get("FAST_PERIOD", "10"))
SLOW_PERIOD = int(os.environ.get("SLOW_PERIOD", "30"))
MAX_BARS = int(os.environ.get("MAX_BARS", "0")) or int(os.environ.get("TOTAL_BARS", "999999"))
# ... (backtest session setup is auto-generated) ...
# ─── SMA CROSSOVER STRATEGY ─────────────────────────────
closes = []
position = None # "LONG" or "SHORT" or None
trade_count = 0
wins = 0
entry_price = 0
def sma(data, period):
"""Simple Moving Average."""
if len(data) < period:
return None
return sum(data[-period:]) / period
print(f"[INFO] Strategy: SMA Crossover (fast={FAST_PERIOD}, slow={SLOW_PERIOD})")
try:
for i in range(MAX_BARS):
bar = get_next_bar()
if bar is None:
print("[INFO] No more bars available")
break
closes.append(bar["c"])
read_speed()
if STEP_DELAY > 0:
time.sleep(STEP_DELAY)
# Need enough data for slow MA
if len(closes) < SLOW_PERIOD:
continue
fast_ma = sma(closes, FAST_PERIOD)
slow_ma = sma(closes, SLOW_PERIOD)
price = bar["c"]
# Buy signal: fast crosses above slow
if fast_ma > slow_ma and position != "LONG":
if position == "SHORT":
# Close short + open long
result = place_order(ACCOUNT_ID, CONTRACT_ID, 0, 2)
pnl = entry_price - price
if pnl > 0: wins += 1
trade_count += 1
else:
result = place_order(ACCOUNT_ID, CONTRACT_ID, 0, 1)
position = "LONG"
entry_price = price
trade_count += 1
print(f"[TRADE] BUY @ {price:.2f} | Fast MA: {fast_ma:.2f} > Slow MA: {slow_ma:.2f}")
# Sell signal: fast crosses below slow
elif fast_ma < slow_ma and position != "SHORT":
if position == "LONG":
result = place_order(ACCOUNT_ID, CONTRACT_ID, 1, 2)
pnl = price - entry_price
if pnl > 0: wins += 1
trade_count += 1
else:
result = place_order(ACCOUNT_ID, CONTRACT_ID, 1, 1)
position = "SHORT"
entry_price = price
trade_count += 1
print(f"[TRADE] SELL @ {price:.2f} | Fast MA: {fast_ma:.2f} < Slow MA: {slow_ma:.2f}")

Key code details

  • sma(data, period) — A helper function that computes the average of the last period values. Returns None if there is not enough data yet.
  • Position flipping — When the strategy switches from long to short (or vice versa), it places an order for size 2 to close the existing position and open a new one in the opposite direction in a single order.
  • read_speed() — Called on every bar to pick up speed changes from the UI. This allows you to speed up or slow down the backtest mid-execution.
  • STEP_DELAY — The delay between bars, controlled by the speed slider. At 1x speed, this is approximately 0.02 seconds per bar.

Example output

[INFO] Strategy: SMA Crossover (fast=10, slow=30)
[INFO] Using pre-created session: NQ (CON.F.US.ENQ.H25)
[INFO] Account: 12345, Contract: CON.F.US.ENQ.H25, Bars: 390
────────────────────────────────────────────────────────────
[TRADE] BUY @ 21503.25 | Fast MA: 21501.30 > Slow MA: 21500.85
[TRADE] SELL @ 21498.50 | Fast MA: 21499.10 < Slow MA: 21500.20
[TRADE] BUY @ 21510.75 | Fast MA: 21508.40 > Slow MA: 21505.60
[TRADE] SELL @ 21525.00 | Fast MA: 21522.15 < Slow MA: 21523.80
...
────────────────────────────────────────────────────────────
[DONE] Backtest Complete!
[DONE] Final Balance: $50,325.00
[DONE] Net P&L: +$325.00
[DONE] Total Trades: 14
[DONE] Win Rate: 57.1%

Tuning tips

Choosing the right periods

If you are using 1-second or 5-second bars:

  • Try FAST_PERIOD=5, SLOW_PERIOD=15 for more reactive signals
  • Expect many trades and frequent whipsaws
  • Works best during high-volatility sessions (market open, major news)
  • The strategy will be in and out of positions quickly

Common pitfalls

  • Whipsaws in ranging markets. The SMA crossover performs poorly when price oscillates in a narrow range. The fast and slow averages will repeatedly cross back and forth, generating losing trades. Consider using a wider spread between periods (e.g., 10/50 instead of 10/30) to reduce false signals.
  • Lag. Moving averages are lagging indicators — they react to price changes after they happen. By the time a crossover occurs, the trend may already be well underway. This means you will miss the first part of a move and may exit after it has partially reversed.
  • Period relative to timeframe. A 30-period SMA means 30 seconds on 1-second bars but 2.5 hours on 5-minute bars. Always think about the real time duration your periods represent.

Ideas for improvement

Once you are comfortable with the basic SMA Crossover, consider these enhancements in a custom strategy:

  • Add a trend filter. Only take long signals when price is above a 200-period SMA (or short signals below it).
  • Add RSI confirmation. Require RSI to be above 50 for buys and below 50 for sells to reduce false signals.
  • Use a stop-loss. Exit a trade if the loss exceeds a fixed point value instead of waiting for the next crossover.
  • Position sizing. Instead of always trading 1 contract, vary the size based on account equity or the strength of the signal.

See Custom Strategies to learn how to implement these modifications.