This research note defines a small, testable NinjaTrader 8 indicator: mark a bar when its close is outside a Bollinger Band and its volume is above a moving average of volume. The example is educational and unvalidated. It does not establish that a marked bar will reverse, that the logic is suitable for live trading, or that any parameter set is profitable.
Research question
A Bollinger Band measures distance from a rolling mean in units of recent dispersion. For a price input P, lookback n, and multiplier k, the conceptual formulas at time t are:
Middle_t = SMA_n(P)_t
Upper_t = Middle_t + k × Sigma_n(P)_t
Lower_t = Middle_t - k × Sigma_n(P)_t
Here, Sigma_n(P)_t is the standard deviation of the same n observations used by the band calculation. The code delegates the platform-specific calculation to NinjaTrader's Bollinger indicator. Its illustrative defaults are n = 20 and k = 2.0.
For volume V and volume lookback m, this version adds one threshold:
HighVolume_t = V_t > SMA_m(V)_t
The default is m = 10. NinjaTrader's current-bar SMA includes V_t, so the comparison is against an average that already contains the observation being tested. The code does not apply a separate volume multiplier.
Under an independent Gaussian model, about 95.45% of observations fall within two population standard deviations of the mean. Market prices do not satisfy those assumptions in general: observations are serially dependent, volatility changes, distributions can be skewed or heavy-tailed, and a rolling estimate adds sampling error. A close outside a two-standard-deviation band is therefore a relative-distance observation, not a calibrated reversal probability or a declaration that price is intrinsically expensive or cheap.
Signal definition
On each completed bar, the indicator evaluates four explicit conditions:
Close[0] > Upper[0]marks an upper-band close.Close[0] < Lower[0]marks a lower-band close.Volume[0] > SMA(Volume, 10)[0]marks above-average volume under the default.- A marker is drawn only when one price condition and the volume condition are both true.
This is a candidate-event detector. It does not identify who traded, distinguish absorption from continuation, confirm exhaustion, or wait for a subsequent reversal. Because Calculate.OnBarClose is used, an intrabar band crossing that closes back inside the band is not marked.
NinjaScript implementation
The implementation creates its dependent indicators in State.DataLoaded, waits for the configured lookbacks, and draws chart annotations from OnBarUpdate. An Indicator cannot call the strategy-only AddChartIndicator method, so users who want the Bollinger lines visible should add NinjaTrader's standard Bollinger indicator to the chart separately.
#region Using declarations
using System;
using System.ComponentModel.DataAnnotations;
using System.Windows.Media;
using NinjaTrader.NinjaScript;
using NinjaTrader.NinjaScript.Indicators;
using NinjaTrader.NinjaScript.DrawingTools;
#endregion
namespace NinjaTrader.NinjaScript.Indicators.OranselIndustries
{
public class VolatilityReversal : Indicator
{
private Bollinger _bb;
private SMA _volAvg;
protected override void OnStateChange()
{
if (State == State.SetDefaults)
{
Description = @"Marks outside-band closes that also have above-average volume.";
Name = "VolatilityReversal";
Calculate = Calculate.OnBarClose;
IsOverlay = true;
DisplayInDataBox = true;
DrawOnPricePanel = true;
PaintPriceMarkers = true;
ScaleJustification = NinjaTrader.Gui.Chart.ScaleJustification.Right;
IsSuspendedWhileInactive = true;
BBPeriod = 20;
StdDev = 2.0;
VolPeriod = 10;
}
else if (State == State.DataLoaded)
{
_bb = Bollinger(StdDev, BBPeriod);
_volAvg = SMA(Volume, VolPeriod);
}
}
protected override void OnBarUpdate()
{
if (CurrentBar < Math.Max(BBPeriod, VolPeriod))
return;
bool closesAboveBand = Close[0] > _bb.Upper[0];
bool closesBelowBand = Close[0] < _bb.Lower[0];
bool hasAboveAverageVolume = Volume[0] > _volAvg[0];
if (closesAboveBand && hasAboveAverageVolume)
{
Draw.ArrowDown(this, "Upper" + CurrentBar, true, 0,
High[0] + TickSize * 5, Brushes.Red);
BackBrushes[0] = Brushes.DimGray;
}
if (closesBelowBand && hasAboveAverageVolume)
{
Draw.ArrowUp(this, "Lower" + CurrentBar, true, 0,
Low[0] - TickSize * 5, Brushes.Lime);
BackBrushes[0] = Brushes.DimGray;
}
}
#region Properties
[Range(1, int.MaxValue)]
[NinjaScriptProperty]
[Display(Name = "BB Period", Description = "Bollinger Band lookback", Order = 1, GroupName = "Parameters")]
public int BBPeriod { get; set; }
[Range(0.1, 5.0)]
[NinjaScriptProperty]
[Display(Name = "Std Dev", Description = "Band standard-deviation multiplier", Order = 2, GroupName = "Parameters")]
public double StdDev { get; set; }
[Range(1, int.MaxValue)]
[NinjaScriptProperty]
[Display(Name = "Volume Period", Description = "Volume SMA lookback", Order = 3, GroupName = "Parameters")]
public int VolPeriod { get; set; }
#endregion
}
}
Disclosed assumptions and thresholds
| Component | Example value | What the code actually tests |
|---|---|---|
| Price input | Close | Completed-bar close, not the intrabar high or low |
| Band lookback | 20 bars | Rolling platform Bollinger calculation |
| Band multiplier | 2.0 | Two estimated standard deviations from the rolling mean |
| Volume lookback | 10 bars | SMA of volume including the current bar |
| Volume threshold | Greater than 1.0 times the SMA | Any amount above the current volume average |
| Evaluation | On bar close | One evaluation after each completed primary-series bar |
These values are hypotheses, not recommendations. A 20-bar setting on a five-minute futures chart and a 50-bar setting on a daily equity chart answer different questions; neither should be selected without a stated instrument, session template, data feed, sample period, and out-of-sample procedure.
Validation plan
A useful test should define the outcome before examining results. For example, a researcher could measure forward return to the contemporaneous middle band within h bars, maximum favorable and adverse excursion, and the same statistics for matched bars that did not meet the volume condition. Results should be separated by instrument, session, volatility regime, and direction. Transaction costs matter if the signal is later converted into a strategy.
Parameter searches create selection bias. Keep a final out-of-sample segment untouched, report all tested variants rather than only the strongest one, and compare against simple baselines such as every outside-band close. The indicator itself submits no orders, sizes no positions, and implements no exit.
Limitations
Volume meaning varies by instrument and feed; equity consolidated volume, futures exchange volume, and synthetic or broker-specific volume are not interchangeable. On-close processing omits intrabar sequence. A strong trend can generate repeated outside-band closes, and above-average volume can accompany continuation rather than reversal. Drawing an arrow and setting BackBrushes[0] provide visual annotation only.
Part 2 converts the same general event into an execution prototype. That additional code introduces order timing, fill, stop, and cost assumptions that must be evaluated separately.