*A practical Pine Script tutorial using a moving average crossover I actually trade — not another copy-paste template.*
---
Most Pine Script moving average crossover tutorials give you a 10-line script and call it a day. The problem? Those scripts look great on a chart but fall apart the moment you try to trade them. No alerts, no position sizing, no filters — just two lines crossing.
I've been running a momentum-based strategy on USDJPY for over a year now, and moving average crossovers are a core building block. In this guide, I'll walk you through building a production-ready Pine Script crossover strategy — the kind you can actually set alerts on and trade.
If you don't have a TradingView account yet, you'll need one to follow along. The free tier works for learning, but you'll want at least the Plus plan for server-side alerts.
---
What Is a Moving Average Crossover?
A moving average crossover happens when a faster-period moving average crosses above or below a slower one. The idea is simple:
- Golden Cross (bullish): Fast MA crosses *above* slow MA → potential buy signal
- Death Cross (bearish): Fast MA crosses *below* slow MA → potential sell signal
Step 1: Your First Pine Script Crossover Indicator
Open TradingView, go to Pine Editor (bottom panel), and paste this:
//@version=6
indicator("MA Crossover — Basic", overlay=true)
// --- Inputs ---
fastLen = input.int(9, "Fast MA Length", minval=1)
slowLen = input.int(21, "Slow MA Length", minval=1)
maType = input.string("EMA", "MA Type", options=["SMA", "EMA"])
// --- Calculate MAs ---
fastMA = maType == "EMA" ? ta.ema(close, fastLen) : ta.sma(close, fastLen)
slowMA = maType == "EMA" ? ta.ema(close, slowLen) : ta.sma(close, slowLen)
// --- Plot ---
plot(fastMA, "Fast MA", color=color.blue, linewidth=2)
plot(slowMA, "Slow MA", color=color.orange, linewidth=2)
// --- Crossover Detection ---
bullCross = ta.crossover(fastMA, slowMA)
bearCross = ta.crossunder(fastMA, slowMA)
plotshape(bullCross, "Buy", shape.triangleup, location.belowbar, color.green, size=size.small)
plotshape(bearCross, "Sell", shape.triangledown, location.abovebar, color.red, size=size.small)
What this does:
- Plots a 9 EMA and 21 EMA on your chart
- Marks buy/sell signals with triangles when they cross
- Lets you switch between SMA and EMA from the settings panel
Step 2: Adding a Trend Filter (This Is What Most Tutorials Skip)
Raw crossover signals generate too many false signals in sideways markets. Here's how I filter them:
//@version=6
indicator("MA Crossover — Filtered", overlay=true)
// --- Inputs ---
fastLen = input.int(9, "Fast MA Length", minval=1)
slowLen = input.int(21, "Slow MA Length", minval=1)
trendLen = input.int(50, "Trend MA Length", minval=1)
useFilter = input.bool(true, "Use Trend Filter")
// --- Calculate MAs ---
fastMA = ta.ema(close, fastLen)
slowMA = ta.ema(close, slowLen)
trendMA = ta.sma(close, trendLen)
// --- Crossover Detection ---
bullCross = ta.crossover(fastMA, slowMA)
bearCross = ta.crossunder(fastMA, slowMA)
// --- Trend Filter ---
bullSignal = useFilter ? (bullCross and close > trendMA) : bullCross
bearSignal = useFilter ? (bearCross and close < trendMA) : bearCross
// --- Plot ---
plot(fastMA, "Fast MA", color=color.blue, linewidth=2)
plot(slowMA, "Slow MA", color=color.orange, linewidth=2)
plot(trendMA, "Trend MA", color=color.gray, linewidth=1, style=plot.style_stepline)
plotshape(bullSignal, "Buy", shape.triangleup, location.belowbar, color.green, size=size.small)
plotshape(bearSignal, "Sell", shape.triangledown, location.abovebar, color.red, size=size.small)
Why this matters: Adding the 50 SMA as a trend filter means you only take long signals when price is above the trend line, and short signals when below. In my USDJPY backtests, this single filter eliminated about 40% of losing trades in ranging periods.
Step 3: Converting to a Strategy (With Backtesting)
TradingView strategies let you backtest with simulated trades. Here's the crossover as a strategy:
//@version=6
strategy("MA Crossover Strategy", overlay=true, default_qty_type=strategy.percent_of_equity, default_qty_value=100)
// --- Inputs ---
fastLen = input.int(9, "Fast MA Length")
slowLen = input.int(21, "Slow MA Length")
trendLen = input.int(50, "Trend Filter Length")
useFilter = input.bool(true, "Use Trend Filter")
useSL = input.bool(true, "Use Stop Loss")
slPct = input.float(2.0, "Stop Loss %", step=0.1)
// --- Calculate ---
fastMA = ta.ema(close, fastLen)
slowMA = ta.ema(close, slowLen)
trendMA = ta.sma(close, trendLen)
bullCross = ta.crossover(fastMA, slowMA)
bearCross = ta.crossunder(fastMA, slowMA)
bullSignal = useFilter ? (bullCross and close > trendMA) : bullCross
bearSignal = useFilter ? (bearCross and close < trendMA) : bearCross
// --- Entry ---
if bullSignal
strategy.entry("Long", strategy.long)
if bearSignal
strategy.entry("Short", strategy.short)
// --- Stop Loss ---
if useSL
strategy.exit("Long SL", "Long", stop=strategy.position_avg_price * (1 - slPct / 100))
strategy.exit("Short SL", "Short", stop=strategy.position_avg_price * (1 + slPct / 100))
// --- Plot ---
plot(fastMA, "Fast", color.blue, 2)
plot(slowMA, "Slow", color.orange, 2)
plot(trendMA, "Trend", color.gray, 1)
After adding to chart, click the Strategy Tester tab to see backtest results. Pay attention to:
- Net Profit % — overall return
- Max Drawdown — worst peak-to-trough drop
- Profit Factor — gross profit / gross loss (above 1.5 is decent)
- Win Rate — percentage of winning trades
Step 4: Setting Up Alerts for Live Trading
This is where TradingView becomes genuinely useful for execution. You can set alerts on crossover events and route them to Telegram, email, or webhook.
// Add these to your indicator (not the strategy version):
alertcondition(bullSignal, "MA Cross Buy", "🟢 MA Crossover BUY signal on {{ticker}} at {{close}}")
alertcondition(bearSignal, "MA Cross Sell", "🔴 MA Crossover SELL signal on {{ticker}} at {{close}}")
To create an alert:
1. Right-click on the chart → Add Alert 2. Select your indicator from the condition dropdown 3. Choose "MA Cross Buy" or "MA Cross Sell" 4. Set notification method (Telegram, webhook, app notification) 5. Set expiration (paid plans get longer alert durations)I route my alerts to a private Telegram channel. When a signal fires, I review the chart manually before deciding whether to act on it. Fully automated execution is a separate topic — if you're interested, I wrote about building automated strategies with Interactive Brokers' Python API.
Step 5: Optimizing Your Parameters
The default 9/21 EMA is a good starting point, but optimal parameters vary by instrument and timeframe:
| Market | Timeframe | Fast/Slow | Trend Filter | Notes |
|---|---|---|---|---|
| USDJPY | Daily | 9/21 EMA | 50 SMA | My production setup |
| BTC/USDT | 4H | 12/26 EMA | 50 SMA | Crypto needs slightly wider windows |
| S&P 500 | Daily | 20/50 SMA | 200 SMA | Classic institutional setup |
| EUR/USD | 1H | 5/13 EMA | 34 EMA | Scalping-friendly |
Common Mistakes to Avoid
1. Overfitting parameters. If your strategy only works with a 13.7 EMA and a 27.3 SMA on USDJPY 4H from 2023-2024, you've curve-fitted. Use round numbers. If it doesn't work with 10/20, it probably doesn't work. 2. Ignoring transaction costs. TradingView's strategy tester defaults to zero commission. Set realistic values: for forex, 1-2 pips spread; for crypto on a platform like Hyperliquid, 0.035% taker fees add up fast. 3. No stop loss. A moving average crossover can keep you in a losing trade for weeks while it waits for the reverse cross. Always have a stop loss — 2-3% of position value is a reasonable starting point. 4. Trading every crossover. The trend filter exists for a reason. In a ranging market, MAs whipsaw constantly and every cross is a false signal.Going Beyond: What I Actually Use
The Pine Script crossover above is a building block. My actual USDJPY strategy layers additional filters:
- Momentum confirmation — 60-day rate of change must align with the crossover direction
- Seasonal filter — certain months historically perform better for specific pairs
- Fixed holding period — I exit after a set number of days rather than waiting for the reverse cross
---
FAQ
Is a moving average crossover strategy still profitable in 2026?
On its own, a raw MA crossover is marginal at best. The edge comes from combining it with filters — trend direction, momentum, volatility, or seasonality. Think of the crossover as a timing mechanism, not a complete strategy.
What's the best moving average type for crossover strategies — SMA or EMA?
EMA reacts faster to recent price changes, which means earlier entries but more false signals. SMA is smoother but slower. For shorter timeframes (1H, 4H), I prefer EMA. For daily charts, either works — I use EMA for the fast line and SMA for the trend filter.
Can I automate Pine Script crossover signals for live trading?
Yes — TradingView alerts can send webhooks to external systems. You'd need a webhook receiver that connects to your broker's API. I use TradingView for signal generation and Interactive Brokers' Python API for execution. Some traders also use services like 3Commas or Alertatron as middleware.
What timeframe works best for MA crossover strategies?
Daily charts produce the most reliable signals with the least noise. 4H is good for active traders who can monitor positions. Anything below 1H generates too many false signals for MA crossovers to be practical — you'd need additional confluence (volume, orderflow, etc.).
How do I avoid false signals from moving average crossovers?
Three approaches that work in practice: (1) Add a trend filter — only take signals in the direction of a longer-term MA. (2) Require a close above/below the crossover level, not just an intrabar touch. (3) Wait for a confirmation bar after the crossover before entering.
---
Try It Yourself
TradingView is hands-down the best platform for developing and testing Pine Script strategies. The free plan lets you build indicators; paid plans unlock server-side alerts and more historical data for backtesting.Start with the filtered crossover (Step 2), apply it to whatever market you trade, and see how it performs before committing real capital.
---
*This article contains affiliate links. If you sign up through them, I may earn a commission at no extra cost to you. I only recommend tools I actually use. Trading involves risk — past performance doesn't guarantee future results.*