Appearance
IvSkew
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, per-expiration 25-delta risk-reversal and 25-delta butterfly skew metrics — the same quote-quartet Bloomberg's OVDV and SKEW pages render against the chain.
text
RR_25 = IV(call, Δ = +0.25) − IV(put, Δ = −0.25)
BF_25 = (IV(call, Δ = +0.25) + IV(put, Δ = −0.25)) / 2 − IV(ATM)One tick per (symbol, watermark) with one IvSkewLeg per forward expiration on the chain, sorted ascending by tenor.
Methodology
For each (symbol, expiration) pair:
- Resolves three legs from the chain registry: the +25Δ call, the −25Δ put, the ATM call.
- Solves implied volatility on each leg via the same closed-form Black–Scholes bisection used by
Greeks. - Computes the risk-reversal and butterfly per the formulas above.
References:
- Black & Scholes (1973). The Pricing of Options and Corporate Liabilities. Journal of Political Economy 81(3): 637–654.
- Hull, Options, Futures, and Other Derivatives (10th ed.) §20.4 — volatility smile / skew.
- Derman & Kani (1994). Riding on a Smile. RISK 7(2): 32–39 — local volatility surfaces.
See /methodology/black-scholes for the formula derivation.
Inputs
- Option
QuoteTickon the three legs per expiration. - Stock
TradeTick(populates the shared engine spot-price cache). - A engine rate provider and optional
annual_dividend. - A engine market calendar for the option-trading cutoff per session.
Per-expiration leg (IvSkewLeg)
Each legs[i] row carries the expiration, tenor_days, atm_iv, iv_25d_call, iv_25d_put, rr_25d (risk-reversal), and bf_25d (butterfly). f64::NAN propagates on legs whose required strike has not yet quoted.
Output schema (IvSkewTick)
The field / type / description table below is regenerated from the IvSkewTick Rust 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. |
date | i32 | Trading-session date (YYYYMMDD). |
ms_of_day | i32 | ms_of_day at emission time. |
spot | f64 | Spot used for IV solving. |
legs | Vec<IvSkewLeg> | Per-expiration skew ladder. Ordered ascending by tenor_days. |
Configuration (IvSkewRequest)
Regenerated from the IvSkewRequest Rust source — see the note above.
| Field | Type | Description |
|---|---|---|
contracts | SecurityFilter | Contracts the subscription tracks. |
rate | Arc<dyn RateService> | Risk-free rate provider. |
annual_dividend | Option<f64> | Optional continuous dividend yield. None falls back to zero. |
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. | |
venues | ExchangeFilter | Exchange / venue admission policy. |
emit | EmitPolicy | Emission policy. Defaults to [EmitPolicy::OnExchangeInterval] at one row per second per symbol: the per-expiration skew ladder is a property of the whole chain snapshot, so the per-second event-time cadence carries the full signal without paying a chain sweep per inbound quote. OnEveryTick fires per admitted Quote; OnClose accumulates and fires on watermark. |
target_delta | f64 | Wing-delta magnitude the picker targets. Default [DEFAULT_TARGET_DELTA] (0.25). The 25-delta convention is the institutional standard (Bloomberg, trade-alert.com, Hull §19.6); raising to 0.10 produces a 10Δ wing sample, lowering to 0.40 produces a near-ATM sample. Calls are picked at +target_delta, puts at -target_delta. Must lie in (0, 0.5) — outside that range the picker silently falls back to the default since a wing delta ≥ 0.5 would collide with the ATM strike. |
delta_tolerance | f64 | Acceptance band around target_delta. Default [DEFAULT_DELTA_TOLERANCE] (0.05). A listed contract is admitted as the wing sample only when its delta lies within target_delta ± delta_tolerance. Sparse legs where no contract sits inside the band drop out cleanly rather than reporting an off-delta sample. A non-positive / non-finite value is treated as "exact match only" — useful for tightly-granulated synthetic chains in testing; in production this would mostly suppress emissions because no live contract hits the target exactly. |
spot_cache | SpotPriceCache | Shared spot price cache. |
calendar | Arc<dyn MarketCalendar> | Market calendar provider. |
Operational characteristics
- Per-tick latency. Three Black–Scholes bisections per leg — bounded by chain forward-expiration count.
- Allocation discipline. Per-symbol leg vector reused across emissions; one allocation per tick for the consumer-facing payload.
- 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} {leg.expiration} "
f"rr25={leg.rr_25d:+.3f} bf25={leg.bf_25d:+.3f}"
)
sub = client.live().iv_skew(["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().ivSkew(["SPX"]).onEvent((tick) => {
for (const leg of tick.legs) {
console.log(
`${tick.symbol} ${leg.expiration} ` +
`rr25=${leg.rr25d.toFixed(3)} bf25=${leg.bf25d.toFixed(3)}`,
);
}
});