Venue plugins

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.

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.Contracts and SabrTrader.Pipeline.Venues.Contracts and 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.TypeId is 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 by in, 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.
Building a broker? Everything here applies unchanged — a broker is a venue whose session also publishes 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.