NeoCharts
Trading

Option Chain & OI Analysis

Option contracts, ATM/top-options feeds, and OI profile/change/analysis, PCR, ATM straddle/IV.

fetchOptionDetails

Option contracts for the underlying symbol's chain — called when the user opens the option chain.

Future<String> fetchOptionDetails({required String underlyingSymbolId});

The first element of the returned array must be the underlying symbol info (no optType field) — the chart uses it to resolve the correct spot price. All subsequent elements are option contracts:

[
  { "id": "NIFTY", "name": "NIFTY 50", "lotSize": 50, "precision": 2, "tickSize": 0.05 },
  { "id": "NIFTY25MAR23000CE", "name": "NIFTY 23000 CE Mar 27", "strike": "23000", "expiry": "2025-03-27", "optType": "CE", "lotSize": 50, "precision": 2, "tickSize": 0.05, "exchange": "NSE" }
]

Return "[]" when no option chain is active.

Streaming & Greeks

fetchOptionDetails is a one-shot snapshot — there's no separate streaming method for the option chain. Live updates (LTP, change, OI, and Greeks) arrive through the same marketDataStreamer tick stream every other price on the chart uses:

  1. When the option chain opens (or the user picks a different expiry/CE-PE side), the SDK adds every visible contract's id — the ids from the array fetchOptionDetails returned, plus the underlying's own id — to its one shared symbol subscription. See When subscribeMarketData/unsubscribeMarketData actually fire for exactly when that subscription changes.
  2. Push a tick for each of those ids the same way you push any other tick — for option contracts specifically, also include the Greeks flat on that same tick object (not nested under a greeks key): itm, iv, delta, gamma, theta, rho, vega, highIV, lowIV. Full field reference: Live ticks (marketDataStreamer).
  3. There's no separate "Greeks changed" event — a tick with updated Greeks and unchanged LTP is a completely normal, valid update.

atmSymbols

[ceSymbolInfo, peSymbolInfo] for the ATM strike, nearest expiry — required for the right panel of the Index + Call/Put or Call + Put layouts. Read when the user switches to one of those layouts, so it's safe to resolve after background data finishes loading. Resolve to "[]" to leave the right panel blank (null also works, see Always return empty, never null).

Future<String?> get atmSymbols;

Same symbolInfo shape as above, array of exactly two: [ceSymbolInfo, peSymbolInfo].

atmSymbolsFor

The parameterized replacement for atmSymbols above — scoped to an explicit underlyingId instead of "whichever underlying is currently charted."

Future<String?> atmSymbolsFor(String underlyingId);

Same shape as atmSymbols: [ceSymbolInfo, peSymbolInfo] for underlyingId's ATM strike, nearest expiry. Resolve to null to leave the right panel blank for that underlying.

chartTopOptions

Top options ranked by active volume — powers the Index + Call/Put/Terminal "Top 5" panel. Called once when the panel opens, not re-fetched afterward.

Future<String> get chartTopOptions;
[
  { "symId": "NIFTY25MAR23000CE", "name": "NIFTY 23000 CE Mar 27", "optType": "CE", "exchange": "NSE" },
  { "symId": "NIFTY25MAR22000PE", "name": "NIFTY 22000 PE Mar 27", "optType": "PE", "exchange": "NSE" }
]
FieldTypeAccepted values
symIdStringrequired; note the key is symId, not id
nameStringrequired; display name
optTypeStringrequired; "CE" or "PE"
exchangeStringrequired; exchange identifier
undIDStringoptional; underlying symbol id. When non-empty, the SDK drops any candidate whose undID doesn't match the current underlying. Omit or leave empty to skip this filtering.

The SDK takes the first 5 candidates per option type, in the order you return them, and only re-sorts within that set of 5 as live volume ticks arrive — it never looks past the first 5 of each type. Return your candidates already ordered by your own best-known priority (e.g. volume, open interest); anything beyond the first 5 per type is discarded and never reconsidered, even if it later turns out to have higher volume.

chartTopOptionsFor

The parameterized replacement for chartTopOptions above — scoped to an explicit underlyingId. Same contract and shape, including the first-5-per-type rule.

Future<String> chartTopOptionsFor(String underlyingId);

Same shape as chartTopOptions. undID on each candidate is no longer a filtering signal here — you've already scoped the array to underlyingId yourself — but the field is still accepted if you include it.

OI Profile / OI Change / OI Analysis

Three related methods — implement whichever your app's features need; each resolves to "{}" (or is simply never called) when unused.

