The BarSpecification

Bar types

The BarSpecification

The immutable, value-comparable identity record that names your bar type and carries its parameters — the half of a custom bar type the engine uses to deduplicate work across charts.

The base record

A BarSpecification is an abstract record, which is deliberate: records compare by value, so two requests for the same instrument and the same settings hash and equate identically, and the engine can hand both charts one shared builder and one output series. The base record carries the instrument id, optional continuous-futures wiring, optional trading-hours (session) binding, and the hooks the host needs for labels and symbol translation. You inherit from it and add your own settings.

BarSpecification.csnamespace TradeStrike.Pipeline.Bars;

public abstract record BarSpecification(string InstrumentId)
{
    // Continuous-futures intent (null for a literal / dated contract).
    public string? ContinuousRoot { get; init; }
    public BarAdjustmentMode ContinuousAdjustment { get; init; } = BarAdjustmentMode.BackAdjusted;

    // Trading-hours (session) binding. Null = no session filtering.
    public string? TradingHoursTemplateName { get; init; }
    public bool ResetOnNewTradingDay { get; init; } = true;

    // Human-readable label for logs / UI. Each concrete spec overrides it.
    public abstract string DisplayName { get; }

    // Symbol-translation hook. Default returns this unchanged.
    public virtual BarSpecification WithInstrumentId(string newInstrumentId) => this;

    // Convenience clones (not overridable) the host's resolution chokepoints use.
    public BarSpecification WithContinuous(string? continuousRoot, BarAdjustmentMode adjustment);
    public BarSpecification WithTradingHours(string? templateName, bool resetOnNewTradingDay = true);
}

Members you override

A concrete spec is a sealed record that adds its settings as positional record parameters and overrides two members the host calls into.

  • DisplayName — the abstract label shown in chart UI and logs. Build it from InstrumentId and your settings (e.g. "MNQ Range 4t").
  • WithInstrumentId(string) — the symbol-translation hook. When the chart layer re-targets a series from a user-typed symbol ("ES") to the venue-native one ("/ESM26:XCME"), the host calls this to clone the spec onto the new instrument. The base default returns this unchanged — safe, but a chart on your spec would keep the user-typed symbol. Override it with a record with-expression so your settings copy across, and, for a composite spec, propagate the rewrite into any nested spec.

The base also exposes WithContinuous(...) and WithTradingHours(...). You do not override these — they are non-virtual clone helpers the host's continuous-futures factory and session-resolution chokepoint use to stamp intent onto any spec via the record with. Because ContinuousRoot, ContinuousAdjustment, TradingHoursTemplateName, and ResetOnNewTradingDay are part of the record's value, two otherwise-identical specs with different sessions (one RTH, one ETH) correctly do not share a builder.

Continuous-futures adjustment. BarAdjustmentMode (in TradeStrike.Pipeline.Bars) has three values — None (raw stitched bars), BackAdjusted (additive, the TA-friendly default), and RatioAdjusted (multiplicative, for returns analysis). It is honoured by the data layer that stitches contracts; your builder never sees it.

The built-in subtypes

All ship in BarSpecification.cs. Study them as templates — your custom type will look like one of these.

Record Parameters
TimeBarSpec (string InstrumentId, TimeSpan Period)
TickBarSpec (string InstrumentId, int Ticks)
VolumeBarSpec (string InstrumentId, long Volume, OversizedTradePolicy OversizedTrades = KeepAtomic)
RangeBarSpec (string InstrumentId, int RangeTicks)
RenkoBarSpec (string InstrumentId, int BrickTicks, bool WithWicks = false)
MedianRenkoBarSpec (string InstrumentId, int BrickTicks, int TrendThresholdTicks)
KagiBarSpec (string InstrumentId, int ReversalTicks)
PointAndFigureBarSpec (string InstrumentId, int BoxTicks, int Reversal)
LineBreakBarSpec (string InstrumentId, int LineCount)
HeikinAshiBarSpec (string InstrumentId, BarSpecification Underlying)

HeikinAshiBarSpec is the one composite: its Underlying is another BarSpecification (usually a TimeBarSpec, but the framework does not restrict it). Its WithInstrumentId override rewrites both itself and the nested underlying so the composed spec stays self-consistent.

Defining your own spec

To define a bar type, add your settings as positional record parameters, override DisplayName, and override WithInstrumentId with a with-expression that copies InstrumentId across.

MomentumBarSpec.csusing TradeStrike.Pipeline.Bars;

public sealed record MomentumBarSpec(string InstrumentId, int Threshold)
    : BarSpecification(InstrumentId)
{
    public override string DisplayName => $"{InstrumentId} Momentum {Threshold}";

    public override BarSpecification WithInstrumentId(string newInstrumentId)
        => this with { InstrumentId = newInstrumentId };
}
Keep specs pure data. A spec is comparable, hashable, and used as a dictionary key. Hold only the parameters that define the series identity — no live state, no services, no tick size. Tick size is resolved later, when the builder is created (see Registration).

A setting worth knowing: OversizedTradePolicy

One built-in setting deserves a callout because its default is load-bearing for orderflow. The VolumeBarSpec takes an OversizedTradePolicy alongside its volume threshold, which decides what happens when a single trade is larger than the volume remaining in the forming bar.

  • KeepAtomic (default) — treat the trade as one indivisible event: the bar closes with its full traded volume even if that overshoots the threshold. This is required for orderflow correctness, because a single exchange print carries one bid/ask snapshot and one aggressor side; splitting it across bars would duplicate that state and corrupt volume-at-price and delta.
  • SplitAcrossBars — divide the oversized trade across consecutive bars to hit the threshold exactly. Each synthetic bar is a single price point, with the tick-count credit on the first bar only. Acceptable for pure volume-only backtests, but wrong for footprint work.

If you author a custom volume-like bar type, mirror this choice and default to KeepAtomic unless you have a specific reason not to — a footprint indicator built on a SplitAcrossBars series will show corrupted delta.