Appearance
Volatility
What it computes
Bit-exact CBOE-methodology volatility indices over any single-symbol option chain. The analytic enumerates the two expirations the methodology brackets, computes the per-expiration variance σ², and linearly interpolates to a 30-day term — the public VolatilityRequest defaults pick the canonical CBOE VIX methodology automatically for SPX / SPXW filters and the same variance-strip integral on calendar-bracketed expirations for every other symbol.
Variance per expiration is the CBOE strip integral:
where
Methodology
CBOE Volatility Index Methodology White Paper (current revision); see CBOE Volatility methodology for the authoritative document.
Inputs
- Option NBBO quote stream per chain strike.
- The engine's risk-free-rate provider (provisioned at
Client::connect). - The engine's market-calendar provider (drives time-to-expiration in calendar time).
The caller does not wire these dependencies on the public surface — the engine sources both providers from the prebuilt artifact at connect time.
Output schema (VolatilityTick)
The tick schema below is regenerated from the engine source by docs-site/scripts/inject-doc-tables.ts on every npm run docs:build. Do not hand-edit between the sentinels.
| Field | Type | Description |
|---|---|---|
symbol | Arc<str> | Underlying symbol the index was computed on. |
window | VolWindow | Window the index resolves to. |
date | i32 | Trading-session date in YYYYMMDD form, sourced from the triggering option event. |
ms_of_day | i32 | Milliseconds since midnight at emit time. |
value | f64 | VIX-equivalent value (e.g. 17.23). f64::NAN when any flag in [VolatilityTickFlags] gates the emission. |
forward | f64 | Forward derived in-stream from put-call parity at K*. |
near | ExpirationLeg | Near (T1) leg snapshot. |
next | ExpirationLeg | Next (T2) leg snapshot. |
flags | VolatilityTickFlags | Per-tick annotations. |
Configuration (VolatilityRequest)
The public wire-params struct is kairos::params::VolatilityRequest. It encodes byte-identically to the engine's wire-params blob over the stable C ABI.
| Field | Type | Description |
|---|---|---|
contracts | SecurityFilter | Contract filter the subscription tracks. Volatility expects [SecurityFilter::Symbols] with exactly one entry; other shapes are rejected at validate time. |
window | VolWindow | Window the index resolves to. Default [VolWindow::CboeVix]. |
rate | Arc<dyn RateService> | Risk-free rate provider. The default constructed by DefaultSpec is a fixture FixedRateService at a hardcoded constant (currently 5.33%, an approximation of the SOFR overnight rate at the time the default was set). Production deployments MUST override this with a live curve provider — the fixture will silently drift from current market rates and bias forward = K* + (C-P)·e^(rT) on every emit. |
calendar | Arc<dyn MarketCalendar> | Market calendar provider. |
chain_source | Option<ChainSource> | Per-product chain source — narrows the listed expirations before bracket / nearest-term selection runs. When None, the engine resolves the chain from [VolWindow::methodology] at recompute time. Override only when a caller wants to deviate from the published CBOE methodology — production deployments should leave this None so the spec tracks the methodology table in lockstep. |
emit | EmitPolicy | Whether the analytic emits on every grid change ([EmitPolicy::OnEveryTick]) or at 1-second boundaries on the watermark hook ([EmitPolicy::OnClose]). |
min_emit_interval_ms | i32 | Minimum interval (ms) between consecutive emissions for the same (symbol, window). |
prewarm | PrewarmPolicy | Chain prewarm policy. Default [PrewarmPolicy::Auto] hydrates the strike grid from the reference-data snapshot service at subscribe time. See [PrewarmPolicy] for the variants. |
stale_quote_filter | Option<StalenessFilter> | Per-strike wall-clock staleness filter applied during the σ² walk. Default: None (DISABLED). The published CBOE Volatility Index Mathematics Methodology has no per-strike wall-clock staleness gate — it filters strikes via zero-bid admission and consecutive-zero-bid OTM-walk termination only (both always-on at the walker layer). Live OPRA SPX deep-OTM strikes update every 1–15 min on the published exchange cadence; enabling this filter at 30 s (the historical kairos default) systematically rejected those wings and collapsed live σ to ~3.5 % vs CBOE published VIX ~17 %. Set to Some(StalenessFilter::new(...)) only on chains where the wire-side quote cadence is genuinely faster than the chosen threshold and the consumer wants to enforce freshness — accepting that σ will diverge below CBOE published VIX whenever a strike's quote cadence is wider than the threshold. |
wide_spread_filter | Option<WideSpreadFilter> | Wide-spread filter applied during the σ² walk; None to disable. CBOE methodology populates this with a 50% relative / $0.05 absolute floor. |
value_band_filter | Option<ValueBandFilter> | Value-band hold filter applied at emit time; None to disable. Kept for opt-in compatibility — the band gate held its anchor for ≤ 2 minutes then released the divergent live value unflagged, producing single-tick discontinuities. The default publishing path is now the online-CUSUM filter ([Self::online_cusum_filter]); subscribers that want the legacy behaviour set this to Some(_) and online_cusum_filter to None. |
time_decay_us | Option<u64> | Time-decay constant τ in microseconds for an opt-in per-strike exponential recency kernel w(age) = exp(−age/τ) applied at σ² integration time. Default: None (DISABLED). The published CBOE Volatility Index Mathematics Methodology has NO continuous-decay term on per-strike contributions; the published recipe filters strikes via zero-bid + consecutive-zero-bid termination only. Enabling this kernel multiplies each strike's mid by exp(−(now − last_update)/τ) BEFORE the variance integral — institutionally a kairos-only mutation that biases σ² downward whenever a strike's wire-side update cadence is wider than τ. Empirically, with τ = 600 s on live RTH SPX, σ drifted 1.94 vol-pt over 287 s while CBOE published VIX moved 0.03 vol-pt (67× wider spread than the institutional bar). Set to Some(τ) only for research / non-CBOE chains where a smooth-fade fallback for sparse wings is acceptable in exchange for a known σ² downward bias. |
halt_handling | HaltHandlingMode | How the σ² walker treats halted strikes. [HaltHandlingMode::Strict] (default) skips halted strikes; [HaltHandlingMode::Permissive] uses the last-known NBBO. |
halt_resume_window_us | u64 | Post-halt resume corroboration window, in microseconds. After a Halted → !Halted quote-condition edge fires on a contract, the engine opens a pending corroboration window and waits for an opening / reopening trade to land. If no corroborating print arrives within halt_resume_window_us microseconds, the watermark sweep logs a one-shot tracing::warn! and closes the window. The analytic does NOT gate variance on corroboration — the field tunes only the observability signal. Default 30_000_000 (30 seconds) matches the CBOE methodology reference. Tighten for products with fast resumption procedures; loosen for venues with slower opening crosses. |
The default VolWindowWire resolution covers the canonical CBOE VIX methodology on SPX / SPXW. Every other single-symbol filter resolves to a calendar-bracketed 30-day variance strip. The override surface is described in Advanced below; the quickstart path never touches it.
Operational characteristics
- Per-tick latency. Incremental strip-integral update with Neumaier compensated summation across the running variance accumulator.
- Allocation discipline. Strike grid is prewarmed at subscribe time from the engine's chain snapshot; live ticks update the cached
(bid, ask)on the grid. No hot-path allocations. - Replay parity. Bit-exact under deterministic input ordering; Neumaier summation defends against the wide-grid precision pathology where deep-OTM contributions land below the running sum's ULP.
- Degraded-path continuity. When either leg's implied-vol solve fails (variance error) or its strike grid is missing (
INSUFFICIENT_STRIKES), the analytic republishes the prior tick's value if one exists, settingREPUBLISHED_PRIOR_VALUEalongside the cause-of-degradation flag. Both the near-leg and the next-leg paths share this contract — a downstream consumer reading the flag set can distinguish "real computation degraded gracefully off the prior value" from "fresh computation just failed with no continuity". If no prior publish exists yet on the stream, the analytic emits a fresh degraded tick instead.
Example
rust
// Cargo.toml:
// kairos = "0.1"
use kairos::{Client, VolatilityRow};
let client = Client::connect(("me@example.com", "secret"))?;
let sub = client
.live()
.volatility(["SPX"])
.on_event(|row: &VolatilityRow| println!("value={} forward={}", row.value, row.forward))?;
// ... later ...
sub.unsubscribe();The same shape works on any liquid underlying — the engine resolves the right window from the filter:
rust
let sub = client
.live()
.volatility(["NVDA"])
.on_event(|row: &VolatilityRow| println!("value={}", row.value))?;Historical and replay drives swap client.live() for the preview client.historical(start, end) (feature historical-mode) and client.replay() (feature replay-mode) namespaces; the per-analytic builder, the filter accessor, and the .on_event callback are identical.
Advanced
The public VolatilityRequest carries two enum knobs — window: VolWindowWire and emit: EmitPolicyWire — plus a flat min_emit_interval_ms: i32. The default seed pins VolWindowWire::CboeVix and EmitPolicyWire::OnExchangeInterval { interval_ms: 1000 }. Research workflows that need a non-default window override chain the .window(...) setter before .on_event:
rust
use kairos::{Client, VolatilityRow, params::{BracketSelectionWire, VolWindowWire, EmitPolicyWire}};
let client = Client::connect(("me@example.com", "secret"))?;
// Canonical CBOE VIX on SPX / SPXW — the default. Shown for
// completeness; omit the `.window(...)` setter for the default
// behaviour.
let _vix = client
.live()
.volatility(["SPX"])
.window(VolWindowWire::CboeVix)
.on_event(|row: &VolatilityRow| println!("value={}", row.value))?;
// Custom-tenor variance strip on QQQ (14-day calendar bracket).
let _qqq = client
.live()
.volatility(["QQQ"])
.window(VolWindowWire::Generalized {
target_days: 14,
bracket_selection: BracketSelectionWire::Calendar,
min_strikes: 4,
})
.emit(EmitPolicyWire::OnEveryTick)
.on_event(|row: &VolatilityRow| println!("value={}", row.value))?;VolWindowWire::Generalized carries three fields: target_days: u16, bracket_selection: BracketSelectionWire (Calendar for CBOE-style; Nearest for nearest-expiration), min_strikes: u8. Both enums implement the codec's WireScalar trait, so they encode byte-identically to the engine side over the stable C ABI.