FeatureMethodScope
OI Profile (current OI)fetchOIpoint-in-time snapshot, summed across the expiries you pass
OI Profile (change) / OI Change AnalysisfetchOIChangeOI change over a time window, summed across expiries
OI Analysis screenfetchOIAnalysisOI, OI-change, and previous-OI per strike, for the strikes around ATM you return, for a single expiry over a time window
Future<String?> fetchOIAnalysis({
  required String underlyingSymbolId,
  required String expiry,
  required int timeFrom,
  required int timeTo,
});
Future<String?> fetchOIChange({
  required String underlyingSymbolId,
  required List<String> expiries,
  required int timeFrom,
  required int timeTo,
});
Future<String?> fetchOI({
  required String underlyingSymbolId,
  required List<String> expiries,
});

timeFrom/timeTo are unix seconds. fetchOIAnalysis does not receive a strike range — your implementation derives the current ATM strike itself (the same way atmSymbols does) and picks however many strikes around it to expose (e.g. ~43). The SDK does not trim this for you — every strike you return renders, in a scrollable, ATM-centered chart. Return only what you actually want shown.

fetchOIAnalysis resolves to:

{ "calls": { "23450": { "oi": 87425, "oiChg": 3770, "prevOi": 83655 } }, "puts": { "23450": { "oi": 0, "oiChg": 0, "prevOi": 0 } } }

fetchOIChange resolves to:

{ "calls": { "23450": 3770 }, "puts": { "23450": 802750 } }

fetchOI resolves to:

{ "calls": { "23450": 87425 }, "puts": { "23450": 3429205 } }

All three resolve to "{}" when no data is available for the window/point (null also accepted — see Always return empty, never null). Strike keys are strings (parsed as double), values are double/int per the field.

What the fields mean

  • oi — open interest: the number of currently outstanding contracts at that strike (not volume traded — a contract stays counted until closed, not just traded once). Rising OI with a rising price is read as bullish conviction (new money entering long); rising OI with a falling price is read as bearish (new shorts). Falling OI generally means positions are being closed/unwound, regardless of price direction.
  • oiChg — the change in oi over the method's window: timeFrom..timeTo for fetchOIAnalysis/fetchOIChange, "since a reference point you define" for fetchOI (it's a point-in-time snapshot, not itself a window). Positive = OI increased (contracts added over the window); negative = OI decreased (contracts closed).
  • prevOi (fetchOIAnalysis only) — the OI value immediately before this window started, i.e. oi - oiChg at the window's start. Lets the UI show a from/to comparison without a second request.

Which screen reads which method

ScreenMethodWhy that one
OI Profile bars (current OI)fetchOINeeds only "OI right now," no history — a single point-in-time snapshot, cheapest to serve
OI Profile bars (change) / OI Change AnalysisfetchOIChangeNeeds "how much did OI move," not the absolute levels
OI Analysis screen (full detail)fetchOIAnalysisNeeds all three together — absolute OI, its change, and the pre-change baseline — for a side-by-side reading

Implement only the method(s) backing the screens you actually enable — the other(s) can resolve to "{}"/never be called, per the table at the top of this section.

PCR / ATM straddle / ATM IV (Analysis screen)

Future<String> fetchPcrIntraday();
Future<String> fetchAtmStraddleIntraday();
Future<String> fetchAtmIvIntraday();

No arguments.

fetchPcrIntraday — historical Put/Call OI ratio, pre-computed per strike range, oldest first:

[{ "time": 1721460600000, "price": 24230.5, "pcr": { "3": 0.42, "5": 0.51, "10": 0.63, "20": 0.7, "all": 0.75 } }]

fetchAtmStraddleIntraday — keyed by expiry date, one minute sampling:

{ "2025-03-27": [{ "time": 1721460600000, "atmStraddlePrice": 435.2 }] }

fetchAtmIvIntraday — one minute sampling:

[{ "time": 1721460600000, "atmIv": 21.8 }]

time is epoch milliseconds in all three. Return "[]" (or "{}" for the straddle map) when no historical data is available.

fetchPcrIntradayFor / fetchAtmStraddleIntradayFor / fetchAtmIvIntradayFor

The parameterized replacements for the three methods above — each takes an explicit underlyingId instead of implicitly answering for "whatever's currently charted." Same contract, same shapes, same empty-value conventions as their ambient counterparts.

Future<String> fetchPcrIntradayFor(String underlyingId);
Future<String> fetchAtmStraddleIntradayFor(String underlyingId);
Future<String> fetchAtmIvIntradayFor(String underlyingId);

If your data source only ever serves a single underlying's worth of this history, it's fine to ignore underlyingId and return the same series regardless of its value.

On this page