A tick bar

Bar types

A tick bar

The simplest complete builder: accumulate ticks into OHLC and close a bar every N trades. This is the whole pattern for a tick-driven type, end to end.

SIMPLE A tick bar, end to end

A tick bar closes after a fixed number of trades. The builder below pairs with the MomentumBarSpec from The BarSpecification: it holds the forming bar's state in plain fields, seeds them on the first trade, folds each subsequent trade into the high / low / close, and emits a finished Bar the moment the tick count reaches the spec's Threshold. Three details are worth calling out as you read:

  1. It gates on TickFlags.Trade — quote-only ticks do not count toward an OHLCV bar.
  2. CurrentBar throws while no bar is forming, exactly as the contract requires.
  3. It clears _has after closing, so the very next trade opens a fresh bar.
NTickBarBuilder.csusing System;
using TradeStrike.Pipeline.Bars;
using TradeStrike.Pipeline.Ticks;

public sealed class NTickBarBuilder : ITickBarBuilder
{
    private readonly MomentumBarSpec _spec;
    private bool _has;
    private double _o, _h, _l, _c;
    private long _vol, _ticks;
    private DateTime _start, _end;

    public NTickBarBuilder(MomentumBarSpec spec) => _spec = spec;

    public BarSpecification Spec => _spec;
    public bool HasCurrentBar => _has;

    // Contract: CurrentBar must throw when no bar is forming — callers check HasCurrentBar first.
    public Bar CurrentBar => _has
        ? new(_start, _end, _o, _h, _l, _c, _vol, _ticks)
        : throw new InvalidOperationException("No bar in progress.");

    public int OnTick(in Tick tick, Span<Bar> closedBars)
    {
        if ((tick.Flags & TickFlags.Trade) == 0) return 0;   // trades only; ignore quotes
        _end = tick.ExchangeTimestampUtc;                    // latest trade time for CurrentBar/close

        if (!_has)
        {
            _has = true; _start = tick.ExchangeTimestampUtc;
            _o = _h = _l = _c = tick.Price; _vol = tick.Size; _ticks = 1;
        }
        else
        {
            _h = Math.Max(_h, tick.Price); _l = Math.Min(_l, tick.Price);
            _c = tick.Price; _vol += tick.Size; _ticks++;
        }

        if (_ticks >= _spec.Threshold)
        {
            closedBars[0] = new Bar(_start, tick.ExchangeTimestampUtc,
                                    _o, _h, _l, _c, _vol, _ticks);
            _has = false;                 // next tick opens a fresh bar
            return 1;
        }
        return 0;
    }
}

Where the closing tick belongs

A tick bar leaves ClosingTickBelongsToNextBar at its default of false: the trade that reaches the threshold is the last tick of the bar that just closed, and its volume + aggressor side are credited to that closing bar. That is the correct accounting for any builder that accumulates "up to" a count or volume threshold. Contrast this with range / Renko bars, where the threshold-crossing tick opens the next band — covered on the next page.

No chaining, no seeding. Each tick bar opens at the next trade's own price, so the series does not chain across a gap. That is why tick bars are not seedable at the history→live handoff — the first live tick correctly opens a fresh bar. See Seeding for the bar types that do chain.