DEV Community

ICF Market LTD
ICF Market LTD

Posted on AI-assisted

NinjaTrader Tick Replay: What Historical Order Flow Really Contains

An order-flow indicator can behave correctly on a live NinjaTrader chart yet produce a different-looking historical delta. Sometimes the script never processed historical trade events; sometimes the events were present but the historical quote context was incomplete.

Tick Replay can help with the first problem. It does not automatically solve the second.

In brief: Tick Replay rebuilds historical bars using stored trade ticks and replays Last events. It does not provide a complete history of changing bid/ask quotes or depth, and it does not make historical strategy fills equivalent to live execution.

What Tick Replay changes

In a normal historical calculation, an indicator commonly works from completed bars. Setting Calculate = Calculate.OnEachTick by itself does not create intrabar events from data that is not available.

With supported historical tick data and Tick Replay enabled, NinjaTrader reconstructs historical bars from stored trade events. A script designed for Tick Replay can receive historical OnBarUpdate() and OnMarketData() calls.

Two event paths matter:

  • OnBarUpdate() follows the indicator's Calculate mode, such as OnEachTick, OnPriceChange, or OnBarClose.
  • OnMarketData() receives historical MarketDataType.Last events under Tick Replay, including the event's price, volume, and associated best bid/ask prices.

NinjaTrader documents that, for the replayed sequence, the relevant OnBarUpdate() runs before its corresponding OnMarketData() event. This matters when you group per-trade volume into bars: the bar boundary and the first Last event of the new bar are not interchangeable callbacks.

The key limitation: Last events are not full quote history

Historical Tick Replay preserves the best bid and ask prices associated with a stored Last event. It does not replay the independent stream of historical bid/ask changes or bid/ask volume events. Nor does it reconstruct historical Level II depth.

Even the trade-time bid/ask context deserves scrutiny: NinjaTrader documents that when the source data lacks those values, the platform may substitute bid/ask prices. A classifier can still return a side, but the result is not proof of genuine historical aggressor-side data.

This distinction is easy to miss when historical charts look convincingly complete.

A minimal NinjaScript Last-event example

This is an excerpt for an indicator class, not a complete copy-and-compile NinjaScript indicator. It demonstrates where the replayed trade's size and quote context come from; it deliberately leaves session boundaries and data-provenance validation to the surrounding implementation.

// Fields declared inside an indicator class
private long buyVolume = 0;
private long sellVolume = 0;
private long unknownVolume = 0;

protected override void OnMarketData(MarketDataEventArgs e)
{
    if (e.MarketDataType != MarketDataType.Last || e.Volume <= 0)
        return;

    // A sane spread is not proof that quotes are historical originals.
    if (e.Bid <= 0 || e.Ask <= 0 || e.Bid >= e.Ask)
    {
        unknownVolume += e.Volume;
        return;
    }

    if (e.Price >= e.Ask)
        buyVolume += e.Volume;
    else if (e.Price <= e.Bid)
        sellVolume += e.Volume;
    else
        unknownVolume += e.Volume; // Inside the spread
}
Enter fullscreen mode Exit fullscreen mode

Notice the use of e.Volume, the size of this Last event. Using Volume[0] as though it were the size of an individual print can double-count volume: it represents bar-series volume, not necessarily the current trade's volume.

These counters are running totals. A bar-level footprint needs additional keys for the instrument, bar/session identity, and integer price level, plus explicit commit/reset logic.

Three traps that distort historical delta

1. Using real-time bid/ask helpers for historical trades

GetCurrentBid() and GetCurrentAsk() are real-time-oriented. NinjaTrader documents that historical calls can return the evaluated bar's close instead. For replayed historical Last events, use the MarketDataEventArgs.Bid and .Ask associated with OnMarketData()—while still checking their provenance.

2. Confusing calculation frequency with data availability

Calculate.OnEachTick controls a calculation path; it does not manufacture missing historical ticks. Conversely, an implementation centered on OnMarketData() does not always need an expensive OnEachTick calculation in OnBarUpdate().

Design for the events actually used, then measure performance with your instrument and provider.

3. Treating reconstructed data as backtest-quality execution data

Tick Replay is designed for historical data-event processing, not precise historical order-fill simulation. Missing quote information, event ordering assumptions, and unsupported bar types can all affect the result.

NinjaTrader also notes compatibility limits for some multi-time-frame/multi-instrument scripts and certain bar types, including built-in Renko and Line Break variants. Test those combinations rather than assuming support.

A practical checklist before you trust a chart

  1. Confirm the provider supplies the required historical Last tick data for the requested dates.
  2. Inspect how the provider and platform handle missing trade-time bid/ask context.
  3. Log several OnBarUpdate() / OnMarketData() sequences near a bar boundary.
  4. Confirm that the sum of event sizes matches the volume accounting in your aggregation.
  5. Test the historical-to-realtime transition and compare with recorded live data where possible.
  6. Benchmark the actual dataset; avoid universal claims about Tick Replay loading time.

At ICF Market, our free volume and order-flow education series explains the trading concepts behind delta and volume-at-price. This article focuses on the NinjaScript data mechanics required to represent those concepts honestly.

Key takeaways

  • Tick Replay replays historical Last events, not an independent historical quote-change stream.
  • OnMarketData() is the appropriate source for trade-event price, size, and associated best bid/ask values.
  • Quote availability, bar boundaries, and execution-fidelity limits still need explicit tests.

Discussion: When you build historical delta in NinjaScript, how do you indicate that bid/ask context was missing or substituted by the provider?

Official NinjaTrader references


Disclosure: This article was prepared with AI assistance and reviewed against NinjaTrader's published documentation. The code excerpt was not compile-tested in NinjaTrader 8/8.1 during this editorial review. Educational content only; not trading advice.

Top comments (0)