Skip to main content

Option Chains

Two feed methods power the option chain ladder: one loads the contract catalog for an underlying, the other refreshes quotes and greeks per expiration. Provider analytics — vanna, charm, DEX, GEX, IV rank, proprietary scores — travel in a per-contract metrics bag and render as ladder columns without the SDK interpreting them.

Option chain ladder for AAPL with calls and puts mirrored around the strike column, showing last, volume, bid, ask, IV, delta, and formatted vanna, DEX, and GEX metric columns, with the at-the-money strike highlighted

Feed methods

MethodThe ladder calls itReturns
getOptionContracts({ symbol })Once per underlying, for the expiration and strike axesOptionSeriesSnapshot
getOptionQuotes({ symbol, expiration })Per selected expiration, for quotes and greeksOptionSeriesSnapshot

Both take the underlying as a ticker string or resolved symbol and return the same snapshot shape; the quotes call narrows to one expiry and carries fresher numbers.

Reference implementation

optionsFeed.ts
import type { MarketDataFeed, OptionSeriesSnapshot, SdkSymbolInfo } from '@tradescript/pro/sdk';

const API = 'https://api.yourco.example';

function underlyingOf(symbol: string | SdkSymbolInfo): string {
return typeof symbol === 'string' ? symbol : symbol.canonicalSymbol ?? symbol.ticker;
}

export const optionsFeed: Pick<MarketDataFeed, 'getOptionContracts' | 'getOptionQuotes'> = {
async getOptionContracts(request) {
const underlying = underlyingOf(request.symbol);
const response = await fetch(`${API}/options/${encodeURIComponent(underlying)}/contracts`);
if (!response.ok) throw new Error(`Option contracts request failed: ${response.status}`);
const snapshot: OptionSeriesSnapshot = await response.json();
return snapshot;
},

async getOptionQuotes(request) {
const underlying = underlyingOf(request.symbol);
const query = request.expiration ? `?expiration=${encodeURIComponent(request.expiration)}` : '';
const response = await fetch(`${API}/options/${encodeURIComponent(underlying)}/quotes${query}`);
if (!response.ok) throw new Error(`Option quotes request failed: ${response.status}`);
const snapshot: OptionSeriesSnapshot = await response.json();
return snapshot;
},
};

Spread these into the same feed that serves your bars (Mixed-Asset Catalogs), or supply them on a dedicated options provider.

Backend contract

Contracts and quotes

GET /options/nasdaq:aapl/contracts
GET /options/nasdaq:aapl/quotes?expiration=2026-08-21

Both return one OptionSeriesSnapshot. Two contracts shown; a real response carries every strike and right:

{
"symbol": "AAPL",
"provider": "provider-options",
"quote_timestamp": 1752000000000,
"expirations": ["2026-08-21", "2026-09-18"],
"strikes": [200, 205, 210],
"contracts": [
{
"code": "OPRA:AAPL260821C00205000",
"underlying_symbol": "AAPL",
"type": "CALL",
"expiration": 20260821,
"expiration_date": "2026-08-21",
"strike_price": 205,
"multiplier": 100,
"last_price": 7.4,
"bid_price": 7.31,
"ask_price": 7.52,
"implied_volatility": 0.412,
"delta": 0.61,
"gamma": 0.031,
"theta": -0.18,
"vega": 0.24,
"open_interest": 4321,
"volume": 876,
"metrics": { "vanna": 0.0142, "dex": 263581, "gex": 1825000 },
"last_update": 1752000000000
},
{
"code": "OPRA:AAPL260821P00205000",
"underlying_symbol": "AAPL",
"type": "PUT",
"expiration": 20260821,
"expiration_date": "2026-08-21",
"strike_price": 205,
"multiplier": 100,
"last_price": 6.9,
"bid_price": 6.81,
"ask_price": 7.02,
"implied_volatility": 0.398,
"delta": -0.39,
"gamma": 0.031,
"theta": -0.17,
"vega": 0.24,
"open_interest": 3890,
"volume": 512,
"metrics": { "vanna": 0.0142, "dex": -151710, "gex": 1642000 },
"last_update": 1752000000000
}
],
"quote_freshness": { "last_update": 1752000000000, "age_ms": 20, "status": "fresh" }
}
  • code uniquely identifies the contract at your provider; it round-trips into orders and chart selections unchanged.
  • type is CALL or PUT; expiration is the provider's numeric id and expiration_date the readable date the ladder groups by.
  • Greeks (delta, gamma, theta, vega, rho) and quote fields are all optional — the ladder renders what a contract carries.
  • metrics holds any scalar analytics under your own keys. The SDK transports them untouched; nothing is derived or renamed.
  • quote_freshness reports how current the numbers are; field_provenance and field_availability are optional per-field metadata maps.

Metric columns

Name the metric keys as ladder columns. Built-in columns (last, mark, volume, openInterest, bid, ask, impliedVolatility, delta, gamma, theta, vega) are strings; a metric column names the exact key and formats its raw scalar:

<OptionChainLadder
sdk={sdk}
trading={trading}
marketData={marketData}
underlying={underlying}
currentPrice={205}
columns={[
'last', 'volume', 'bid', 'ask', 'impliedVolatility', 'delta',
{ kind: 'metric', metric: 'vanna', label: 'Vanna' },
{ kind: 'metric', metric: 'dex', label: 'DEX', renderCell: ({ value }) => compact(value) },
{ kind: 'metric', metric: 'gex', label: 'GEX', renderCell: ({ value }) => compact(value) },
]}
/>

The call side mirrors the column order around the strike; bid and ask always read bid-then-ask on both sides. Unknown metric keys render empty cells, so a provider can roll out a new metric before every contract carries it.

OptionChainLadder can browse and select contracts with market data alone. Pass trading only when account-specific state should supply display context; placing or previewing an order belongs to OptionOrderTicket.

Trading and charting from the chain

Selecting a contract updates the ladder's selection and calls onContractSelect with the full contract and quote — feed it to your order ticket. Charting the contract is opt-in and needs a mapping only your catalog can supply:

<OptionChainLadder
sdk={sdk}
trading={trading}
marketData={marketData}
underlying={underlying}
onContractSelect={handleContractSelection}
chartBridge={{ toChartSelection: (contract) => catalog.optionSelectionFor(contract) }}
onChartSelection={(selection) => void chart.setSymbol(selection)}
/>

The bridge receives the complete BrokerOptionContract and returns a normal chart selection; the chart resolves it like any other symbol, and the resolved symbol exposes its facts through the discriminated instrument: { kind: 'option', ... } block. Passing onChartSelection without a chartBridge fails with datafeed.unsupported — the SDK never reverse-engineers a symbol from OCC contract strings.

Verify before continuing:

  • The ladder loads expirations and strikes for an underlying, calls mirrored left, puts right.
  • Switching expiration refetches quotes for that expiry only.
  • Your metric columns render with your labels and formatting; a missing metric key leaves the cell empty.
  • Selecting a contract fires onContractSelect; with a bridge configured, the chart switches to the exact contract.

Next steps