Metadata & discovery
Metadata & discovery
Instrument metadata lets the platform turn price moves into money; the instrument directory populates a symbol catalog; and discovery is how your finished plugin becomes a first-class data source with no registration code.
On this page
Instrument metadata
Bars and ticks alone do not tell the platform what a one-tick move is worth. To compute P&L, size
positions, build range / Renko bars, and render a price axis correctly, the host needs the instrument's
tick size and (where it exists) point value. You supply those by advertising the
InstrumentMetadata capability and pointing IDataProvider.Instruments at an
IInstrumentMetadata (namespace TradeStrike.Pipeline.Bars).
The interface is synchronous because these lookups happen constantly. Only the first three members are
required — the rest are interface defaults that return null (or QuantityUnit.Contract),
so you implement only what your venue actually knows. GetTickSize throws for an unknown
instrument (better to fail loudly than build range bars on a fabricated increment);
IsRegistered lets callers probe first.
IInstrumentMetadata.csnamespace TradeStrike.Pipeline.Bars;
public interface IInstrumentMetadata
{
// Required:
double GetTickSize(string instrumentId); // e.g. MNQ = 0.25 (throws if unknown)
bool IsRegistered(string instrumentId);
// Optional — interface defaults return null / Contract:
double? TryGetPointValue(string instrumentId); // currency per point (ES = 50)
QuantityUnit GetQuantityUnit(string instrumentId); // Contract | Share | Lot | Unit | Coin
double? TryGetLotSize(string instrumentId); // base units per lot (forex)
double? TryGetQuantityStep(string instrumentId); // smallest tradable increment
double? TryGetMinQuantity(string instrumentId); // venue minimum order size
string? TryGetQuoteCurrency(string instrumentId); // ISO-4217, e.g. "USD"
string? TryGetAdjustmentVersion(string instrumentId); // corporate-action stamp (equities)
InstrumentCategory? TryGetCategory(string instrumentId);// Forex | Equity | Crypto | ...
}
InMemoryInstrumentMetadata — a
thread-safe registry with a fluent Register(instrumentId, tickSize, pointValue: …, quantityUnit: …,
…). It is ideal for a venue whose security master you load once at connect, and for tests. The
typical lifecycle is "host populates at startup, many readers thereafter".populating metadata on connectprivate readonly InMemoryInstrumentMetadata _meta = new();
public IInstrumentMetadata? Instruments => _meta; // with ProviderCapabilities.InstrumentMetadata set
public async Task ConnectAsync(CancellationToken ct = default)
{
foreach (var def in await FetchSecurityMasterAsync(ct))
_meta.Register(def.Symbol, def.TickSize,
pointValue: def.PointValue,
quoteCurrency: def.Currency);
Status = ProviderConnectionStatus.Connected;
ConnectionStatusChanged?.Invoke(Status);
}
The instrument directory
A venue that can enumerate its tradeable universe implements the separate
IInstrumentDirectory interface (namespace TradeStrike.Pipeline.Providers). The host
uses it to populate a searchable symbol catalog — connect once and get every symbol the venue lists,
instead of typing each ticker by hand. It is a separate interface, not a core
IDataProvider member, because not every provider has a discoverable universe; consumers probe
with provider is IInstrumentDirectory.
IInstrumentDirectory.csusing System.Collections.Generic;
namespace TradeStrike.Pipeline.Providers;
public interface IInstrumentDirectory
{
// The instrument ids this provider offers, in its canonical id form (e.g. "BTC/USDT").
// Empty — never null — until the universe has loaded; read it after the provider
// reports Connected if you need it complete.
IReadOnlyList<string> ListInstruments();
}
The unified historical-data service
Consumers do not call your backfill chain directly. They ask one venue-neutral entry point —
IHistoricalDataService (namespace TradeStrike.Pipeline.Services) — for bars
between two UTC timestamps, regardless of which venue owns the symbol. The service resolves the active
provider for a venue key through the active-provider
registry, requires its Backfill to be range-aware, and delegates the load. Caching,
pagination, and retry live inside your provider's chain; the service is thin composition + validation.
Knowing this clarifies why implementing IRangeAwareBackfillProvider matters: the
service requires it.
IHistoricalDataService.csnamespace TradeStrike.Pipeline.Services;
public interface IHistoricalDataService
{
// venueKey matched case-insensitively against the active-provider registry;
// bars carry UTC timestamps. Unknown venue throws InvalidOperationException.
Task<LoadResult> LoadAsync(
string venueKey, BarSpecification spec,
DateTime fromUtc, DateTime toUtcExclusive,
IProgress<LoadProgress>? progress = null, CancellationToken cancellationToken = default);
// Raw recorded ticks for the EveryTickReal backtest path. Default impl throws
// NotSupportedException, so a bar-only service need not implement it.
Task<IReadOnlyList<Tick>> LoadTicksAsync(
string venueKey, string instrumentId,
DateTime fromUtc, DateTime toUtcExclusive,
IProgress<LoadProgress>? progress = null, CancellationToken cancellationToken = default);
}
Discovery
There is no manifest to edit and no registration call to make. The platform discovers connection plugins the same way it discovers indicators and drawing tools — by scanning assemblies for the contracts they implement. Once your factory is on disk it becomes selectable from the host's provider list automatically.
Drop your plugin DLL into the Plugins folder and the loader puts your
IDataProviderFactory into the data-provider-factory bucket. The host lists your provider,
renders a credentials form from RequiredCredentials, and calls Create with the
collected values. From there it is a first-class data source: charts, backtests and strategies use it
like any built-in venue.