Skip to main content

Moving Average Crossover

TL;DR

The MA crossover strategy goes long when a fast moving average crosses above a slow one, and short when it crosses below. It's the most intuitive trend-following idea in trading. In its raw form, it loses money in most conditions because of whipsaws in choppy markets. Add a regime filter (ADX > 20, or higher-timeframe trend alignment) and it becomes viable.

The base strategy

Classic 9/21 EMA crossover:

  • Long: 9 EMA crosses above 21 EMA
  • Short: 9 EMA crosses below 21 EMA
  • Exit: opposite crossover (always in the market)
  • Stop: 1× ATR beyond the opposite EMA

Simple. Easy to code. Fails in practice on its own.

Why the raw version fails

In ranging markets — which is most markets, most of the time — the two EMAs dance around each other. Each crossover looks like a breakout signal. Each whips back within a few bars. You lose the stop-to-entry distance on each whipsaw.

Over a typical quarter, a raw 9/21 EMA crossover strategy on ES fires ~30–50 trades. 60–65% are losers. Winners are larger than losers on average, but not by enough to overcome the whipsaw frequency.

Result: ~1.0 profit factor in most market regimes. Breakeven, minus commissions. Not profitable.

The fixes that make it work

Fix 1: Higher-timeframe trend filter. Only take longs if the daily close > 20-day SMA. Only take shorts if daily close < 20-day SMA. Cuts signal count by half. Removes most counter-trend failures.

Fix 2: ADX regime filter. Require ADX > 20 on the trade timeframe. Skips chop. Cuts another 30% of signals.

Fix 3: Price confirmation. Require the crossover bar to close beyond a recent swing high/low — confirming breakout alongside the crossover. Filters out "EMAs crossing while price is flat."

Fix 4: Partial exits and trailing stops. Don't always hold to opposite crossover. Scale out half at +2R, trail the rest. Captures trend without giving back all profit when it rolls.

With these fixes, a 9/21 strategy on ES daily can reach:

  • 40–50% win rate
  • Avg winner 2–3× avg loser
  • Profit factor 1.3–1.8
  • 5–15 trades per month

Still a long way from a perfect strategy. But tradeable.

Full filtered Pine Script (v6)

//@version=6
strategy("9/21 EMA with Filters", overlay=true, initial_capital=50000, default_qty_type=strategy.fixed, default_qty_value=1)

// ---------- Inputs ----------
fastLen = input.int(9, "Fast EMA")
slowLen = input.int(21, "Slow EMA")
htfLen = input.int(20, "Higher-TF Trend Filter SMA (daily)")
adxLen = input.int(14, "ADX Length")
adxMin = input.float(20, "Min ADX for Entry")
atrLen = input.int(14, "ATR Length")
stopAtrMult = input.float(1.5, "Stop = N × ATR")

// ---------- Indicators ----------
fastEma = ta.ema(close, fastLen)
slowEma = ta.ema(close, slowLen)

// Higher-timeframe trend: daily close vs. daily 20-SMA
htfTrend = request.security(syminfo.tickerid, "D", ta.sma(close, htfLen))
htfClose = request.security(syminfo.tickerid, "D", close)
trendUp = htfClose > htfTrend
trendDown = htfClose < htfTrend

// ADX
[_diPlus, _diMinus, adxVal] = ta.dmi(adxLen, adxLen)
strongTrend = adxVal > adxMin

// ATR
atrVal = ta.atr(atrLen)

// ---------- Signals ----------
bullCross = ta.crossover(fastEma, slowEma)
bearCross = ta.crossunder(fastEma, slowEma)

longSignal = bullCross and trendUp and strongTrend
shortSignal = bearCross and trendDown and strongTrend

if longSignal
stopPrice = slowEma - stopAtrMult * atrVal
strategy.entry("MA Long", strategy.long)
strategy.exit("Long Exit", "MA Long", stop=stopPrice)

if shortSignal
stopPrice = slowEma + stopAtrMult * atrVal
strategy.entry("MA Short", strategy.short)
strategy.exit("Short Exit", "MA Short", stop=stopPrice)

// Exit on opposite crossover
if strategy.position_size > 0 and bearCross
strategy.close("MA Long", comment="Bear cross exit")
if strategy.position_size < 0 and bullCross
strategy.close("MA Short", comment="Bull cross exit")

// ---------- Plots ----------
plot(fastEma, color=color.yellow, linewidth=2, title="9 EMA")
plot(slowEma, color=color.blue, linewidth=2, title="21 EMA")
plotshape(longSignal, style=shape.triangleup, location=location.belowbar, color=color.green, size=size.small)
plotshape(shortSignal, style=shape.triangledown, location=location.abovebar, color=color.red, size=size.small)

Automating with CrossTrade

Standard CrossTrade webhook:

key=YOUR-SECRET-KEY;
command=place;
account=Sim101;
instrument=ES 06-26;
action={{strategy.order.action}};
qty={{strategy.order.contracts}};
order_type=market;
tif=day;
sync_strategy=true;
market_position={{strategy.market_position}};
prev_market_position={{strategy.prev_market_position}};
out_of_sync=flatten;

Because this strategy fires relatively infrequently on daily, alerts can use alert.freq_once_per_bar_close. On intraday timeframes (15-min, 1-hour), monitor carefully — strategy.entry() can fire multiple times before position opens on NT8.

Common mistakes

  • Trading the raw 9/21 on intraday without filters. It loses money. Don't bother.
  • Optimizing the EMA periods endlessly. The specific numbers matter very little. The filters matter enormously.
  • Holding too long. Trailing after profitable moves captures more edge than waiting for opposite crossovers.
  • Using it on mean-reverting products. MA crossovers are trend strategies. On sideways currency pairs or range-bound commodities, they whipsaw badly even with filters.

Frequently Asked Questions

Does the 9/21 EMA crossover strategy work?

Raw, no — it breaks even to slightly negative across most market regimes due to whipsaws in choppy conditions. With a higher-timeframe trend filter and an ADX minimum, it becomes profitable. The filters are the strategy; the crossover is just the trigger.

What are the best moving average crossover settings?

The classic 9/21 works as well as any other pair (5/20, 8/21, 13/34). Obsessing over the specific numbers is wasted time. Adding regime filters produces dramatically more improvement than tuning the periods.

What timeframe is best for MA crossovers?

Higher timeframes (daily, 4-hour) produce more reliable signals because each bar carries more information. Intraday 5-minute crossovers fire too frequently and whipsaw more — possible to trade, but much harder to profit from.

Should I exit on an opposite crossover or trail the stop?

Trailing stops typically outperform strict opposite-crossover exits because they capture trend continuation after the initial edge is won. A common hybrid: trail below the slow EMA, exit if price closes below it.