Options: live quotes & greeks

Options data & trading

Live quotes & greeks

Stream per-contract quote deltas through the chain view — prices, implied volatility, open interest, volume and greeks — and merge them correctly on your side.

Subscribing

SubscribeQuotes takes the provider instrument ids of the contracts you want — ids from this view's chain — and a handler. It returns an IDisposable; disposing it tears down every venue-side subscription that call created.

Subscribeusing SabrTrader.Pipeline.Options;

// The front-expiry contracts within ~10 strikes of the money, say:
List<string> ids = pickedContracts.Select(c => c.ProviderInstrumentId).ToList();

IDisposable subscription = view.SubscribeQuotes(ids, OnQuote);

void OnQuote(OptionQuote q)
{
    // q.ProviderInstrumentId tells you which contract this delta belongs to.
}
Subscribe to what you display or compute, not the whole board. Every id is a live venue-side subscription. A few hundred near-the-money contracts stream fine; a whole chain of thousands is venue load for data you'll never read. For one-shot bulk reads (exposure analytics), use a snapshot instead — that's what it's for.

Updates are partial

Streaming venues send deltas: a quote update carries only the fields that changed, and a null field means "unchanged" — never "gone". Keep one merged state per contract and overwrite only the fields that are present:

Merging deltasprivate readonly Dictionary<string, OptionQuote> _latest = new();
private readonly object _gate = new();

void OnQuote(OptionQuote q)
{
    lock (_gate)
    {
        _latest[q.ProviderInstrumentId] = _latest.TryGetValue(q.ProviderInstrumentId, out var prev)
            ? prev with
              {
                  Bid          = q.Bid          ?? prev.Bid,
                  Ask          = q.Ask          ?? prev.Ask,
                  Mid          = q.Mid          ?? prev.Mid,
                  Last         = q.Last         ?? prev.Last,
                  Delta        = q.Delta        ?? prev.Delta,
                  Gamma        = q.Gamma        ?? prev.Gamma,
                  Theta        = q.Theta        ?? prev.Theta,
                  Vega         = q.Vega         ?? prev.Vega,
                  ImpliedVol   = q.ImpliedVol   ?? prev.ImpliedVol,
                  Volume       = q.Volume       ?? prev.Volume,
                  OpenInterest = q.OpenInterest ?? prev.OpenInterest,
                  NetChange    = q.NetChange    ?? prev.NetChange,
                  PercentChange= q.PercentChange?? prev.PercentChange,
                  AccessDenied = q.AccessDenied ?? prev.AccessDenied,
              }
            : q;
    }
}

Greeks, activity and day-change fields are populated only when the venue serves them — that is entitlement- and field-group-dependent. Render blanks for fields that never arrive; don't invent zeros.

The quote fields

Field Meaning
ProviderInstrumentId Which contract (or underlying) this delta belongs to — always present.
Bid, Ask, Mid, Last Prices, in the contract's quote currency.
Delta, Gamma, Theta, Vega Greeks, when the venue serves them.
ImpliedVol Implied volatility as a fraction0.25m means 25%. Uniform across venues; multiply by 100 only for display.
Volume, OpenInterest Day volume and open interest, in contracts.
NetChange, PercentChange Day change (absolute / percent) — mainly useful for the underlying header.
AccessDenied true when the venue explicitly refused market data for this instrument (entitlement) — show "not entitled" instead of silent blanks. Null means normal/unknown; it is never inferred from absent fields.

Quoting the underlying

The chain's UnderlyingProviderInstrumentId goes through the same subscription surface — pass it alongside the contract ids and you get the spot price for ATM centering, moneyness and exposure math from one stream:

Underlying + contractsvar ids = new List<string>(contractIds);
if (view.Chain.UnderlyingProviderInstrumentId.Length > 0)
    ids.Add(view.Chain.UnderlyingProviderInstrumentId);

var sub = view.SubscribeQuotes(ids, OnQuote);
// In OnQuote, an update whose id equals UnderlyingProviderInstrumentId is the spot.

When the venue doesn't report an underlying id the property is empty — fall back to centering on the middle strike of the front expiry.

Threading

The handler fires on arbitrary threads. Exactly like trading events: the reader does not marshal for you. Touch your own state under a lock (as above) or post to your dispatcher before touching UI. Keep the handler cheap — it sits on the venue's data path; aggregate and let your normal render/compute cadence pick the merged state up.

Teardown

Dispose the subscription handle when the composition changes (new expiry picked, indicator removed); dispose the view and every subscription it created goes down with it. The safe order in a plugin's dispose path: subscription handles first, then the view, then unhook AvailabilityChanged.

OnDisposepublic override void OnDispose()
{
    _subscription?.Dispose();
    _view?.Dispose();
    if (_options is not null) _options.AvailabilityChanged -= OnOptionsAvailability;
}