Skip to main content

Bollinger Band Mean Reversion

TL;DR

Mean reversion with Bollinger Bands: when price tags the outer band in a non-trending market, fade back toward the middle band (the 20-period SMA). Simple rules, high win rate (~60%), modest per-trade R (~1:1). Works on any liquid instrument but needs strict regime filtering — mean reversion in a trending market is a blowup waiting to happen.

The rules

1. Regime filter. Skip trades when the market is trending:

  • ADX(14) > 25 → skip
  • Price making higher highs and higher lows (or inverse) over the last 20 bars → skip

2. Setup. Price touches or wicks through the outer Bollinger Band (20-period SMA, 2 standard deviations).

3. Rejection trigger. The setup bar must show rejection:

  • For longs: long lower wick, close above the low
  • For shorts: long upper wick, close below the high
  • OR: immediate reversal candle (bullish/bearish engulfing)

4. Entry. Market order at the next bar open after the trigger.

5. Stop. 1× ATR beyond the extreme of the rejection bar.

6. Target. The middle band (20 SMA). Optionally: scale half off at the middle band, trail the rest to the opposite outer band.

7. Time stop. If the trade hasn't hit target within 15 bars on a 5-minute chart (or 10 bars on a 15-minute), close it. Mean reversion that hasn't reverted is probably a trend day.

Expected performance

Realistic stats for ES during non-trending sessions:

  • Win rate: 58–65%
  • Average win / loss: 0.8R / 1R (targets closer than stops)
  • Profit factor: 1.3–1.6
  • Signal frequency: 3–8 trades per day in range regimes, 0 on trend days

The combination of high win rate with modest average R produces a psychologically easier equity curve than trend strategies. Losing streaks tend to be short but the outlier big loss — when a trend breaks out while you were fading — can be a big fraction of monthly P&L.

Full Pine Script (v6)

//@version=6
strategy("Bollinger Mean Reversion", overlay=true, initial_capital=50000, default_qty_type=strategy.fixed, default_qty_value=1)

// ---------- Inputs ----------
bbLen = input.int(20, "BB Length")
bbMult = input.float(2.0, "BB Std Dev Multiplier")
adxLen = input.int(14, "ADX Length")
adxMax = input.float(25, "Skip if ADX > X")
atrLen = input.int(14, "ATR Length")
stopAtrMult = input.float(1.0, "Stop = N × ATR beyond trigger")
timeStopBars = input.int(15, "Time stop (bars)")

// ---------- Bollinger Bands ----------
basis = ta.sma(close, bbLen)
dev = bbMult * ta.stdev(close, bbLen)
upper = basis + dev
lower = basis - dev

plot(basis, color=color.orange, linewidth=2, title="Basis")
plot(upper, color=color.blue, title="Upper")
plot(lower, color=color.blue, title="Lower")

// ---------- Regime filter ----------
[_diPlus, _diMinus, adxVal] = ta.dmi(adxLen, adxLen)
rangeRegime = adxVal <= adxMax

// ---------- Rejection detection ----------
body = math.abs(close - open)
upperWick = high - math.max(close, open)
lowerWick = math.min(close, open) - low
bullReject = lowerWick > 1.5 * body and close > open
bearReject = upperWick > 1.5 * body and close < open

// ---------- ATR ----------
atrVal = ta.atr(atrLen)

// ---------- Entry setups ----------
longSetup = low <= lower and bullReject and rangeRegime and strategy.position_size == 0
shortSetup = high >= upper and bearReject and rangeRegime and strategy.position_size == 0

if longSetup
stopPrice = low - stopAtrMult * atrVal
strategy.entry("BB Long", strategy.long)
strategy.exit("Long Exit", "BB Long", stop=stopPrice, limit=basis)

if shortSetup
stopPrice = high + stopAtrMult * atrVal
strategy.entry("BB Short", strategy.short)
strategy.exit("Short Exit", "BB Short", stop=stopPrice, limit=basis)

// ---------- Time stop ----------
barsInTrade = strategy.position_size != 0 ? bar_index - strategy.opentrades.entry_bar_index(strategy.opentrades - 1) : 0
if barsInTrade >= timeStopBars
strategy.close_all("Time stop")

// ---------- End-of-day flatten ----------
if hour == 15 and minute >= 55
strategy.close_all("EOD")

// ---------- Plot signals ----------
plotshape(longSetup, style=shape.triangleup, location=location.belowbar, color=color.green, size=size.small)
plotshape(shortSetup, style=shape.triangledown, location=location.abovebar, color=color.red, size=size.small)

Automating with CrossTrade

Standard payload:

key=YOUR-SECRET-KEY;
command=place;
account=Sim101;
instrument=ES 06-26;
action={{strategy.order.action}};
qty={{strategy.order.contracts}};
order_type=market;
tif=day;
sync_strategy=true;
market_position={{strategy.market_position}};
prev_market_position={{strategy.prev_market_position}};
out_of_sync=flatten;

On fast-firing reversion strategies, consider adding an out_of_sync=flatten directive so if NT8 and TradingView disagree on position state, NT8 defaults to flat — avoids double-sized positions during fast reversals.

Common mistakes

  • Trading without the ADX filter. Mean reversion in a trend is statistically negative-expectancy. Filter or skip.
  • Trading without the rejection trigger. Pure band-touch entries fail often. The rejection candle is a cheap filter that adds measurable edge.
  • Using tight stops. "Stop at the band" means you're constantly stopped out on normal volatility. ATR-based stops beyond the trigger bar extremes are more durable.
  • Holding too long. Reversion that hasn't reverted in 15 bars is usually a trend — get out. The time stop protects against catastrophic breaks.

Combining with other strategies

Bollinger reversion pairs well with ORB and trend strategies because the regimes they need are opposite:

  • Trending days: ORB and trend crossovers profit; BB reversion should be disabled
  • Range days: BB reversion profits; ORB sits out

Running both with ADX as the regime switch gives you a strategy that trades every kind of day — trend or chop — without over-trading either.

Frequently Asked Questions

What's the win rate on a Bollinger mean reversion strategy?

With proper filters (ADX, rejection trigger, time stop), 58–65% is realistic. Without filters, closer to 45% because trending days produce catastrophic losing streaks. The filters are what make the strategy tradeable.

Why target the middle band instead of the opposite band?

The middle Bollinger Band (20 SMA) is hit far more often than the opposite outer band. Targeting the middle produces a higher win rate. Traders who want bigger winners scale half off at the middle band and trail the rest toward the opposite band.

Does this strategy work on all instruments?

It works on any liquid instrument with enough volatility to reach the bands — ES, NQ, CL, GC, currency futures, forex majors. On very low-volatility instruments, band touches are rare and signal count is too low to be useful. On illiquid instruments, slippage erodes the edge.

Should I use a 1.5σ or 2σ Bollinger Band?

2σ is the standard and produces the cleanest signals. 1.5σ fires more often but each signal is lower quality. 2.5σ fires rarely but with stronger edge. Start at 2σ and adjust only if backtesting clearly supports a change.