Options data & trading

Options data & trading

Overview

One venue-agnostic surface for 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. Your plugin never names 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 methods on the strategy. Together they cover the full loop: discover contracts, read their market data, and trade them as one order.

🔗 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 updates: 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 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. The same plugin runs unchanged for a user connected to any options-capable broker or data feed.

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 and the underlying id) is only meaningful within that same view, so ids from different venues cannot be mixed. 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. When the user's account is not entitled to options data, the reader reports IsAvailable == false and calls return empty results or null views. Your plugin sees "no options data", not the reason. Individual contracts a venue refuses to quote carry OptionQuote.AccessDenied.

Getting the reader

The host installs one reader process-wide and both plugin contexts expose it; you never construct 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 or an "unsupported" result. Your strategy runs identically in live, backtest and test harnesses and finds no options data where none is served.

Availability is dynamic

Venues connect after charts load, disconnect mid-session, and reconnect. Hold the reader and watch AvailabilityChanged instead of sampling availability once in OnInit:

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, because a 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. Pair every += in OnInit with a -= in OnDispose.

Where the types live

The options data model ships in SabrTrader.Pipeline.Contracts, namespace SabrTrader.Pipeline.Options, and comes with the SabrTrader.Sdk meta-package. The chain SPI a venue plugin implements lives in SabrTrader.Pipeline.Venues.Options (package SabrTrader.Pipeline.Venues.Contracts) and returns the same records, so a consumer and a venue speak one data model. 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, ImpliedMoveMath
Multi-leg trading SabrTrader.Pipeline.Venues.Trading MultiLegOrderRequest, OptionLeg, PositionEffect, NetPriceDirection, MultiLegPlacement, MultiLegPrecheckResult
Chain SPI (venue plugins) SabrTrader.Pipeline.Venues.Options IOptionChainSource, IOptionChainSnapshotSource, OptionQuoteCapacity
Context access SabrTrader.Pipeline.Indicators / SabrTrader.Pipeline.Strategies IIndicatorContext.OptionChains, IStrategyContext.OptionChains and the multi-leg methods on Strategy
Options levels are a separate chapter. Vendor-published gamma and 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