Backfill (history)
Backfill (history)
Backfill is how your provider hands the platform a block of historical bars. The base contract is one method; richer extension interfaces let the host ask for exactly the window it needs, with progress and — for order-flow / open-interest charts — the per-bar microstructure attached.
On this page
The base contract
Backfill is the most commonly implemented capability, because charts and backtests both need history
before they can show anything. The contract starts deliberately small: a single Load that
returns the complete series for a bar specification, ordered oldest-first. If that is
all you implement, the host simply asks for everything and trims as needed.
IBackfillProvider.csusing System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
using TradeStrike.Pipeline.Bars;
namespace TradeStrike.Pipeline.Runtime;
public interface IBackfillProvider
{
// Complete historical set for spec, chronological (oldest first). May return
// an empty list (cold instrument). Honour ct promptly. Implementations own caching —
// the runtime calls Load once per spec per session.
Task<IReadOnlyList<Bar>> Load(BarSpecification spec, CancellationToken ct = default);
}
this[0] is the newest bar.Window & progress extensions
Most real venues can do better than "give me everything" — a REST API usually accepts a lookback or
an explicit date range, and fetching only what is needed is far cheaper. You express that by also
implementing one or more of the extension interfaces below, all in TradeStrike.Pipeline.Runtime.
The host detects them and prefers the most specific one available, falling back to the base
Load when none is present.
| Interface | Adds |
|---|---|
IBackfillProvider |
Load(spec, ct) — the whole series. |
ILookbackAwareBackfillProvider |
Load(spec, TimeSpan lookback, ct) — "last 30 days". |
IRangeAwareBackfillProvider |
Load(spec, fromUtc, toUtcExclusive, ct) — an explicit absolute window. |
IProgressReportingBackfillProvider |
LoadWithProgress(spec, progress, ct) — loading-overlay updates. |
IProgressReportingRangeBackfillProvider |
LoadWithProgress(spec, fromUtc, toUtcExclusive, progress, ct) — range + progress, for accurate percent-complete. |
These compose cleanly: one class can implement the base interface plus the lookback and range variants,
and the host chooses per request. The semantic difference between lookback and range is that ranges are
not relative to "now" — the chart can request an analysis window that ended weeks ago. A
range overload must validate that both timestamps are DateTimeKind.Utc and that
fromUtc is strictly before toUtcExclusive, throwing before any I/O.
IBackfillProvider.cs (extensions)namespace TradeStrike.Pipeline.Runtime;
public interface ILookbackAwareBackfillProvider : IBackfillProvider
{
Task<IReadOnlyList<Bar>> Load(BarSpecification spec, TimeSpan lookback, CancellationToken ct = default);
}
public interface IRangeAwareBackfillProvider : IBackfillProvider
{
// Bars whose timestamps fall in [fromUtc, toUtcExclusive). Both MUST be Utc;
// fromUtc strictly before toUtcExclusive (throws ArgumentException before any I/O).
Task<IReadOnlyList<Bar>> Load(
BarSpecification spec, DateTime fromUtc, DateTime toUtcExclusive, CancellationToken ct = default);
}
ct.ThrowIfCancellationRequested() between chunks.Reporting progress
For multi-second fetches, IProgressReportingBackfillProvider drives the chart's loading
overlay so the UI does not look frozen. You report through an IProgress<BackfillProgress>;
every field is a best-effort estimate except StageMessage. The contract guarantees only that
at least one report fires on success, with a final PercentComplete = 1.0. A null progress
sink means "no reporting requested" — treat it as such without throwing.
BackfillProgress.csusing System;
namespace TradeStrike.Pipeline.Runtime;
public sealed record BackfillProgress(
int BarsReceived, // grows monotonically
DateTime? LatestBarTimeUtc, // null until the first bar arrives
double PercentComplete, // 0.0 when un-estimable, jumps to 1.0 on completion
string StageMessage); // the only fully-reliable field
Order-flow-aware backfill
Footprint / cumulative-delta / volume-profile charts need per-bar bid/ask volume at price, which native
OHLCV bars do not carry. A provider that can deliver bars plus per-bar order flow in one
round-trip implements IOrderFlowAwareBackfillProvider. The host routes to it when at least
one attached indicator is marked RequiresOrderFlow; otherwise it falls back to the base
Load and the chart degrades gracefully. The returned Bars and
OrderFlow lists are equal-length and index-aligned.
IOrderFlowAwareBackfillProvider.csusing System.Threading;
using System.Threading.Tasks;
using TradeStrike.Pipeline.Bars;
namespace TradeStrike.Pipeline.Runtime;
public interface IOrderFlowAwareBackfillProvider : IBackfillProvider
{
// Bars + per-bar orderflow, equal length, aligned indices, chronological.
Task<BackfillResultWithOrderFlow> LoadWithOrderFlow(
BarSpecification spec, OrderFlowBackfillRequest request, CancellationToken ct = default);
}
OrderFlowBackfillRequest is an options bag — one method instead of a lookback/range/progress
overload explosion. Exactly one of Lookback or the (FromUtc,
ToUtcExclusive) pair is meaningful; both null means "your default window". Resolution
selects fidelity: Auto (the default) lets the provider derive a concrete tier from the
primary spec's period, with Tick exact-but-heaviest and Second/Minute
far lighter at the cost of an estimated aggressor. Call request.Validate() to enforce the
field invariants once.
OrderFlowBackfillRequest.csusing System;
using TradeStrike.Pipeline.OrderFlow;
namespace TradeStrike.Pipeline.Runtime;
public sealed record OrderFlowBackfillRequest(
DateTime? FromUtc = null,
DateTime? ToUtcExclusive = null,
TimeSpan? Lookback = null,
IProgress<BackfillProgress>? Progress = null,
OrderFlowResolution Resolution = OrderFlowResolution.Auto) // Auto | Tick | Second | Minute
{
public bool HasRange => FromUtc.HasValue && ToUtcExclusive.HasValue;
public bool HasLookback => Lookback.HasValue;
public void Validate(); // throws on bad Utc kind / ordering / negative lookback
}
Open-interest-aware backfill
Open interest is a venue-reported scalar (outstanding contracts), not derivable from trades, so there is
no tick aggregation — you simply map whatever per-bar OI the feed exposes and emit
double.NaN for bars the venue does not report it for. A provider opts in with
IOpenInterestAwareBackfillProvider; the host uses it when an attached indicator is marked
RequiresOpenInterest, and otherwise the OI channel stays all-NaN.
IOpenInterestAwareBackfillProvider.csnamespace TradeStrike.Pipeline.Runtime;
public interface IOpenInterestAwareBackfillProvider : IBackfillProvider
{
// result.Bars and result.OpenInterest are equal length, aligned (NaN = no OI for that bar).
Task<BackfillResultWithOpenInterest> LoadWithOpenInterest(
BarSpecification spec, OpenInterestBackfillRequest request, CancellationToken ct = default);
}
Both result types — BackfillResultWithOrderFlow and
BackfillResultWithOpenInterest — validate the equal-length alignment invariant in their
constructor, so downstream consumers index either parallel list by the same i without extra
checks. Each exposes Bars, the parallel data list, Count,
FirstBarStartUtc, LastBarEndUtc, and a shared Empty instance.
The source & store seam
The built-in caching backfill provider does not call your venue directly; it sits on a pair of
source and store seams in TradeStrike.Pipeline.Storage. A venue adapter
implements the source (the un-cached upstream fetch); the host's caching layer owns the
store (coverage-tracked, gap-filling) and calls the source only for sub-ranges it does not
already hold. This is why one download of a day's ticks serves every chart and bar type forever, and why
re-opening a chart that already cached the last 30 days makes zero venue calls.
| You implement (source) | For |
|---|---|
IHistoricalTimeBarSource |
LoadTimeBars(instrumentId, period, fromUtc, toUtcExclusive, ct) — native time bars. |
IHistoricalTickSource |
LoadTicks(instrumentId, fromUtc, toUtcExclusive, ct) — raw ticks. ProvidesHistoricalTicks declares whether tick-derived bars can be back-filled. |
INativeBarSource |
SupportsNativeBars(spec) + LoadNativeBars(...) — range / volume / tick bars the venue aggregates server-side. |
IOpenInterestAwareTimeBarSource |
time bars + per-bar OI in one call. |
The *WithProgress twins (IProgressReportingTimeBarSource,
INativeBarProgressSource) add a progress sink that the caching provider prefers when one is
supplied. The cache layer's coverage records use the half-open DateRange value type, where
adjacent [a, b) and [b, c) merge cleanly into [a, c) — a range
that was fetched and produced zero bars (weekend, holiday) stays covered, not re-fetched forever.
IHistoricalTimeBarSource.csusing System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
using TradeStrike.Pipeline.Bars;
namespace TradeStrike.Pipeline.Storage;
public interface IHistoricalTimeBarSource
{
// Bars whose StartUtc falls in [fromUtc, toUtcExclusive), chronological. An empty
// result is legal (the cache still marks the range covered). Honour ct promptly.
Task<IReadOnlyList<Bar>> LoadTimeBars(
string instrumentId, TimeSpan period,
DateTime fromUtc, DateTime toUtcExclusive, CancellationToken ct = default);
}
BackfillSourceUnavailableException — and
only that — for the not-connected condition. The caching provider treats it specially: it serves
whatever the on-disk cache already holds and returns, then re-runs backfill when the connection comes up.
Any other exception propagates unchanged, so real bugs are never masked behind a silent cache-only
fallback.Integrity observers
Two optional observer seams let the host audit feed integrity synchronously with the fetch, before bad
data ever reaches the cache — the answer to NinjaTrader 8's "silent missing tick" pain. Both run on
the backfill task continuation, so an implementation must be cheap and must not throw. A
Null…Observer.Instance no-op default is provided for each.
| Observer | Reports |
|---|---|
ISequenceGapObserver |
a SequenceGapEvent when consecutive ticks for an instrument show a sequence delta > 1 (the venue dropped a tick the adapter never recovered). |
IVolumeConsistencyObserver |
a VolumeMismatchEvent when a bar's TotalBidVolume + TotalAskVolume does not equal its reported Volume — a builder bug or upstream hiccup. |