Instruments & discovery
Instruments & discovery
Tick sizing and point values, the typed instrument catalog, live symbol search, how the loader finds your plugin, and the conformance kit that gates it.
On this page
Instrument metadata
IInstrumentMetadata (namespace SabrTrader.Pipeline.Bars) is how the
platform learns the numbers behind an instrument: tick size, point value, quantity unit and step,
quote currency, price precision. Get tick size wrong and every chart, ladder and PnL figure for
that instrument is wrong with it.
Most venues never implement the interface by hand — hydrate the shipped
InMemoryInstrumentMetadata from your reference data as it arrives, and publish it as
IDataProvider.Instruments.
IInstrumentMetadata (the members most venues fill)double GetTickSize(string instrumentId);
bool IsRegistered(string instrumentId);
double? TryGetPointValue(string instrumentId);
QuantityUnit GetQuantityUnit(string instrumentId); // Contract | Share | Unit …
double? TryGetLotSize(string instrumentId);
double? TryGetQuantityStep(string instrumentId);
double? TryGetMinQuantity(string instrumentId);
int? TryGetPriceSignificantFigures(string instrumentId);
string? TryGetQuoteCurrency(string instrumentId);
InstrumentCategory? TryGetCategory(string instrumentId);
The typed catalog
IVenueInstrumentCatalog is THE surface for "what instruments does this venue have",
probed on the data port with AsCapability<IVenueInstrumentCatalog>(). It serves
both consumers: the persistent symbol-catalog sync and the live exchange-browse listing in the
symbol picker.
IVenueInstrumentCatalog.cspublic interface IVenueInstrumentCatalog
{
// May be empty right after connect and GROW as background loads land.
IReadOnlyList<VenueCatalogEntry> ListEntries();
// Raised when the entry set changes. May fire on any thread; the host marshals.
event Action? EntriesChanged;
}
public readonly record struct VenueCatalogEntry(
string InstrumentId, // the venue's raw id ("ESU6", "AAPL")
VenueAssetClass AssetClass, // the class the VENUE lists it under
string? Exchange = null, // catalog identity ("GLBX", "XNAS")
string? Description = null,
string? ProductCode = null, // product root ("MES") — browse groups by it
string? InstrumentType = null, // the venue's own label ("Future"), display-only
string? Expiration = null); // as the venue reports it ("20260918")
Typed entries are the authoritative form, because asset class often cannot be inferred
from the symbol string. A dual-dataset venue legitimately lists ES both as a futures
root and as an equity ticker, and only the venue knows which catalog each occurrence came from.
Give ids listed under several classes distinct Exchange codes so their catalog
identities never collide.
VenueCapabilities.InstrumentCatalog and the host schedules the symbol-catalog sync
against it.Flat id listing
IInstrumentDirectory.ListInstruments() returns a venue's instrument ids as a flat list.
That is what the symbol picker reads to show everything a venue carries, and it is the right surface
for exactly that job — a list of strings, no classification, no identity.
IVenueInstrumentClassifier pairs with it: given one raw id, it returns the asset class
and symbol the venue means by it. Author your catalog as typed
VenueCatalogEntry values and both questions are already answered — use the classifier
when a venue's universe is genuinely a flat id list and classification is derivable per id.
IVenueInstrumentClassifier.cspublic interface IVenueInstrumentClassifier
{
VenueInstrumentClassification? Classify(string instrumentId);
}
public readonly record struct VenueInstrumentClassification(
VenueAssetClass AssetClass, string Symbol);
Search & resolve
IRemoteSymbolSearch (probed with AsCapability<T>(), declared as
VenueCapabilities.SymbolSearch) backs the symbol picker's live search box and the
resolve of a symbol the user typed. Return the metadata you already know on each hit — the host
adopts it rather than issuing a second round trip.
IRemoteSymbolSearch.cspublic interface IRemoteSymbolSearch
{
Task<IReadOnlyList<RemoteSymbolHit>> SearchAsync(string query, CancellationToken ct = default);
Task<RemoteSymbolHit?> ResolveAsync(string symbol, CancellationToken ct = default);
}
public sealed record RemoteSymbolHit(
string Symbol,
string? Exchange = null,
string? Description = null,
InstrumentCategory? Category = null,
QuantityUnit? QuantityUnit = null,
double? TickSize = null,
double? PointValue = null,
double? LotSize = null,
double? QuantityStep = null,
double? MinQuantity = null,
string? QuoteCurrency = null);
Search-only universes
Some venues cannot enumerate their universe at all — the only way in is a query. Declare that
with ISearchOnlyUniverse so the host stops expecting an enumerable catalog and
routes discovery through search instead.
ISearchOnlyUniverse.cspublic interface ISearchOnlyUniverse
{
bool UniverseIsSearchOnly { get; }
}
Corporate actions
IInstrumentMetadata.TryGetAdjustmentVersion is the stamp that keeps split- and
dividend-adjusted history out of a stale cache: change the stamp and the cache key changes with it.
It is synchronous, but on some venues the version is only knowable by an async per-symbol lookup —
an equity broker's corporate-actions endpoint, with no bulk list. That is what
IInstrumentAdjustmentResolver is for: the async companion that resolves and caches the
version BEFORE it is read into a cache key.
IInstrumentAdjustmentResolver.csnamespace SabrTrader.Pipeline.Storage;
public interface IInstrumentAdjustmentResolver
{
// Ensure this instrument's adjustment version is resolved + cached, so the
// synchronous TryGetAdjustmentVersion that follows returns the real stamp.
Task EnsureAdjustmentVersionAsync(string instrumentId, CancellationToken ct = default);
}
How the host finds your plugin
- The plugin loader scans plugin assemblies and instantiates every public, concrete,
parameterless-constructible
IVenuePlugin. - Each descriptor's
Manifest.Validate()runs at registration — a malformed manifest fails here, not at dialog render. - Every venue is registered into the connection registry under its
TypeId, and appears in the Connections dialog under its category and asset classes. - When the user saves a profile, the host builds a
VenueSessionContextand callsCreateSession; connecting is a separate step.
Shipping the plugin
-
Ship the full closure. A venue's vendor dependencies are the complete transitive package
set from its
CopyLocalLockFileAssembliesbin — never a hand-written list. Native assets underruntimes\and content files ship with it. -
Vendor sidecars resolve through the load context. A plugin's vendor DLL sitting in the app
root is not in the app's
deps.json; the plugin load context's last-chance resolve hook serves it. Every vendor-package venue relies on that. -
Build with the app running. Pass
-p:SkipPluginDeploy=truewhen an app instance may hold the plugin DLL — never kill the process to win a file lock.
The conformance kit
VenuePluginConformanceKit is the per-venue merge gate. Subclass it in your venue's
test project and the seam checks come for free. Each check exists because its failure costs a
customer something real.
MyVenuePluginConformanceTests.cspublic sealed class MyVenuePluginConformanceTests : VenuePluginConformanceKit
{
protected override IVenuePlugin CreatePlugin() => new MyVenuePlugin();
// The ids this venue carried BEFORE the seam. Saved profiles key on them.
protected override IReadOnlyList<string> LegacyTypeIds => new[] { "MyVenue" };
}
| Check | What it prevents |
|---|---|
| Plugin type is loader-compatible | A non-public / abstract / ctor-less plugin is silently skipped and the venue never appears. |
| Manifests valid, TypeIds unique | A malformed manifest never renders a settings form; a duplicate id collides in the registry. |
| Legacy TypeIds preserved verbatim | A renamed id orphans every saved profile of that venue. |
| Settings schema well-formed | A broken schema produces a form the user cannot complete. |
| Assembly is contracts-only | An engine reference duplicates type identity under the plugin load context. |
| Create / disconnect / dispose safe offline | A session that throws on pre-connect disposal crashes the dialog's cleanup path. |
AssertPortsMatchCapabilities (from your own connected-fake harness) |
A declared capability whose port is absent — or a port wider than the manifest. |
TradingProviderContractKit — see
Contract kit & capabilities.