Pine Script

Pine Script Moving Average Crossover Strategy: Full Code + Backtest Walkthrough

|10 min read

Build a Pine Script moving average crossover strategy in TradingView. Complete code, EMA vs SMA, golden cross filter, full backtest walkthrough.

The moving average crossover is the oldest mechanical trading strategy still in regular use. It's been tested across every market for the last fifty years. It compiles in twenty lines of Pine Script. And yet most retail traders either get it slightly wrong or skip the parameter testing that determines whether the strategy works on their instrument.

This guide is the full version: the complete Pine Script code, the difference between EMA and SMA crossovers, the golden-cross / death-cross variants, parameter testing, and the realistic limitations you should know before risking real capital.

By the end you will have a strategy script you can paste into TradingView, run the Strategy Tester on, and tune to your own market.

What a Moving Average Crossover Actually Is

A moving average crossover strategy enters when a fast-period moving average crosses above (or below) a slower-period moving average. The fast MA reacts to recent price; the slow MA reacts to longer-term trend. When the fast crosses the slow, momentum has shifted.

The two most-quoted variants:

  • Golden cross: 50-day moving average crosses above the 200-day. Treated as a long-term bullish signal.
  • Death cross: 50-day moving average crosses below the 200-day. Treated as a long-term bearish signal.

Both are descriptive, not predictive. A golden cross does not mean prices will rise — it means prices already have. The strategy works in trending markets and loses money in ranging ones, which is the central tradeoff in every backtest you'll run.

Annotated price chart showing a golden cross and a death cross with the fast and slow moving averages labelled

The Full Pine Script Strategy Code

This is a complete, working moving average crossover strategy. It includes realistic commission, a session filter, barstate.isconfirmed gates, and exposed inputs for parameter testing. Paste it into TradingView's Pine Editor, save, and add to chart.

//@version=6
strategy("MA Crossover Strategy", overlay=true,
         initial_capital=25000,
         default_qty_type=strategy.percent_of_equity,
         default_qty_value=10,
         commission_type=strategy.commission.cash_per_order,
         commission_value=1.0,
         slippage=2)

// Inputs
fastLen = input.int(50, "Fast MA length", minval=1)
slowLen = input.int(200, "Slow MA length", minval=1)
maType = input.string("EMA", "MA type", options=["EMA", "SMA"])
useSession = input.bool(true, "Restrict to session")
sessionStr = input.session("0930-1600", "Session (NY time)")

// Moving averages
fastMA = maType == "EMA" ? ta.ema(close, fastLen) : ta.sma(close, fastLen)
slowMA = maType == "EMA" ? ta.ema(close, slowLen) : ta.sma(close, slowLen)

// Session filter
inSession = not useSession or
    not na(time(timeframe.period, sessionStr, "America/New_York"))

// Crossover signals (confirmed on bar close)
longSignal  = ta.crossover(fastMA, slowMA)  and barstate.isconfirmed and inSession
shortSignal = ta.crossunder(fastMA, slowMA) and barstate.isconfirmed and inSession

// Entries
if longSignal
    strategy.entry("Long", strategy.long)
if shortSignal
    strategy.entry("Short", strategy.short)

// Plots
plot(fastMA, "Fast MA", color=color.new(color.blue, 0), linewidth=2)
plot(slowMA, "Slow MA", color=color.new(color.orange, 0), linewidth=2)

// Alerts
alertcondition(longSignal,  "Long signal",  "Fast MA crossed above slow MA")
alertcondition(shortSignal, "Short signal", "Fast MA crossed below slow MA")

A few things to notice that distinguish this from the typical YouTube version:

  • barstate.isconfirmed prevents intra-bar entries
  • The session filter uses an explicit "America/New_York" timezone string
  • Commission and slippage are realistic, not zero
  • The MA type toggles between EMA and SMA without rewriting
  • Initial capital and position sizing are both exposed

Without these defaults, the backtest you run would be optimistic by 15–30%. With them, the result is at least roughly comparable to live trading.

EMA vs SMA — Which to Use

The EMA (exponential moving average) weights recent bars more heavily. The SMA (simple moving average) weights all bars in the window equally. In practice this means:

  • EMA crossovers fire earlier. Good for short-term strategies on volatile instruments. Bad for high-noise charts where the early signal becomes a false signal.
  • SMA crossovers fire later but with more confirmation. Good for long-horizon trend following on indices and currency majors. Bad for fast-moving stocks where the late entry costs you the bulk of the move.

The right choice depends entirely on the instrument and the timeframe. The strategy code above lets you toggle between them in the TradingView UI without re-editing — change the parameter, rerun the Strategy Tester, compare results.

A starting heuristic:

  • EMA + short periods (9/21, 12/26): intraday on volatile stocks
  • SMA + medium periods (20/50): swing trading on liquid equities
  • SMA + long periods (50/200): position trading on indices

But heuristics are no substitute for testing on your specific instrument. Which is exactly what the Strategy Tester is for.

How to Backtest the Strategy in TradingView

