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.

Feed methods
| Method | The ladder calls it | Returns |
|---|---|---|
getOptionContracts({ symbol }) | Once per underlying, for the expiration and strike axes | OptionSeriesSnapshot |
getOptionQuotes({ symbol, expiration }) | Per selected expiration, for quotes and greeks | OptionSeriesSnapshot |
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
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" }
}
codeuniquely identifies the contract at your provider; it round-trips into orders and chart selections unchanged.typeisCALLorPUT;expirationis the provider's numeric id andexpiration_datethe 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. metricsholds any scalar analytics under your own keys. The SDK transports them untouched; nothing is derived or renamed.quote_freshnessreports how current the numbers are;field_provenanceandfield_availabilityare 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
- Mixed-Asset Catalogs — the feed these methods extend.
- Option order ticket — preview and place the selected contract.