Skip to content

IvTermStructure

Preview on the public Rust client. The analytic ships in the engine and is reachable today through the Python wheel and the TypeScript / Node addon; a per-analytic builder on the public Rust thin client (kairos::Client) is tracked on the roadmap. The output tick schema below is stable.

What it computes

For each subscribed underlying, the analytic builds the ATM implied-volatility ladder across every forward expiration on the chain. One tick per (symbol, watermark_ms, date) triple; the legs payload carries one IvTenorLeg per registered expiration, sorted ascending by tenor_days.

The ladder is the same shape Bloomberg's OVDV / OVME term structure pages render, sourced from per-strike NBBO snapshots rather than periodic dealer marks.

Methodology

For each (symbol, expiration) pair the analytic:

  1. Resolves the ATM strike — the call strike nearest to spot from the per-symbol chain.
  2. Reads the ATM call's most-recent NBBO mid as the option market value, then solves implied volatility against the cached (spot, rate, t) via the same closed-form Black–Scholes bisection used by Greeks.
  3. Reports the tenor in calendar days against the per-symbol engine market calendar's close-time convention (regular session close, plus 15 minutes for early-close days).

References:

  • Black & Scholes (1973). The Pricing of Options and Corporate Liabilities. Journal of Political Economy 81(3): 637–654.
  • Derman & Kani (1994). Riding on a Smile. RISK 7(2): 32–39 — surface-shape conventions.
  • Hull, Options, Futures, and Other Derivatives (10th ed.) §20.4 — volatility term structure.

See /methodology/black-scholes for the formula derivation.

Inputs

  • Option QuoteTick per forward expiration's ATM call.
  • Stock TradeTick (populates the shared engine spot-price cache).
  • A engine rate provider for (event_date, expiry_date) lookups.
  • A engine market calendar for the option-trading cutoff per session.

Per-expiration leg (IvTenorLeg)

Each legs[i] row carries: expiration: i32 (YYYYMMDD), tenor_days: f64 (calendar days to close), atm_strike: f64, atm_iv: f64, plus the resolved bid / ask / mid for the ATM call quote. f64::NAN propagates on legs whose ATM quote has not yet landed.

Output schema (IvTermStructureTick)

The field / type / description table below is regenerated from the IvTermStructureTick Rust source by docs-site/scripts/inject-doc-tables.ts on every npm run docs:build. Do not hand-edit between the sentinels.

FieldTypeDescription
symbolArc<str>Underlying symbol.
datei32Trading session date (YYYYMMDD).
ms_of_dayi32Milliseconds since midnight at emission time.
spotf64Spot price used for ATM-strike resolution. f64::NAN when no underlying trade has landed yet on the session.
ivol30f64ATM IV for the 30-day tenor slot — picks the nearest-to-30-day expiration on the chain (subject to min_dte_buffer), the closest-to-spot listed strike on that expiration, and reports the parity-anchored call/put IV midpoint. f64::NAN when no expiration meets the slot.
ivol60f64ATM IV for the 60-day tenor slot. See [Self::ivol30].
ivol90f64ATM IV for the 90-day tenor slot. See [Self::ivol30].
ivol120f64ATM IV for the 120-day tenor slot. See [Self::ivol30].
ivol180f64ATM IV for the 180-day tenor slot. See [Self::ivol30].
ivol360f64ATM IV for the 360-day tenor slot. See [Self::ivol30].
ivol720f64ATM IV for the 720-day tenor slot. See [Self::ivol30].
chg_ivol30f64Change in ivol30 vs the rolled-up prior-session reference. f64::NAN until a prior-session reference lands (either via the seeded [IvHistoryCache] or after the first watermark-driven session rollover).
chg_ivol60f64Change in ivol60 vs the prior-session reference. See [Self::chg_ivol30].
chg_ivol90f64Change in ivol90 vs the prior-session reference. See [Self::chg_ivol30].
chg_ivol120f64Change in ivol120 vs the prior-session reference. See [Self::chg_ivol30].
chg_ivol180f64Change in ivol180 vs the prior-session reference. See [Self::chg_ivol30].
chg_ivol360f64Change in ivol360 vs the prior-session reference. See [Self::chg_ivol30].
chg_ivol720f64Change in ivol720 vs the prior-session reference. See [Self::chg_ivol30].
legsVec<IvTenorLeg>Per-expiration ATM IV ladder — the full walk-every-expiration roll-up. Ordered ascending by tenor_days. Useful for downstream consumers that want the entire chain shape (e.g. calendar-spread mispricing scans) rather than just the seven ladder slots above.