With the script loaded on a chart, open the Strategy Tester panel (it should appear at the bottom by default). Set the date range to at least five years. For each parameter combination you want to test, open the strategy settings (gear icon), change fastLen, slowLen, or maType, click OK and wait for the backtest to rerun, then record the results from the Overview tab.

The metrics that matter for a moving average crossover strategy:

  • Profit factor: want 1.5+ for a long-only version, 1.3+ for a long-and-short version
  • Max drawdown: depends on your tolerance; for retail, 25–30% is usually the upper limit
  • Number of trades: below 50 over 5 years means the parameters are too slow to be statistically meaningful
  • Average bars in trade: tells you the realistic holding period; if it's 200+, this is position trading; under 10, it's intraday

If you systematically test combinations of fastLen and slowLen — say fast in {10, 20, 50} and slow in {50, 100, 200} — you can build a grid of profit factor outcomes. The grid usually reveals a stable region (where most combinations work reasonably well) and a high-variance region (where small parameter changes produce wildly different results). Pick parameters from the stable region. The high-variance region is where strategies fall apart in live trading.

5x5 heatmap showing profit factor across fast and slow moving average parameter combinations

The Two Big Limitations of MA Crossover Strategies

Every moving average crossover backtest hides the same two problems. You should account for both before risking capital.

Choppy markets destroy this strategy. In ranging conditions, the two MAs crisscross repeatedly, producing whipsaw signals. Each false cross costs you commission, slippage, and a small loss. Across a month of chop, the strategy bleeds equity even when price ends near where it started.

The standard fix is a trend filter — only enter long when the slow MA itself is rising, only enter short when it's falling. In code:

trendUp = slowMA > slowMA[10]
trendDown = slowMA < slowMA[10]
longSignal  = ta.crossover(fastMA, slowMA)  and trendUp   and barstate.isconfirmed and inSession
shortSignal = ta.crossunder(fastMA, slowMA) and trendDown and barstate.isconfirmed and inSession

This cuts trade count by about half and substantially raises the win rate, at the cost of missing the first crossover of any new trend.

Stop placement matters more than crossover quality. A naked crossover strategy enters on the cross and exits on the opposite cross. The drawdown between those events can be brutal. Adding a fixed stop loss — at 2x ATR below the entry, for example — caps the worst-case loss and meaningfully improves Sharpe ratio in almost every backtest.

atrStop = ta.atr(14) * 2
if longSignal
    strategy.entry("Long", strategy.long)
    strategy.exit("Long Stop", "Long", stop=close - atrStop)

These two additions — trend filter and ATR stop — turn the basic crossover from "barely tradeable" to "actually deployable" on most charts.

Common Mistakes When Implementing This Strategy

A few errors come up repeatedly in TradingView forums for moving average crossover code:

  • Plotting the MAs on the wrong source. ta.ema(hlc3, 50) and ta.ema(close, 50) give materially different signals. Be deliberate about which price input you're using.
  • Forgetting barstate.isconfirmed. Without it, the strategy fires on every tick and the backtest overstates fills by 10–20%.
  • Hardcoding session times without timezone. "0930-1600" means "chart's local time" by default. On an NSE chart this is India time, not New York time.
  • Optimizing on the same window you backtest on. This is overfitting. Always hold out 12 months as out-of-sample.

The strategy script above already avoids the first three. The fourth is a discipline problem, not a coding problem — solve it by setting your test window explicitly in the Strategy Tester properties. For a deeper walkthrough of these issues, see our guide to common Pine Script mistakes.

Beyond the Basic Crossover

Most traders who use MA crossovers eventually customize them. The common variants:

  • Triple MA filter: add a third, very slow MA (300 or 400 period) and only take signals in its direction
  • MACD-style: trade the difference between fast and slow MAs as its own oscillator
  • Volume-weighted: replace ta.ema(close, ...) with ta.vwma(close, volume, ...) for an MA that weights bars by volume
  • Adaptive period: vary fastLen and slowLen based on ATR or volatility regime

Each variant is another 10–30 lines of Pine Script. Doable by hand, but tedious if you want to test five variations on one instrument.

PineWiz generates each of these from a one-sentence description. "Same MA crossover, but use VWMA instead of EMA, and add a 200-period filter." The output already includes barstate.isconfirmed, realistic commission, and the "America/New_York" timezone. You go from variant idea to backtest result in under a minute.

Start Backtesting Your Own MA Crossover Variant

The base strategy above is a working starting point — paste it into TradingView, run the Strategy Tester, and you have something to compare against. To test variants without hand-editing the script, describe what you want in PineWiz and paste the generated code into the same Pine Editor.

The most valuable strategies come from running the right experiments, not from writing the cleanest code. Skip the code part.

Share this article
P

PineWiz Team

The PineWiz team specializes in Pine Script and algorithmic trading. We build AI tools that help retail traders turn their ideas into production-ready TradingView strategies and indicators — no coding required.

Ready to bring your idea to life?

Turn your trading ideas into Pine Script code without writing a single line.

Start Building