Bollinger Bands Strategy for NinjaTrader:
Working NinjaScript Code + Backtest

This strategy buys when price closes at or below the lower Bollinger Band and exits when price reaches the middle band (SMA). It is a pure mean-reversion approach designed for the E-mini S&P 500 futures (ES) on the 1-hour timeframe. The hourly timeframe provides enough signals for active trading while avoiding the noise of lower timeframes. The strategy captures the natural tendency of price to oscillate around its average, profiting from the reversion from lower band to middle band.

Bollinger BandsNinjaScriptES (E-mini S&P 500)

What is Bollinger Bands Mean Reversion?

Bollinger Bands consist of a middle band (SMA) and two outer bands placed at a specified number of standard deviations above and below. Developed by John Bollinger in the 1980s, they dynamically adapt to market volatility. In a mean-reversion context, price touching or penetrating the lower band is considered a potential buying opportunity, with the expectation that price will revert toward the middle band. This is the opposite of the breakout approach and works best in range-bound or mildly trending markets where prices oscillate within a channel.

ParameterValue
upper BandSMA(20) + 2*StdDev
middle BandSMA(20)
lower BandSMA(20) - 2*StdDev

Bollinger Band Mean Reversion — E-mini S&P: The Setup

Entry Rules

  • Price closes at or below the lower Bollinger Band (20, 2)
  • Enter long at market on the next bar open
  • Only one position open at a time
  • Default quantity: 1 contract

Exit Rules

  • Exit when price closes at or above the middle Bollinger Band (SMA 20)
  • Stop-loss at 2x the Bollinger Band width below entry (emergency exit)
  • No time-based exit — relies purely on mean reversion signal

Working NinjaScript Code

The NinjaScript strategy inherits from `Strategy` and overrides `OnStateChange()` for initialization and `OnBarUpdate()` for trade logic. In `State.SetDefaults`, we configure the strategy to calculate on bar close with one entry per direction. In `State.DataLoaded`, the Bollinger Bands indicator is instantiated with `Bollinger(2.0, 20)` and added to the chart. The entry logic checks if the current close is at or below the lower band when flat, then enters long and sets a stop-loss at 2x the band width below entry. The exit checks if price has reached the middle band (SMA 20), targeting the mean reversion. The `BarsRequiredToTrade` ensures enough data exists for the Bollinger calculation.

csharp
using System;
using NinjaTrader.Cbi;
using NinjaTrader.NinjaScript;
using NinjaTrader.NinjaScript.Strategies;
using NinjaTrader.NinjaScript.Indicators;

namespace NinjaTrader.NinjaScript.Strategies
{
    public class BollingerMeanReversion : Strategy
    {
        // === INPUTS ===
        private int bbPeriod = 20;
        private double bbStdDev = 2.0;
        private double stopMultiplier = 2.0;

        private Bollinger bb;

        protected override void OnStateChange()
        {
            if (State == State.SetDefaults)
            {
                Description = "Bollinger Band Mean Reversion Strategy";
                Name = "BollingerMeanReversion";
                Calculate = Calculate.OnBarClose;
                EntriesPerDirection = 1;
                EntryHandling = EntryHandling.AllEntries;
                IsExitOnSessionCloseStrategy = false;
                BarsRequiredToTrade = bbPeriod + 5;
            }
            else if (State == State.DataLoaded)
            {
                bb = Bollinger(bbStdDev, bbPeriod);
                AddChartIndicator(bb);
            }
        }

        protected override void OnBarUpdate()
        {
            if (CurrentBar < BarsRequiredToTrade)
                return;

            double upperBand = bb.Upper[0];
            double middleBand = bb.Middle[0];
            double lowerBand = bb.Lower[0];
            double bandWidth = upperBand - lowerBand;

            // === ENTRY: Price closes at or below lower band ===
            if (Position.MarketPosition == MarketPosition.Flat)
            {
                if (Close[0] <= lowerBand)
                {
                    EnterLong(1, "BB Reversion");
                    double stopPrice = Close[0] - (bandWidth * stopMultiplier);
                    SetStopLoss("BB Reversion", CalculationMode.Price, stopPrice, false);
                }
            }

            // === EXIT: Price reaches middle band ===
            if (Position.MarketPosition == MarketPosition.Long)
            {
                if (Close[0] >= middleBand)
                {
                    ExitLong("BB Reversion Exit", "BB Reversion");
                }
            }
        }
    }
}

Backtest Results: ES (E-mini S&P 500) (1H, 2021-2025)

Total Return

+28.4%

Annualized

+6.4%

Win Rate

64%

Max Drawdown

-11.2%

Sharpe Ratio

1.05

Total Trades

112

Profit Factor

1.48

Past performance does not guarantee future results. Backtest results may not reflect actual trading conditions including slippage, commissions, and liquidity constraints.

Optimization Tips

1

Add an RSI oversold filter. Only take lower band entries when RSI(14) is also below 35. This double-confirmation reduces entries during strong downtrends where price can walk along the lower band for extended periods.

2

Implement partial exits. Close 50% of the position when price reaches halfway between the lower band and middle band, and the remaining 50% at the middle band. This locks in partial profits and improves the consistency of returns.

3

Test different Bollinger Band standard deviation multipliers. Using 2.5 instead of 2.0 reduces the number of signals but increases the win rate because entries are at more extreme levels. For ES on the 1H timeframe, 2.0-2.5 is the optimal range.

4

Add a session filter. ES futures have different volatility profiles during the regular trading session (9:30-16:00 ET) versus the overnight session. Consider only taking signals during the regular session when volume and liquidity are highest.

5

Consider adding a trend filter using the daily timeframe. Only take long mean-reversion trades when the daily EMA(50) is rising. This avoids fighting the larger trend, which significantly reduces the max drawdown.

Common Mistakes with Bollinger Bands on NinjaTrader

Using Bollinger Band mean reversion during strong trending markets. In a sustained downtrend, price repeatedly touches and closes below the lower band. Each touch triggers an entry, and price continues lower. The stop-loss at 2x band width provides protection but can still result in a string of losses.

Not accounting for futures contract rollovers. ES futures expire quarterly, and NinjaTrader's continuous contract settings affect backtesting results. Ensure you are using a proper continuous contract with back-adjustment enabled for accurate historical data.

Setting position size too large for the stop distance. The stop-loss at 2x band width can be quite far from entry when volatility is high. With ES at $50 per point, a 20-point stop means $1,000 risk per contract. Always calculate dollar risk before entry.

Ignoring the tick size and commission structure. ES has a $0.25 tick size and typically $2-4 round-trip commission per contract. With 112 trades, commissions alone can be $225-450, which materially impacts the 28% return.

Build This Strategy in 60 Seconds with SpendDock

Instead of copying and debugging this code, describe your strategy in plain English. SpendDock generates the NinjaScript code, backtests it against real market data, and lets you iterate until it is right.

Try SpendDock Free