Configuration (IvTermStructureRequest)

Regenerated from the IvTermStructureRequest Rust source — see the note above.

FieldTypeDescription
contractsSecurityFilterContracts the subscription tracks. The institutional idiom is to subscribe by underlying symbol ([SecurityFilter::Symbols]) — the engine fans out across every option on the chain plus the underlying stock contract (the latter feeds the shared [SpotPriceCache]).
rateArc<dyn RateService>Risk-free rate provider. Looked up per emission with (event_date, expiry_date); production deployments inject a curvekit-backed provider that auto-refreshes on date rollover.
annual_dividendOption<f64>Optional override of the implied dividend yield (annual decimal). None falls back to zero dividend yield (CBOE convention).
dividend_cache`std::sync::Arc<crate::reference_data::dividend_yield::
DividendYieldCache>`Engine-wired per-symbol dividend-yield cache. Seated at subscribe time by DefaultSpec via wire_dividend_yield. Consulted on the dispatch path only when annual_dividend is None — the per-tick lookup resolves the continuous-dividend carry yield q = D / S from the live underlying spot the analytic already holds (O(1), lock-light, non-blocking). An explicit annual_dividend override always wins.
venuesExchangeFilterExchange / venue admission policy.
emitEmitPolicyEmission policy. - [EmitPolicy::OnEveryTick] (default) — fire one tick per admitted Quote that advances the per-symbol dedup stamp. - [EmitPolicy::OnClose] — accumulate; fire on watermark advance.
tenor_ladder[u16;TENOR_LADDER_LEN]Fixed tenor ladder in calendar days. Per-slot the analytic picks the nearest-to-tenor expiration on the chain (subject to [Self::min_dte_buffer]) and reports its ATM IV plus the change vs the rolled-up prior-session reference. Default is [CANONICAL_TENOR_LADDER] = {30, 60, 90, 120, 180, 360, 720}, the trade-alert.com ivol30..720 glossary surface.
min_dte_bufferu16Minimum days-to-expiration buffer applied to every ladder slot. Expirations within this many days of the session date are excluded from every slot's nearest-tenor picker. Defaults to [DEFAULT_MIN_DTE_BUFFER] = 2 days, filtering 0-DTE / 1-DTE noise out of the front tenor.
iv_historyOption<Arc<IvHistoryCache>>Optional boot-time IV history. When supplied, the per-symbol chg_ivol_t columns seed from the cache's most-recent sample at construction time instead of starting NaN; downstream dashboards that load on top of a populated cache see live delta columns from emission #1 rather than emission #2.
spot_cacheSpotPriceCacheShared spot price cache, populated by the analytic from underlying stock trades and read by every option contract on the same subscription. Build via new_spot_price_cache and clone the returned Arc into the spec — sharing it with a paired GreeksParams gives both analytics the same per-symbol spot view.
calendarArc<dyn MarketCalendar>Market calendar provider. Looked up per emission with the triggering event's date to derive the option-trading cutoff.

Operational characteristics

  • Per-tick latency. One Black–Scholes bisection per emission leg (≈ 30 iterations max) and one shared chain-registry lookup.
  • Allocation discipline. Per-symbol leg vector is reused across emissions; the only hot-path allocation is the result vector handed to the consumer.
  • Replay parity. Deterministic given identical input tick order.

Example

Preview. This analytic ships in the engine and is reachable today through the Python wheel and the TypeScript / Node addon. A per-analytic builder on the public Rust thin client (kairos::Client) is tracked on the roadmap — bare-string symbol filters passed to the analytic accessor will match the other analytics already exposed there. The output tick rows are stable and documented above.

Cross-language surface

python
import kairos_thetadata as kt

client = kt.Client.connect(kt.Credentials.from_env())

def on_event(tick):
    for leg in tick.legs:
        print(
            f"{tick.symbol} exp={leg.expiration} "
            f"t={leg.tenor_days:.1f}d iv={leg.atm_iv:.3f}"
        )

sub = client.live().iv_term_structure(["SPX"]).on_event(on_event)
sub.wait(timeout_seconds=60.0)
typescript
import { Client, Credentials } from "kairos-thetadata";

const client = await Client.connect(Credentials.fromEnv());

const sub = await client.live().ivTermStructure(["SPX"]).onEvent((tick) => {
  for (const leg of tick.legs) {
    console.log(
      `${tick.symbol} exp=${leg.expiration} t=${leg.tenorDays.toFixed(1)}d iv=${leg.atmIv.toFixed(3)}`,
    );
  }
});

Proprietary. All rights reserved.