Options data & trading

Options data & trading

Overview

One venue-agnostic surface for everything options: search underlyings, open chains, stream per-contract quotes with IV, open interest and greeks, capture bounded snapshots for exposure analytics, and place multi-leg orders from a strategy — without your plugin ever naming a venue.

What you can build

The options surface has two halves. The data half is IOptionChainReader — a read-only seam your indicator or strategy obtains from its context. The trading half is a set of multi-leg order verbs on the strategy context. Together they cover the full loop: discover contracts, read their market data, and trade them atomically.

🔗 Underlyings & chains

Search option-capable underlyings, open a chain, walk its expiries and fetch the call/put contracts per expiry — strike, right, style, multiplier and the ids you trade with.

Open a chain →

📡 Live quotes & greeks

Stream per-contract quote deltas: bid/ask/last, implied volatility, open interest, volume and the greeks — plus the underlying itself through the same subscription.

Subscribe to quotes →

📸 Chain snapshots

One call captures a bounded, point-in-time slice of the chain with market data — the bulk input for GEX/DEX/VEX exposure profiles, IV-rank and screeners.

Capture a snapshot →

🧾 Multi-leg trading

Place verticals, straddles and iron condors as one atomic net-limit order from a strategy, pre-check margin, and exercise held positions.

Trade a spread →

One reader, no venues

Your plugin asks for "the option chain of SPY" — never "the Schwab chain of SPY". The host routes every call to whichever connected, options-capable venue serves that underlying, and nothing in the results tells you which one answered. This is deliberate: the same plugin runs unchanged for a user connected to any options-capable brokerage or data feed, today's and tomorrow's.

The key mechanism is the chain view. OpenChainAsync("SPY") resolves the symbol and returns an IOptionChainView bound to the venue that resolved it. Every provider instrument id inside that view — contract ids, the underlying id — is only meaningful within that same view, so ids from different venues can never be mixed by construction. Quotes, extra expiries and snapshots are all requested through the view, and disposing the view releases the venue-side resources it holds.

graph LR;
      A[Your plugin]-->B[IOptionChainReader];
      B-->C[IOptionChainView per underlying];
      C-->D[Chain listing];
      C-->E[Quote stream];
      C-->F[Bounded snapshot];
Entitlements are invisible too. If the user's account isn't entitled to options data, the reader simply reports IsAvailable == false and calls return empty results or null views — your plugin sees "no options data", never why. Individual contracts a venue refuses to quote carry OptionQuote.AccessDenied instead of throwing.

Getting the reader

You never construct the reader — the host installs one process-wide and both plugin contexts expose it. It is null when the host serves no options data at all (for example a bare backtest harness), so the access pattern is a null-check, then use:

From an indicatorusing SabrTrader.Pipeline.Options;

// IIndicatorContext.OptionChains — available anywhere you hold the context.
IOptionChainReader? options = Context.OptionChains;
if (options is null || !options.IsAvailable) return;   // no options-capable venue right now

var view = await options.OpenChainAsync("SPY");
From a strategyusing SabrTrader.Pipeline.Options;

// Strategy.OptionChains — a protected shortcut for Context.OptionChains.
IOptionChainReader? options = OptionChains;
if (options is null || !options.IsAvailable) return;

var view = await options.OpenChainAsync(underlyingSymbol);
Backtests keep compiling. The options members are default-implemented on the contexts: a host without options support answers null / "unsupported" results instead of throwing. Your strategy runs identically in live, backtest and test harnesses — it just finds no options data where none is served.

Availability is dynamic

Venues connect after charts load, disconnect mid-session, and reconnect. Don't sample availability once in OnInit and give up — hold the reader and watch AvailabilityChanged:

Availability patternprivate IOptionChainReader? _options;
private IOptionChainView?   _view;

public override void OnInit(IIndicatorContext ctx)
{
    _options = ctx.OptionChains;
    if (_options is null) return;              // this host never serves options data
    _options.AvailabilityChanged += OnOptionsAvailability;
    if (_options.IsAvailable) _ = OpenAsync();
}

private void OnOptionsAvailability()
{
    // May fire on ANY thread. A venue (re)connected or dropped:
    // reopen the view — an old view does not outlive its venue.
    if (_options!.IsAvailable) _ = OpenAsync();
}

public override void OnDispose()
{
    if (_options is not null) _options.AvailabilityChanged -= OnOptionsAvailability;
    _view?.Dispose();
}
Always unsubscribe on dispose. The reader is process-lived. An AvailabilityChanged subscription that outlives your plugin keeps its assembly load context alive across hot reloads — the classic plugin memory leak. Pair every += in OnInit with a -= in Dispose.

Where the types live

The options data model ships inside SabrTrader.Pipeline.Contracts, namespace SabrTrader.Pipeline.Options, bundled by the SabrTrader.Sdk meta-package — if you installed the SDK, you already have it. The venue-side chain SPI a venue plugin implements lives in SabrTrader.Pipeline.Venues.Options (package SabrTrader.Pipeline.Venues.Contracts). Like every contract assembly they hold only interfaces, records and enums; the engine that answers the calls lives in the host.

Concern Namespace Key types
Chain data (read) SabrTrader.Pipeline.Options IOptionChainReader, IOptionChainView, OptionChain, OptionContract, OptionQuote, OptionChainSnapshot, OptionChainSnapshotRequest, OccOptionSymbol
Multi-leg trading SabrTrader.Pipeline.Venues.Trading MultiLegOrderRequest, OptionLeg, PositionEffect, NetPriceDirection, MultiLegPlacement, MultiLegPrecheckResult
Context access SabrTrader.Pipeline.Indicators / SabrTrader.Pipeline.Strategies IIndicatorContext.OptionChains, IStrategyContext.OptionChains + the multi-leg verbs on Strategy
Options levels are a different chapter. Vendor-published gamma/exposure levels (walls, HVL, expected-move bands) come from SabrTrader.Pipeline.Levels and are covered in Options levels. This chapter is the raw chain data those vendors compute from — with it you can build your own exposure analytics.

The chapter, page by page