Moving Average Crossover Strategy: Complete Guide with Code
Master moving average crossover strategies with SMA, EMA, and HMA. Includes code for TradingView, MetaTrader 5, NinjaTrader, and Python.
The moving average crossover strategy is one of the oldest and most reliable approaches in technical analysis. This guide covers SMA, EMA, and HMA crossovers, dual and triple MA systems, and includes code for all four major platforms — TradingView, MetaTrader 5, NinjaTrader, and Python.
How Moving Average Crossovers Work
A moving average crossover occurs when a faster (shorter period) moving average crosses above or below a slower (longer period) moving average.
Key Crossovers:
- Golden Cross: Fast MA crosses above Slow MA → Bullish signal
- Death Cross: Fast MA crosses below Slow MA → Bearish signal
Common MA Pairs:
- 9/21 EMA — Short-term trading
- 20/50 EMA — Swing trading
- 50/200 SMA — Position trading (the classic Golden/Death Cross)
SMA vs EMA vs HMA
| Type | Full Name | Speed | Smoothness | Best For |
|---|---|---|---|---|
| SMA | Simple Moving Average | Slow | Very smooth | Long-term trend, position trading |
| EMA | Exponential Moving Average | Medium | Moderate | Swing trading, day trading |
| HMA | Hull Moving Average | Fast | Smooth | Scalping, responsive signals |
| DEMA | Double Exponential MA | Fast | Moderate | Reducing EMA lag |
| TEMA | Triple Exponential MA | Very fast | Less smooth | Ultra-responsive signals |
Dual EMA Crossover Strategy
The most popular crossover setup. Buy on golden cross, sell on death cross.
PineScript Code (TradingView)
//@version=5
strategy("EMA Crossover Strategy", overlay=true)
// Settings
fastLength = input(20, "Fast EMA")
slowLength = input(50, "Slow EMA")
// Calculate EMAs
fastEMA = ta.ema(close, fastLength)
slowEMA = ta.ema(close, slowLength)
// Crossover signals
longCondition = ta.crossover(fastEMA, slowEMA)
shortCondition = ta.crossunder(fastEMA, slowEMA)
// Execute trades
if (longCondition)
strategy.entry("Long", strategy.long)
if (shortCondition)
strategy.close("Long")
// Plot EMAs
plot(fastEMA, "Fast EMA", color=color.green, linewidth=2)
plot(slowEMA, "Slow EMA", color=color.red, linewidth=2)MQL5 Code (MetaTrader 5)
//+------------------------------------------------------------------+
//| EMA Crossover Expert Advisor |
//+------------------------------------------------------------------+
#include <Trade\Trade.mqh>
input int FastPeriod = 20;
input int SlowPeriod = 50;
input double LotSize = 0.1;
input int StopLossPips = 50;
input int TakeProfitPips = 100;
CTrade trade;
int fastHandle, slowHandle;
int OnInit()
{
fastHandle = iMA(_Symbol, PERIOD_CURRENT, FastPeriod, 0, MODE_EMA, PRICE_CLOSE);
slowHandle = iMA(_Symbol, PERIOD_CURRENT, SlowPeriod, 0, MODE_EMA, PRICE_CLOSE);
if(fastHandle == INVALID_HANDLE || slowHandle == INVALID_HANDLE)
return(INIT_FAILED);
return(INIT_SUCCEEDED);
}
void OnTick()
{
double fast[], slow[];
ArraySetAsSeries(fast, true);
ArraySetAsSeries(slow, true);
CopyBuffer(fastHandle, 0, 0, 3, fast);
CopyBuffer(slowHandle, 0, 0, 3, slow);
bool goldenCross = fast[2] <= slow[2] && fast[1] > slow[1];
bool deathCross = fast[2] >= slow[2] && fast[1] < slow[1];
bool hasPosition = PositionSelect(_Symbol);
if(goldenCross && !hasPosition)
{
double ask = SymbolInfoDouble(_Symbol, SYMBOL_ASK);
double point = SymbolInfoDouble(_Symbol, SYMBOL_POINT);
double sl = ask - StopLossPips * point * 10;
double tp = ask + TakeProfitPips * point * 10;
trade.Buy(LotSize, _Symbol, ask, sl, tp, "EMA Cross Buy");
}
if(deathCross && hasPosition)
{
trade.PositionClose(_Symbol);
}
}
void OnDeinit(const int reason)
{
IndicatorRelease(fastHandle);
IndicatorRelease(slowHandle);
}NinjaScript Code (NinjaTrader 8)
// EMA Crossover Strategy for NinjaTrader 8
public class EMACrossoverStrategy : Strategy
{
private EMA _fastEMA;
private EMA _slowEMA;
[NinjaScriptProperty]
[Range(1, int.MaxValue)]
public int FastPeriod { get; set; } = 20;
[NinjaScriptProperty]
[Range(1, int.MaxValue)]
public int SlowPeriod { get; set; } = 50;
protected override void OnStateChange()
{
if (State == State.SetDefaults)
{
Description = "EMA Crossover Strategy";
Name = "EMACrossover";
Calculate = Calculate.OnBarClose;
}
else if (State == State.DataLoaded)
{
_fastEMA = EMA(Close, FastPeriod);
_slowEMA = EMA(Close, SlowPeriod);
AddChartIndicator(_fastEMA);
AddChartIndicator(_slowEMA);
}
}
protected override void OnBarUpdate()
{
if (CurrentBar < SlowPeriod) return;
// Golden Cross
if (CrossAbove(_fastEMA, _slowEMA, 1))
{
EnterLong("EMA Cross Long");
}
// Death Cross
if (CrossBelow(_fastEMA, _slowEMA, 1))
{
ExitLong("EMA Cross Exit");
}
}
}Python Code
import pandas as pd
import numpy as np
def ema_crossover_strategy(df, fast_period=20, slow_period=50):
"""Dual EMA Crossover Strategy"""
df = df.copy()
df['fast_ema'] = df['close'].ewm(span=fast_period, adjust=False).mean()
df['slow_ema'] = df['close'].ewm(span=slow_period, adjust=False).mean()
# Detect crossovers
df['signal'] = 0
df.loc[
(df['fast_ema'] > df['slow_ema']) &
(df['fast_ema'].shift(1) <= df['slow_ema'].shift(1)),
'signal'
] = 1 # Golden cross
df.loc[
(df['fast_ema'] < df['slow_ema']) &
(df['fast_ema'].shift(1) >= df['slow_ema'].shift(1)),
'signal'
] = -1 # Death cross
df['position'] = df['signal'].replace(0, np.nan).ffill().fillna(0)
return dfTriple MA Crossover Strategy
Uses three moving averages for stronger trend confirmation:
- Fast: 9 EMA (entry timing)
- Medium: 21 EMA (trend direction)
- Slow: 55 EMA (trend filter)
//@version=5
strategy("Triple EMA Crossover", overlay=true)
fast = ta.ema(close, 9)
medium = ta.ema(close, 21)
slow = ta.ema(close, 55)
// Buy: Fast crosses above Medium, both above Slow
longCondition = ta.crossover(fast, medium) and medium > slow
if (longCondition)
strategy.entry("Long", strategy.long)
// Exit: Fast crosses below Medium
if (ta.crossunder(fast, medium))
strategy.close("Long")
plot(fast, "Fast", color=color.green)
plot(medium, "Medium", color=color.orange)
plot(slow, "Slow", color=color.red)MA + ADX Trend Filter
Avoid whipsaws by only taking crossover signals when the market is trending:
//@version=5
strategy("EMA Crossover + ADX Filter", overlay=true)
// EMAs
fastEMA = ta.ema(close, 20)
slowEMA = ta.ema(close, 50)
// ADX trend filter
adxValue = ta.adx(high, low, close, 14)
isTrending = adxValue > 25
// Only trade crossovers in trending markets
if (ta.crossover(fastEMA, slowEMA) and isTrending)
strategy.entry("Long", strategy.long)
if (ta.crossunder(fastEMA, slowEMA))
strategy.close("Long")Best Moving Average Settings
| Strategy | Fast MA | Slow MA | Type | Timeframe |
|---|---|---|---|---|
| Scalping | 5 | 13 | EMA | 1-5min |
| Day Trading | 9 | 21 | EMA | 15-60min |
| Swing Trading | 20 | 50 | EMA | 4H-Daily |
| Position Trading | 50 | 200 | SMA | Daily-Weekly |
Backtest Results
| MA Pair | Market | Annual Return | Max Drawdown | Win Rate |
|---|---|---|---|---|
| 20/50 EMA | S&P 500 | 10-14% | -18% | 42-48% |
| 50/200 SMA | S&P 500 | 8-11% | -12% | 52-58% |
| 9/21 EMA | Forex (EUR/USD) | 6-10% | -15% | 38-44% |
| 20/50 EMA + ADX | Crypto (BTC) | 22-35% | -25% | 45-52% |
Results vary based on market conditions and settings.
Common Moving Average Mistakes
- Over-optimizing MA periods — Curve-fitting to past data doesn't predict the future
- Trading crossovers in choppy markets — Use ADX or volume to filter
- Using SMA when EMA is better — SMA lags too much for short-term trading
- Ignoring the bigger trend — Always check the higher timeframe trend
- No stop loss — Crossover exits can be slow; use ATR-based stops
Generate Your Crossover Strategy Instantly
Instead of writing code manually, describe your strategy in plain English:
"Create a triple EMA crossover strategy using 9, 21, and 55 periods. Only buy when ADX is above 25. Use 2 ATR stop loss."
SpendDock generates production-ready code for TradingView, MetaTrader, NinjaTrader, or Python in seconds — no coding required.
Conclusion
Moving average crossovers are simple yet effective when used correctly. Key takeaways:
- EMA crossovers are faster and better for short-term trading
- SMA crossovers are smoother and better for position trading
- Triple MA systems add trend confirmation
- ADX filters dramatically reduce whipsaws
- Always backtest your specific MA combination before trading live
Ready to build your own crossover strategy? Try SpendDock and generate production-ready code in seconds.
Skip the Coding — Generate Your Strategy
Describe your trading strategy in plain English and get production-ready code for TradingView, MetaTrader, NinjaTrader, or Python.
Try SpendDock Free