Venue plugins
Venue plugins: overview
Teach the platform to talk to a new venue — a data feed, a broker, a crypto exchange, a levels vendor. One plugin DLL, one seam, one SDK: the platform holds no knowledge of your venue, and your venue holds no knowledge of the platform's internals.
On this page
One seam, one plugin
A venue is anything the platform connects to for market data, order routing, or both: a futures feed, a broker, a crypto exchange, a levels vendor, a vendor REST API. Every venue is one plugin DLL that references only the SDK contract packages, and the platform holds no compile-time knowledge of it whatsoever. Drop the DLL in, and the venue appears in the Connections dialog.
Three types carry the whole model, all in namespace
SabrTrader.Pipeline.Venues (package
SabrTrader.Pipeline.Venues.Contracts):
IVenuePlugin
The ONE export the loader looks for. It returns the venues this DLL provides — usually one, but a broker family or a crypto container ships many.
IVenueDescriptor
One venue, declaratively: a VenueManifest (id, capabilities, settings schema) plus
the handful of pre-connect behaviours, plus the session factory. Stateless.
IVenueSession
One live connection profile: the lifecycle owner of everything the venue runs, and the composition root for its ports. All per-connection state lives here.
graph LR L["Plugin loader
scans assemblies"] -->|discovers| PL["IVenuePlugin"] PL -->|Venues| D["IVenueDescriptor
+ VenueManifest"] D -->|SettingsSchema| UI["Host renders
settings form"] UI -->|CreateSession(context)| S["IVenueSession
(Disconnected)"] S -->|ConnectAsync| C["Connected"] C -->|Data / Trading / GetPort| H["Charts · DOM · Orders · Chains"]
The ports a session publishes
A session hands the platform the ports it already speaks, and the platform reads them the same way
for every venue. Two are properties; everything else is probed, so a consumer branches on
null — "this venue offers that" or "it doesn't" — and stays free of venue names.
| Port | How the host reaches it | Serves |
|---|---|---|
IDataProvider Data |
Property; null on a trading-only venue | Backfill, live ticks, depth/MBO, instrument metadata, summaries |
ITradingProvider Trading |
Property; null on a data-only venue | Accounts, orders, positions, fills |
IOptionChainSource |
session.AsPort<T>() |
Option chains + live quotes |
IVenueTradeHistorySource |
session.AsPort<T>() |
Historical fills for the performance blotter |
IVenueCashActivitySource |
session.AsPort<T>() |
Posted financing / interest / dividends |
ILevelsFeed |
session.AsPort<T>() |
Gamma / options levels vendors |
IVenueInstrumentCatalog, IRemoteSymbolSearch, IContinuousFuturesSource, … |
Data.AsCapability<T>() |
Optional facets OF the data plane |
IPositionPropagationDelay, IOcoLegGateway, … |
Trading.AsCapability<T>() |
Optional facets OF the trading plane |
Two layers of capability
VenueCapabilities on the manifest declares intent — what this venue can ever
do, readable before any session exists. The port bitmasks
(ProviderCapabilities, TradingProviderCapabilities) are the runtime
fact for one connected session, and may legitimately be narrower (entitlement,
account tier, a partial connect phase). They may never be wider: the host logs a
wider-than-declared port as a venue bug at bring-up, and the conformance kit fails it.
The manifest layer exists for consumers that must decide something before connecting —
which dialog section lists the venue, whether to schedule the catalog sync, whether to register
a chain source. A fact that only matters at runtime stays on the port layer, and a surface that
carries behaviour rather than a yes/no is not a flag at all: it is probed with
AsCapability<T>() / AsPort<T>().
Where a feature belongs
The seam has exactly three optional-surface mechanisms, each with one non-overlapping job. Two mechanisms exposing the same feature is a defect, not a convenience — pick by this rule:
| Mechanism | Use it for | Examples |
|---|---|---|
IVenueSession.GetPort<T>() |
Lifecycle-scoped SIDE CHANNELS that are not part of the data or trading plane. Registered and unregistered by the host's port registrar on connect/teardown. | Option chains, trade-history backfill, cash activity, levels feeds, a replay clock |
IDataProvider + AsCapability<T>()
|
Optional facets of the data plane. | Instrument catalog, symbol search, search-only universe, continuous futures, summary / OI / fundamentals feeds |
ITradingProvider + AsCapability<T>()
|
Optional facets of the trading plane. | Position-propagation delay, the OCO leg gateway |
What the host hands you
A session is constructed with a VenueSessionContext: the one bundle that replaced
per-venue constructor argument lists. Settings arrive already decrypted; the host owns
profile JSON and DPAPI, and venue code never sees either. Everything a venue implements
is a port on the session — never a context member.
VenueSessionContext.cs (abridged)public sealed class VenueSessionContext
{
public VenueProfileInfo Profile { get; } // profile id, name, paper flag
public IReadOnlyDictionary<string, string> Settings { get; } // decrypted, keyed by schema key
public IVenueNoticeSink Notices { get; } // user notices + activity log
public IVenueStores Stores { get; } // bar / tick / order-flow caches
public IVenueStateStorage Storage { get; } // small venue-scoped state files
public IVenueCredentialWriter Credentials { get; } // persist rotated tokens
public IMarketDepthHost? Depth { get; } // the depth/MBO ingest plane
public IVenueBackfillComposer? BackfillComposer { get; } // host-composed caching chain
public IVenueIvHistorySink? IvHistory { get; } // venue-served IV history
public TimeProvider Clock { get; } // real time; overridable in tests
public IVenueExecutionGate? ExecutionGate { get; } // execution entitlement verdict
}
The rules that never bend
-
Contracts only. A venue plugin references
SabrTrader.Pipeline.ContractsandSabrTrader.Pipeline.Venues.Contractsand nothing else from the platform. An engine reference duplicates type identity under the plugin load context and is a conformance-kit failure. -
TypeId is forever.
VenueManifest.TypeIdis the connection-profile key. Renaming it orphans every saved profile of that venue. - The venue owns its threads. Never block in a host callback — queue and return. In-session transport retries are venue-internal and must be storm-guarded; profile-level reconnect cycling belongs to the host supervisor.
-
The hot path allocates nothing. Tick delivery and
Inject(in T)are the seam boundary: struct events byin, no boxing, no LINQ, no string formatting per event. Tasks belong on lifecycle, backfill and registration paths only. - Disconnect is idempotent and never throws. The host calls it at arbitrary points, including on a session that never connected.
A tour of the chapter
| Page | Covers |
|---|---|
| The plugin & manifest |
IVenuePlugin, IVenueDescriptor, the manifest, the settings schema,
validation, dynamic choices and the symbol grammar. |
| The venue session | Lifecycle, failures, the context, notices, state storage, credential rotation, optional ports, and the data port itself. |
| Backfill (history) | The raw historical sources you write, and the caching / re-aggregation chain the host composes around them. |
| Live ticks & depth | The tick stream, and the hosted depth/MBO plane every venue injects into. |
| Instruments & discovery | Tick sizing, the typed instrument catalog, search, how the loader finds you, and the conformance kit. |
| A complete venue plugin | A full worked venue, end to end. |
Trading. The Broker venues chapter covers
what is specific to routing orders: the trading port, the venue-neutral order components, and the
contract kit that gates every trading venue.