Bars & series
Bars & series
The OHLCV bar value type, the bar-specification records that identify a series, the
builder interfaces that construct bars from ticks, per-instrument metadata, and the barsAgo-indexed
series you read in OnBarUpdate. Namespaces TradeStrike.Pipeline.Bars and
TradeStrike.Pipeline.Series.
On this page
Bar
TradeStrike.Pipeline.Bars · a readonly record struct (64 bytes, passed by
value through pipeline callbacks). For non-time bars there is no fixed period — EndUtc
is the timestamp of the closing tick.
Bar.cspublic readonly record struct Bar(
DateTime StartUtc,
DateTime EndUtc,
double Open,
double High,
double Low,
double Close,
long Volume,
long TickCount);
| Member | Meaning |
|---|---|
StartUtc |
Bar open time. |
EndUtc |
Moment the bar closed (closing-tick timestamp for non-time bars). |
Open / High / Low / Close
|
OHLC prices. |
Volume |
Total traded volume in the bar. |
TickCount |
Number of underlying ticks that formed the bar. Optional metadata for time bars; load-bearing for order-flow / tick bars. |
BarSpecification
An abstract record that is the identity of a series — comparable by value,
hashable, serialisable. Two consumers asking for the same spec share one builder and one output series.
Adding a bar type is one new record plus one builder; no core changes.
BarSpecification.cspublic abstract record BarSpecification(string InstrumentId)
{
public string? ContinuousRoot { get; init; }
public BarAdjustmentMode ContinuousAdjustment { get; init; } = BarAdjustmentMode.BackAdjusted;
public string? TradingHoursTemplateName { get; init; }
public bool ResetOnNewTradingDay { get; init; } = true;
public abstract string DisplayName { get; }
public virtual BarSpecification WithInstrumentId(string newInstrumentId) => this;
public BarSpecification WithContinuous(string? continuousRoot, BarAdjustmentMode adjustment);
public BarSpecification WithTradingHours(string? templateName, bool resetOnNewTradingDay = true);
}
| Member | Meaning |
|---|---|
InstrumentId |
The venue-native symbol the series is built from. Stays the front-month wire symbol even for continuous futures. |
ContinuousRoot |
When non-null, the continuous-futures root (e.g. "ES") the data layer stitches dated contracts behind. Null for a literal / dated / equity / forex / crypto series. |
ContinuousAdjustment |
How rollover gaps are bridged when ContinuousRoot is set. Default BackAdjusted. Ignored when ContinuousRoot is null. |
TradingHoursTemplateName |
Resolved trading-hours template the series is bound to (part of identity, so RTH vs ETH series don't share a builder). Null = no session filtering. |
ResetOnNewTradingDay |
Whether bar construction restarts the grid at each session boundary ("Break at EOD"). Only meaningful when a template is set. Default true. |
DisplayName |
Abstract; each concrete spec overrides for logs / UI (e.g. "MNQ Time 5m"). |
WithInstrumentId(string) |
Copy with a replaced instrument id (symbol-translation step). Default returns this; built-ins and plugin specs override. |
WithContinuous(string?, BarAdjustmentMode) |
Copy carrying continuous-futures intent. |
WithTradingHours(string?, bool) |
Copy bound to a resolved trading-hours template. |
Concrete bar specs
All sealed record in TradeStrike.Pipeline.Bars. Each overrides
DisplayName and WithInstrumentId.
BarSpecification.cspublic sealed record TimeBarSpec(string InstrumentId, TimeSpan Period) : BarSpecification(InstrumentId);
public sealed record TickBarSpec(string InstrumentId, int Ticks) : BarSpecification(InstrumentId);
public sealed record VolumeBarSpec(string InstrumentId, long Volume,
OversizedTradePolicy OversizedTrades = OversizedTradePolicy.KeepAtomic) : BarSpecification(InstrumentId);
public sealed record RangeBarSpec(string InstrumentId, int RangeTicks) : BarSpecification(InstrumentId);
public sealed record RenkoBarSpec(string InstrumentId, int BrickTicks, bool WithWicks = false) : BarSpecification(InstrumentId);
public sealed record MedianRenkoBarSpec(string InstrumentId, int BrickTicks, int TrendThresholdTicks) : BarSpecification(InstrumentId);
public sealed record KagiBarSpec(string InstrumentId, int ReversalTicks) : BarSpecification(InstrumentId);
public sealed record PointAndFigureBarSpec(string InstrumentId, int BoxTicks, int Reversal) : BarSpecification(InstrumentId);
public sealed record LineBreakBarSpec(string InstrumentId, int LineCount) : BarSpecification(InstrumentId);
public sealed record HeikinAshiBarSpec(string InstrumentId, BarSpecification Underlying) : BarSpecification(InstrumentId);
| Spec | Closes when… |
|---|---|
TimeBarSpec(Period) |
Every Period (seconds/minutes/hours/days). |
TickBarSpec(Ticks) |
Every N ticks (N positive). |
VolumeBarSpec(Volume, OversizedTrades) |
Accumulated traded volume reaches Volume; OversizedTrades controls a print larger than remaining capacity. |
RangeBarSpec(RangeTicks) |
Price moves RangeTicks ticks from the bar's open; the breaking tick opens the next bar at the close (no gap). |
RenkoBarSpec(BrickTicks, WithWicks) |
Price moves one brick; reversal needs 2 bricks. WithWicks exposes intra-brick excursions (default false = classical wickless). |
MedianRenkoBarSpec(BrickTicks, TrendThresholdTicks) |
"Median Renko" variant: always-wicked, body height BrickTicks, continuation/reversal driven by TrendThresholdTicks. |
KagiBarSpec(ReversalTicks) |
Price reverses ReversalTicks from the active extreme; line closes and a new one opens opposite. |
PointAndFigureBarSpec(BoxTicks, Reversal) |
One column = one bar. BoxTicks box height; Reversal boxes (classically 3) to start a new column. |
LineBreakBarSpec(LineCount) |
Price closes above the highest high / below the lowest low of the last LineCount bars. |
HeikinAshiBarSpec(Underlying) |
Derived from another BarSpecification; OHLC computed from the underlying bars. WithInstrumentId propagates into Underlying. |
BarAdjustmentMode · OversizedTradePolicy
BarAdjustmentMode.cspublic enum BarAdjustmentMode { None, BackAdjusted, RatioAdjusted }
public enum OversizedTradePolicy { KeepAtomic, SplitAcrossBars }
| Value | Meaning |
|---|---|
BarAdjustmentMode.None |
Raw stitched bars; rollover gaps visible. |
BarAdjustmentMode.BackAdjusted |
Additive back-adjustment (default). Standard for TA — absolute price distances stay meaningful. |
BarAdjustmentMode.RatioAdjusted |
Multiplicative back-adjustment. Standard for returns analysis; absolute distances drift. |
OversizedTradePolicy.KeepAtomic |
Default. A single print is atomic — the bar can exceed the threshold. Required for order-flow correctness. |
OversizedTradePolicy.SplitAcrossBars |
Split a print across bars so each equals the threshold. Pure-volume backtests only; do not use with order-flow. |
IBarBuilder
Shared metadata every builder exposes. HasCurrentBar is false until the first bar starts;
reading CurrentBar while false throws.
IBarBuilder.cspublic interface IBarBuilder
{
BarSpecification Spec { get; }
bool HasCurrentBar { get; }
Bar CurrentBar { get; }
}
ITickBarBuilder
Tick-driven builders (Time, Tick, Volume, Range, Renko, …). They consume only
TickFlags.Trade ticks. OnTick writes newly-closed bars into a caller-supplied
span and returns the count (0 mid-bar, 1 typical, N ≥ 2 on a gap).
IBarBuilder.cspublic interface ITickBarBuilder : IBarBuilder
{
int OnTick(in Tick tick, Span<Bar> closedBars);
bool ClosingTickBelongsToNextBar => false;
bool ResetsOnSessionBoundary => true;
bool ExposesFormingBar => true;
int ForceCloseCurrentBar(Span<Bar> closedBars) => 0;
int ForceCloseAtSessionEnd(DateTime sessionEndUtc, Span<Bar> closedBars) => ForceCloseCurrentBar(closedBars);
void BeginSession(DateTime sessionBeginUtc) { }
}
| Member | Meaning |
|---|---|
OnTick(in Tick, Span<Bar>) |
Process one tick; writes closed bars chronologically, returns the count written. |
ClosingTickBelongsToNextBar |
false (default): closing tick credited to the closing bar (Volume / Tick). true: it opens the next bar (Time / Range / Renko). Order-flow uses this to finalize the snapshot before/after crediting the tick. |
ResetsOnSessionBoundary |
Default true (time/tick/volume/range force-close at the boundary). false for continuous price-pattern builders (Renko, Median-Renko, Kagi, P&F, Line-Break). |
ExposesFormingBar |
Default true — CurrentBar is a continuously-forming tail bar. false for classical Renko (returns the last completed brick). |
ForceCloseCurrentBar(Span<Bar>) |
Force-close at a session boundary; returns count written. |
ForceCloseAtSessionEnd(DateTime, Span<Bar>) |
Force-close clamping the bar end to the exact session end. |
BeginSession(DateTime) |
Notify a new session open so a time-grid builder anchors its bars. |
IDerivedBarBuilder
Builders whose input is another series' closed-bar stream (e.g. Heikin Ashi).
IBarBuilder.cspublic interface IDerivedBarBuilder : IBarBuilder
{
int OnSourceBarClosed(in Bar sourceBar, Span<Bar> closedBars);
void PreviewSourceBar(in Bar developingSourceBar);
}
IBarBuilderFactory
Resolves a spec to a freshly-constructed builder. Plugins register a custom spec + builder via the host's
DefaultBarBuilderFactory.Register<TSpec>, or supply their own factory.
IBarBuilderFactory.cspublic interface IBarBuilderFactory
{
IBarBuilder Create(BarSpecification spec);
bool Supports(BarSpecification spec);
}
Seeding capabilities
Opt-in interfaces the historical→live handoff probes so the first live tick continues the series seamlessly. A builder implements whichever applies.
ISeedableBarBuilder.cspublic readonly record struct BarSeedResult(bool Seeded, DateTime Cutoff, bool LastSeriesBarIsInProgress)
{
public static readonly BarSeedResult NotSeeded;
}
public interface ISeedableBarBuilder
{
BarSeedResult SeedFromLastBar(in Bar lastBar, DateTime nowUtc);
}
public interface ISeedableDerivedBarBuilder
{
void SeedFromLastDerivedBar(in Bar lastDerivedBar);
}
public interface IHistorySeedableBarBuilder
{
BarSeedResult SeedFromHistory(IBarSeries series, DateTime nowUtc);
}
| Interface | For |
|---|---|
ISeedableBarBuilder |
Builders that chain from the last single bar (Time re-opens the period; Range opens at the previous close). Volume / tick bars don't chain and aren't seedable. |
ISeedableDerivedBarBuilder |
A derived builder (e.g. Heikin Ashi) whose output depends on its own previous emitted bar. |
IHistorySeedableBarBuilder |
Builders needing more than one bar to continue (e.g. Line-Break's N-line window). Preferred over ISeedableBarBuilder when both are present. |
IInstrumentMetadata
Per-instrument static metadata the runtime needs — most importantly GetTickSize,
used by range / Renko builders. InMemoryInstrumentMetadata is the public thread-safe
fixture/registry.
IInstrumentMetadata.cspublic interface IInstrumentMetadata
{
double GetTickSize(string instrumentId);
bool IsRegistered(string instrumentId);
double? TryGetPointValue(string instrumentId) => null;
QuantityUnit GetQuantityUnit(string instrumentId) => QuantityUnit.Contract;
double? TryGetLotSize(string instrumentId) => null;
double? TryGetQuantityStep(string instrumentId) => null;
double? TryGetMinQuantity(string instrumentId) => null;
string? TryGetQuoteCurrency(string instrumentId) => null;
string? TryGetAdjustmentVersion(string instrumentId) => null;
InstrumentCategory? TryGetCategory(string instrumentId) => null;
}
| Member | Meaning |
|---|---|
GetTickSize(string) |
Minimum price increment (MNQ = 0.25, CL = 0.01). Throws if the instrument isn't registered. |
IsRegistered(string) |
True when GetTickSize would return a real value. |
TryGetPointValue(string) |
Currency value of one full point (ES = 50), or null. Non-throwing. |
GetQuantityUnit(string) |
Unit the quantity is counted in. Default Contract. |
TryGetLotSize(string) |
For Lot instruments, base-currency units per lot (forex standard 100 000), or null. |
TryGetQuantityStep(string) |
Smallest tradable quantity increment, in the instrument's unit, or null. |
TryGetMinQuantity(string) |
Smallest order quantity the venue accepts, or null. |
TryGetQuoteCurrency(string) |
ISO-4217 quote/settlement currency, or null. |
TryGetAdjustmentVersion(string) |
Opaque corporate-action token for split-adjusted equity series, or null. Flows into the cache key. |
TryGetCategory(string) |
The venue's explicit instrument category, or null when none reported. |
InMemoryInstrumentMetadata.Register(instrumentId, tickSize, pointValue?, quantityUnit?,
lotSize?, quantityStep?, minQuantity?, quoteCurrency?, adjustmentVersion?, category?) populates the
registry; only tickSize is required.
QuantityUnit
QuantityUnit.cspublic enum QuantityUnit { Contract = 0, Share, Lot, Unit, Coin }
| Value | Meaning |
|---|---|
Contract |
Futures / derivatives — whole contracts. The default. |
Share |
Equities — shares (whole or fractional). |
Lot |
Forex — standard lots (one lot = TryGetLotSize base-currency units). |
Unit |
Forex — raw base-currency units. |
Coin |
Spot crypto — fractional coin amounts. |
InstrumentCategory
A venue's explicit asset-class signal, used when QuantityUnit alone is ambiguous (a forex
pair and a metal/index CFD are both Lot/Unit). Carried nullable everywhere;
null is the single canonical "no category reported" — there is deliberately no Unknown.
InstrumentCategory.cspublic enum InstrumentCategory { Forex, Equity, Crypto, Index, Commodity, Metal, Bond }
IBarSeries
TradeStrike.Pipeline.Series · read-only bar series with NinjaTrader-style barsAgo indexing:
this[0] is the current bar, this[1] the one before. Backed by a power-of-2 ring
(struct-of-arrays); Count is monotonic, the largest legal barsAgo is
Math.Min(Count, Capacity) - 1.
ISeries.cspublic interface IBarSeries
{
BarSpecification Spec { get; }
int SeriesIndex { get; }
int Count { get; }
int Capacity { get; }
Bar this[int barsAgo] { get; }
bool TryGet(int barsAgo, out Bar bar);
bool TryReadCloseSpan (int fromBarsAgo, int count, out ReadOnlySpan<double> firstSlice, out ReadOnlySpan<double> secondSlice);
bool TryReadOpenSpan (int fromBarsAgo, int count, out ReadOnlySpan<double> firstSlice, out ReadOnlySpan<double> secondSlice);
bool TryReadHighSpan (int fromBarsAgo, int count, out ReadOnlySpan<double> firstSlice, out ReadOnlySpan<double> secondSlice);
bool TryReadLowSpan (int fromBarsAgo, int count, out ReadOnlySpan<double> firstSlice, out ReadOnlySpan<double> secondSlice);
bool TryReadVolumeSpan(int fromBarsAgo, int count, out ReadOnlySpan<long> firstSlice, out ReadOnlySpan<long> secondSlice);
ISeries<double> Close { get; }
ISeries<double> High { get; }
ISeries<double> Low { get; }
ISeries<double> Open { get; }
ISeries<double> Volume { get; }
ISeries<double> Typical { get; } // (H+L+C)/3
ISeries<double> Median { get; } // (H+L)/2
ISeries<double> Weighted { get; } // (H+L+2C)/4
ISeries<double> OHLC4 { get; } // (O+H+L+C)/4
}
| Member | Meaning |
|---|---|
Spec |
Spec the series was constructed from. Stable for its lifetime. |
SeriesIndex |
Position in the runtime's series table. 0 = primary; 1, 2, … = series added via AddDataSeries. Routes per-bar callbacks (matches NT's BarsInProgress). |
Count / Capacity
|
Total bars ever observed (monotonic) / ring size (power of 2). |
this[int barsAgo] |
O(1) random-access read; this[0] = most recent. Materialises a Bar per call. |
TryGet(int, out Bar) |
Safe variant — returns false during warmup instead of throwing. |
TryRead*Span(…) |
Zero-copy contiguous column slices in chronological order; up to two spans when the slice wraps the ring. False when the range exceeds bars in the ring. |
Close … OHLC4
|
Cached price-series projections (single instance per component, so indicator dedup relies on reference equality). |
ISeries<T>
Read-only generic series — the shape indicator output plots expose. Warmup positions hold
double.NaN for double-typed series.
ISeries.cspublic interface ISeries<T> where T : struct
{
int Count { get; }
int Capacity { get; }
T this[int barsAgo] { get; }
bool TryGet(int barsAgo, out T value);
}
IBarSourcedSeries
Marker on an ISeries<double> that knows the IBarSeries it was derived from
— the indicator framework uses it to resolve a child's primary bars in indicator-of-indicator chains.
IBarSourcedSeries.cspublic interface IBarSourcedSeries
{
IBarSeries SourceBars { get; }
}
ChunkedSeries<T>
The public writable backing for indicator output. ISeries<T> +
IClearableSeries; grows without bound (4096-slot chunks), single-writer / multi-reader,
NaN-aware for warmup. this[0] is the newest value.
ChunkedSeries.cspublic sealed class ChunkedSeries<T> : ISeries<T>, IClearableSeries where T : struct
{
public int Count { get; }
public int Capacity { get; } // int.MaxValue (unbounded)
public T this[int barsAgo] { get; }
public bool TryGet(int barsAgo, out T value);
public void Append(T value); // becomes the new this[0]
public void UpdateLast(T value); // rewrite the newest value (intra-bar)
public void Set(int barsAgo, T value); // rewrite at an arbitrary offset (swing/pivot backfill)
public void Clear(); // drop all values, Count -> 0
}
IClearableSeries
Opt-in: a mutable series that can be reset to empty in place — the recalc path clears values without
reallocating, so the chart's IPlot.Values references never dangle.
IClearableSeries.cspublic interface IClearableSeries
{
void Clear();
}