Back to all posts

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.

SpendDock Team··6 min read
Moving AveragesEMASMACrossover StrategyPineScriptMQL5NinjaScript

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

TypeFull NameSpeedSmoothnessBest For
SMASimple Moving AverageSlowVery smoothLong-term trend, position trading
EMAExponential Moving AverageMediumModerateSwing trading, day trading
HMAHull Moving AverageFastSmoothScalping, responsive signals
DEMADouble Exponential MAFastModerateReducing EMA lag
TEMATriple Exponential MAVery fastLess smoothUltra-responsive signals

Dual EMA Crossover Strategy

The most popular crossover setup. Buy on golden cross, sell on death cross.

PineScript Code (TradingView)

pinescript
//@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)

mql5
//+------------------------------------------------------------------+
//| 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)

csharp
// 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

python
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 df

Triple 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)
pinescript
//@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:

pinescript
//@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

StrategyFast MASlow MATypeTimeframe
Scalping513EMA1-5min
Day Trading921EMA15-60min
Swing Trading2050EMA4H-Daily
Position Trading50200SMADaily-Weekly

Backtest Results

MA PairMarketAnnual ReturnMax DrawdownWin Rate
20/50 EMAS&P 50010-14%-18%42-48%
50/200 SMAS&P 5008-11%-12%52-58%
9/21 EMAForex (EUR/USD)6-10%-15%38-44%
20/50 EMA + ADXCrypto (BTC)22-35%-25%45-52%

Results vary based on market conditions and settings.

Common Moving Average Mistakes

  1. Over-optimizing MA periods — Curve-fitting to past data doesn't predict the future
  2. Trading crossovers in choppy markets — Use ADX or volume to filter
  3. Using SMA when EMA is better — SMA lags too much for short-term trading
  4. Ignoring the bigger trend — Always check the higher timeframe trend
  5. 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:

  1. EMA crossovers are faster and better for short-term trading
  2. SMA crossovers are smoother and better for position trading
  3. Triple MA systems add trend confirmation
  4. ADX filters dramatically reduce whipsaws
  5. 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