A tick bar
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:
- It gates on
TickFlags.Trade— quote-only ticks do not count toward an OHLCV bar. -
CurrentBarthrows while no bar is forming, exactly as the contract requires. - It clears
_hasafter 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.