The rules at a glance
- Indicators: a 9-period EMA, a 21-period EMA and a 200-period EMA as the trend filter
- Long: the 9 EMA crosses above the 21 EMA at a candle close, with the price above the 200 EMA
- Short: the 9 EMA crosses below the 21 EMA at a candle close, with the price below the 200 EMA
- Stop loss: the lowest low of the last 10 candles for a long (highest high for a short), or 1.5 × ATR
- Exit: when the EMAs cross back the other way, or at a fixed 2R target; you choose in the settings
What an EMA is
An exponential moving average (EMA) is an average of recent closing prices that gives more weight to the newest ones. Each new close moves it by a fixed share of the gap between the close and the previous EMA. That share is 2 ÷ (period + 1): 0.20 for a 9 EMA and about 0.09 for a 21 EMA. So the 9 EMA turns quickly with the price and the 21 EMA follows more slowly.
When the price starts trending up, the fast line moves first and crosses above the slow line. When the trend fades and the price turns down, the fast line crosses back below. The crossover strategy simply trades those crosses.
Why 9 and 21?
There's nothing special about these two numbers. They're popular and show up in a lot of trading courses, and 8 and 21 (both Fibonacci numbers) is also widely used. What matters is the gap between them: the fast line has to be quick enough to catch a turn, and the slow line slow enough to ignore small wiggles. The script lets you change both, so you can test 8/21, 10/20 or anything else on your own market.
The rules, step by step
1. Add three EMAs
A 9 EMA and a 21 EMA for the signals, and a 200 EMA for the trend. The 200 EMA is the filter: above it, only longs; below it, only shorts.
2. Wait for a cross at a candle close
The signal is a candle that closes with the 9 EMA on the other side of the 21 EMA from where it was on the previous close. Lines that touch during a candle and separate again don't count.
3. Place the stop
For a long, use the lowest low of the last 10 candles; for a short, the highest high. That puts the stop beyond the swing the cross came out of. The other choice in the script is 1.5 × the 14-period ATR from the entry, which adapts to how much the market is moving.
4. Choose your exit
The classic exit is the opposite cross: hold the long until the 9 EMA crosses back below the 21 EMA. It rides long trends but gives back part of the move, because the cross comes late. The other exit is a fixed target at twice the risk, which banks the profit sooner and misses the long runs. You'll see both in the example.
5. Size from the stop
Risk the same 0.5% to 1% on every trade and work out the lot size from the stop distance. The script does this automatically.
Worked example: EURUSD, 1-hour chart
9 EMA 21 EMA
After a slide, EURUSD bases and starts to climb, and the candle that closes at 1.1656 takes the 9 EMA above the 21 EMA. The lowest low of the last 10 candles is 1.1622, 34 pips below. With $100 of risk that's $100 ÷ (34 × $10) = 0.29 lots, or $2.90 a pip, and a $98.60 loss if the stop is hit.
| Exit | Where it closes | Time in the trade | Result on 0.29 lots |
|---|---|---|---|
| Opposite cross | 1.1698 | 42 hours | +42 pips, $121.80 (about 1.2R) |
| Fixed 2R target | 1.1724 | 23 hours | +68 pips, $197.20 (2R) |
Here the 2R target wins: the price runs through 1.1724 to about 1.1745, then stalls and turns, and by the time the 9 EMA crosses back below the 21 EMA, a good part of the move has gone. In a long, smooth trend it works the other way round, and the cross exit holds on for far more than 2R. Neither exit is better in general, which is why the script lets you test both on your own pair. The 200 EMA isn't drawn; under these rules the long only counts if the price is above it at the cross, so assume it is here. After a slide like this it often isn't, and the filter would skip the trade.
Crossovers lag, and that has a cost
Moving averages are built from past prices, so a crossover always comes after the turn has started. In a strong trend that doesn't matter much, because there's plenty of move left. In a sideways market it's the whole problem: the two lines cross back and forth, and each cross is a small loss plus the spread. For crossover systems, most losses tend to come from those flat stretches; your backtest will show how much.
You can't remove the lag, but you can avoid some of the chop:
- The 200 EMA filter. It's in the script. It keeps you trading with the bigger trend and cuts the number of trades.
- Trading hours. On forex pairs, crosses during the quiet Asian hours are more likely to be noise. Many traders only act on crosses during London and New York; see the forex market hours.
- A higher-timeframe check. Take a 1-hour cross only if the 4-hour chart's 9 EMA is on the same side of its 21 EMA.
- Distance from the 200 EMA. Crosses right on top of the 200 EMA are where the market is undecided.
Only the first filter is built into the script. Test the others by hand before you add them.
Risk management
- Expect losing streaks in ranges. A run of small losses while the market goes sideways is normal for any crossover system. Fixed, small risk per trade is what lets you survive until the next trend.
- Don't override the signal. Skipping a cross because the last two lost is how traders miss the trend that pays for them.
- Check the cost per trade. On the 5-minute chart, crosses come often and the stops are small, so the spread is a big share of each trade. The higher the timeframe, the smaller that share.
- Mind the overnight swap. On the 1-hour and 4-hour charts, trades can last days. The swap calculator shows what holding costs.
Common mistakes
- Acting before the candle closes. The EMAs can cross and uncross several times inside one candle.
- Trading every cross in a range. If the two lines are flat and tangled together, there's no trend to follow.
- Moving the stop to break-even too soon. Trends pull back. A stop at entry gets hit by the first normal dip.
- Changing settings after every loss. 9/21 one week, 5/13 the next: you never learn whether any of them work. Test, choose, then stick with it for a decent sample.
Backtest it yourself on TradingView
This page doesn't quote a win rate or a profit figure, and you should be wary of anyone who does without showing the test. Results change with the market, the dates, the data feed and the costs you put in. The honest way is to run the test yourself, and the free script below does the work in a couple of minutes.
- Open the chart. On TradingView, open EURUSD on the 1-hour chart.
- Add the script. Open the Pine Editor from the Pine icon in the panel on the right of the chart (older layouts have it as a tab under the chart), delete the code that's already there, paste this strategy's code and click "Add to chart".
- Put in your costs. Open the strategy's settings and go to Properties. Enter your broker's commission and a little slippage. A test without costs flatters every strategy, and short-term ones most of all.
- Read the Strategy Tester. The panel under the chart shows the net profit, the maximum drawdown, the profit factor, the number of trades and a list of every trade. Click a few trades and check on the chart that each one follows the rules. Trade sizes are shown in units rather than lots: 100,000 units is one lot of a forex pair, and 100 ounces is one lot of gold.
- Change one setting at a time. Compare the two exits first, then the swing stop with the ATR stop, the 200 EMA filter on and off, and other pairs of EMA lengths such as 8/21. Run the same settings on the 15-minute and 4-hour charts; crossover systems usually behave very differently across timeframes.
As a rough guide, don't trust a result with fewer than 100 trades, a profit factor under 1 once costs are in, or a drawdown you couldn't sit through with real money. Settings tuned to look good on one stretch of history often fail on the next, so check the same settings on dates you didn't tune them on, then demo trade the strategy for a few weeks.
The script sizes each trade from the stop loss and your risk percentage, starts with $10,000 and assumes 1:100 leverage (a 1% margin setting), so the tester doesn't flag margin calls on normal forex trade sizes. Change these under Properties to match your account. By default TradingView fills orders at the open of the candle after the signal, so the fill can differ a little from the signal candle's close. In either exit mode, the script reverses straight away when an opposite cross gives a new signal in the trend's direction.
Download the free Pine Script and PDF
The script is written in Pine Script version 6 for this guide and follows the rules above. It can't read the economic calendar, so skipping news days is up to you. It's free to use, change and share. The PDF is a one-page cheat sheet with the rules, a worked example with both exits, the lot size maths and a checklist, to keep next to your chart.
Show the code (Pine Script v6, 70 lines)
//@version=6
// 9 and 21 EMA Crossover strategy by PipLedger (https://pipledgerfx.com/9-21-ema-crossover-strategy)
// Buys when the 9 EMA crosses above the 21 EMA, sells on the opposite cross, with an optional 200 EMA trend filter.
// Free to use and change. For education and backtesting only; not financial advice.
strategy("PipLedger: 9 and 21 EMA Crossover", shorttitle="9/21 EMA", overlay=true, initial_capital=10000, pyramiding=0, default_qty_type=strategy.fixed, default_qty_value=1, margin_long=1, margin_short=1, currency=currency.USD)
// ---------- Inputs
fastLen = input.int(9, "Fast EMA", minval=1)
slowLen = input.int(21, "Slow EMA", minval=2)
useTrend = input.bool(true, "Only trade in the direction of the trend EMA")
trendLen = input.int(200, "Trend EMA", minval=10)
stopType = input.string("Swing", "Stop loss", options=["Swing", "ATR"])
swingBars = input.int(10, "Swing lookback (bars)", minval=2)
atrMult = input.float(1.5, "ATR multiple (for the ATR stop)", minval=0.1, step=0.1)
exitMode = input.string("Opposite cross", "Exit", options=["Opposite cross", "R multiple"])
rr = input.float(2.0, "Target (R multiple, when used)", minval=0.5, step=0.25)
riskPct = input.float(1.0, "Risk per trade (% of equity)", minval=0.1, maxval=5.0, step=0.1)
// ---------- Helpers
fx = strategy.convert_to_account(1.0) // one unit of the chart's quote currency in account currency (1 on USD-quoted charts)
calcQty(float entry, float stop) =>
float dist = math.abs(entry - stop)
dist > 0 and not na(fx) ? strategy.equity * riskPct / 100 / (dist * syminfo.pointvalue * fx) : 0.0
emaF = ta.ema(close, fastLen)
emaS = ta.ema(close, slowLen)
emaT = ta.ema(close, trendLen)
atr = ta.atr(14)
lowSw = ta.lowest(low, swingBars)
highSw = ta.highest(high, swingBars)
bull = ta.crossover(emaF, emaS)
bear = ta.crossunder(emaF, emaS)
longSig = bull and (not useTrend or close > emaT)
shortSig = bear and (not useTrend or close < emaT)
longSl = stopType == "Swing" ? lowSw : close - atr * atrMult
shortSl = stopType == "Swing" ? highSw : close + atr * atrMult
longQty = calcQty(close, longSl)
shortQty = calcQty(close, shortSl)
longGo = longSig and longSl < close and longQty > 0
shortGo = shortSig and shortSl > close and shortQty > 0
// ---------- Exits on the opposite cross (skipped when the same cross opens a trade the other way: that reverses the position)
if exitMode == "Opposite cross"
if bear and strategy.position_size > 0 and not shortGo
strategy.close("Long", comment="9 EMA crossed below 21")
if bull and strategy.position_size < 0 and not longGo
strategy.close("Short", comment="9 EMA crossed above 21")
// ---------- Entries (an entry against an open trade reverses it, in either exit mode)
if longGo and strategy.position_size <= 0
strategy.entry("Long", strategy.long, qty=longQty)
if exitMode == "R multiple"
strategy.exit("Long exit", "Long", stop=longSl, limit=close + (close - longSl) * rr)
else
strategy.exit("Long stop", "Long", stop=longSl)
if shortGo and strategy.position_size >= 0
strategy.entry("Short", strategy.short, qty=shortQty)
if exitMode == "R multiple"
strategy.exit("Short exit", "Short", stop=shortSl, limit=close - (shortSl - close) * rr)
else
strategy.exit("Short stop", "Short", stop=shortSl)
// ---------- Chart
plot(emaF, "9 EMA", color=color.new(color.teal, 0), linewidth=2)
plot(emaS, "21 EMA", color=color.new(color.orange, 0), linewidth=2)
plot(useTrend ? emaT : na, "Trend EMA", color=color.new(color.gray, 0))
plotshape(longSig, "Bullish cross", shape.triangleup, location.belowbar, color.teal, size=size.small)
plotshape(shortSig, "Bearish cross", shape.triangledown, location.abovebar, color.red, size=size.small)Trading this from India
Residents can trade EURUSD, GBPUSD and USDJPY futures and options on NSE and BSE through a SEBI-registered broker. The contracts are quoted the same way as the spot pair, one lot is 1,000 units of the base currency, and they're cash-settled in rupees. They trade only from 9 a.m. to 7:30 p.m. IST, volumes are thin and not every broker offers them, so check the hours and the spread first. Trading forex or CFDs with offshore online platforms isn't allowed for residents under FEMA, even if the app is easy to download. Details are in is forex trading legal in India.
The script doesn't use pips, so it runs unchanged on index charts such as the Nifty 50 and Bank Nifty on TradingView.
More strategies with free Pine Scripts: London breakout, break and retest, liquidity sweep, XAUUSD scalping.