Live ticks & depth
Live ticks & depth
Streaming trades and quotes through the tick source, and delivering L2 / market-by-order books into the host's one depth ingest plane.
On this page
The tick stream
Live trades ride ITickSource (namespace SabrTrader.Pipeline.Runtime),
published as IDataProvider.LiveTicks. One subscription per instrument, disposed by
the consumer. Quotes ride the same stream, distinguished by TickFlags.
ITickSource.cspublic interface ITickSource : IDisposable
{
IDisposable Subscribe(string instrumentId, Action<Tick> onTick);
// Exchange-routed venues override this; single-feed venues inherit the default.
IDisposable Subscribe(string instrumentId, string? exchange, Action<Tick> onTick)
=> Subscribe(instrumentId, onTick);
}
- Deliver one instrument's ticks from one thread at a time — per-instrument ordering follows your delivery. Venue dispatchers keyed by symbol satisfy this by construction.
- Ref-count your venue-side subscription: several charts may subscribe to the same instrument, and the last dispose is what releases the venue stream.
- The handler runs on your thread. It is the platform's job to be fast, but never hold a venue lock across the callback.
Quotes, summaries & open interest
Bid/ask quotes are ticks with the quote flags set — there is no second surface for them. Daily
statistics have one: IMarketSummaryFeed is THE surface for settlement, previous
close, official open and the daily OHLCV a venue publishes. Open interest and fundamentals have
their own optional feeds.
| Surface | Serves |
|---|---|
IMarketSummaryFeed MarketSummary |
Settlement / previous close / official daily statistics. Declared as
VenueCapabilities.MarketSummary. |
IOpenInterestFeed OpenInterest |
The venue's outstanding-contract count, a scalar per instrument. Stamped onto the forming bar's OI channel. |
IFundamentalsFeed Fundamentals |
Beta / EPS / dividends / market cap. Market Analyzer columns render blank without it. |
IMarketSummaryFeed; the consumer
precedence chain reads summary first and derives from bars only when the venue publishes
nothing.The hosted depth plane
Depth is the one place the seam inverts. A venue does not build an order book: it
injects raw book events into a feed the host creates, and exposes that same object as its
Depth / Mbo surface. Sequencing, the shared canonical book, shard pumps
and subscriber fan-out all happen behind Inject, identically for every venue.
Venues never see rings, sequencers or the book hub. That is deliberate: it is the same ingest path at every rate, from a slow REST-polled book to an exchange-rate MBO storm, so there is no "standard path" and "fast path" to choose between.
IMarketDepthHost.csnamespace SabrTrader.Pipeline.MarketDepth;
public interface IMarketDepthHost
{
IHostedDepthFeed CreateDepthFeed(IDepthUpstream upstream);
IHostedMboFeed CreateMboFeed(IMboUpstream upstream);
}
graph LR W["Venue wire
(socket / REST)"] -->|Inject(in evt)| F["IHostedDepthFeed
IHostedMboFeed"] F -->|sequence · apply · fan out| BK["Shared book + subscribers"] F -->|OnSubscribe / OnUnsubscribe| U["IDepthUpstream
IMboUpstream"] F -->|Reimage / FailClosed| U U --> W
The plane arrives as context.Depth. It is nullable — a headless host runs without a
depth plane — and a venue whose context.Depth is null simply exposes no
Depth/Mbo surfaces.
Aggregated L2 depth
Create one feed per venue book stream, implement the upstream, and publish the feed as
IDataProvider.Depth. The host calls OnSubscribe when the FIRST platform
consumer for an instrument arrives and OnUnsubscribe when the LAST one leaves — the
ref-counting is done for you.
Hosted L2public interface IHostedDepthFeed : IMarketDepthFeed
{
void Inject(in DepthUpdate update); // host stamps SequenceNumber; leave it 0
void Reset(); // scoped to THIS feed's instruments
}
public interface IDepthUpstream
{
void OnSubscribe(string instrumentId);
void OnUnsubscribe(string instrumentId);
}
MyVenueDepth.cs_depthFeed = context.Depth!.CreateDepthFeed(new DepthUpstream(this));
// …on the wire thread. SequenceNumber stays 0 — the host stamps it.
_depthFeed.Inject(new DepthUpdate(
SequenceNumber: 0,
ExchangeTimestampUtc: timestampUtc,
InstrumentId: instrumentId,
Side: Side.Bid,
Action: DepthAction.Update,
Price: price,
Size: size));
private sealed class DepthUpstream(MyVenueSession owner) : IDepthUpstream
{
// Host threads: queue venue I/O, never perform it inline.
public void OnSubscribe(string instrumentId) => owner.EnqueueSubscribeDepth(instrumentId);
public void OnUnsubscribe(string instrumentId) => owner.EnqueueUnsubscribeDepth(instrumentId);
}
Market-by-order
An L3 feed adds the image bracket and the directional health calls. MboEvent.OrderId
must be the venue's exchange order id, stable across Modify including a price move — the
book's identity depends on it.
Hosted MBOpublic interface IHostedMboFeed : IMarketByOrderFeed
{
void Inject(in MboEvent evt);
void BeginReimage(string instrumentId); // clear + HOLD presentation
void CompleteReimage(string instrumentId); // image complete, resume
void FailImage(string instrumentId, string reason); // image failed venue-side
void Reset(); // scoped to THIS feed
}
public interface IMboUpstream
{
void OnSubscribe(string instrumentId);
void OnUnsubscribe(string instrumentId);
void Reimage(string instrumentId); // host asks for a fresh image
void FailClosed(string instrumentId, string reason); // host gave up on this book
}
Reimage is precisely how heal requests went unanswered before this seam existed. A
venue that genuinely cannot re-image writes the empty method where a reviewer can see it — and
documents why.The re-image protocol
Any time the venue is about to replay a book image — the initial subscribe snapshot, a reconnect
rebuild, or an honoured Reimage request — bracket it. Between
BeginReimage and CompleteReimage the host clears the book and HOLDS
presentation, so consumers never render a half-built ladder. The bracket is re-entrant: calling
BeginReimage while a rebuild is already running keeps the partial image rather than
re-clearing it.
Image bracketpublic void OnSubscribe(string instrumentId)
{
// Bracket BEFORE the stream starts, so the first event is inside the image.
_mboFeed.BeginReimage(instrumentId);
_client.SubscribeMbo(instrumentId);
}
// …when the venue signals its snapshot finished:
_mboFeed.CompleteReimage(instrumentId);
// …when the venue's rebuild fails:
_mboFeed.FailImage(instrumentId, error.Code);
FailImage is the escape hatch, and it matters more than it looks: it releases the
rebuild hold AND stops the host treating MBO as authoritative for that instrument, so the venue's
aggregated depth (if it has any) can serve the book instead. Without it, a failed image leaves a
wedged DOM. A venue with no snapshot-complete signal at all should not open a bracket it
cannot close — document that instead, and let the book converge.
Reset & fail-closed
Reset() invalidates everything built from this feed: it clears the shared
books of this feed's instruments (empty IS the truth after a real disconnect) and raises
SubscriptionReset to this feed's consumers. It is scoped — it can never touch
another venue's books. Call it on connection loss, on a venue-issued book clear, or on any event
that voids resting levels.
FailClosed is the opposite direction: the host telling you it has given up on an
instrument's MBO book and already stopped presenting it. Stop the MBO stream if you can, and fall
back to aggregated depth for that instrument.
The perf contract
Inject is synchronous, returns void,
takes the event by in, allocates nothing, never blocks, and is safe from any
venue thread. No LINQ, no closures, no string formatting, no logging on the injection path.
- Per-instrument ordering follows the caller: deliver one instrument's events from one thread at a time.
- A stalled platform consumer can never backpressure your receive thread. Overload is absorbed
by the host's coalescing rings, and sustained loss surfaces as an
IMboUpstream.Reimagerequest — never as a blockedInject. - Upstream calls (
OnSubscribe,Reimage, …) arrive on host threads and must return quickly. Queue the venue I/O; never perform it inline. - Dispose your feeds on session teardown.
Flow diagnostics
Two diagnostic sinks stayed on the venue side of the seam on purpose, because a wedged book is almost always diagnosed from the venue outward. Both are optional, both are wired by the host into the activity log, and neither is on any per-event path.
| Type | Reports |
|---|---|
MboFlowMonitor |
Live MBO flow per layer (sequencer → hub → feed → view) and per UI consumer. Logs
consumer-stall when a consumer that was receiving data goes quiet — so a post-connect
freeze is visible without per-event noise. |
MboConnectionDiagnosticLog |
The end-to-end MBO connection lifecycle, venue subscribe through to the SuperDOM and chart consumers. |