Crack the Sky
pricing
Calculators, plus two derived objects: the ATM term structure and the
SVI vol surface. Pass
S, K, T, r, q (or F) and sigma
explicitly to the calculators.
- GreeksConventions
- Defaults: vega per 1.00 vol, theta per year of calendar time, spot delta/gamma. Dual (∂/∂K) is always also populated. Tests that read a Greek must name the convention.
- Named catalog
-
price; 1st
delta, dual_delta, vega, theta, rho, rho_dividend; 2ndgamma, dual_gamma, vanna, volga, charm, veta, vera; 3rdspeed, zomma, color, ultima; elasticity. - European vs American
-
Engineprotocol:EuropeanBSMclosed-form;AmericanCRRtree with bump-and-revalue Greeks. Style comes from the contract (SPY American, SPX/SPXW European), but the engine is what an IV invert follows: a chain downgrades far-from-the-money SPY strikes toEuropeanBSM, and a row inverted on one engine and repriced on the other carries the early-exercise premium as residual. - IV invert
-
One inverter per exercise style, matching the two engines.
implied_volinverts European BSM: Newton with bounds, Brent fallback, closed-form vega for the Newton step.implied_vol_americaninverts the American CRR tree with Brent alone — the tree has no closed-form vega, and a central-difference one costs two extra trees per Newton step, more evaluations than the bracketed search it was meant to accelerate. Bounds differ with the style, so each has its own:discounted_boundsandamerican_bounds(undiscounted intrinsic below, andKrather thanKe-rTabove, for a put). A price outside them →ValueError. Neither returns NaN. - The tree is part of the answer
-
An American sigma is the value that reprices the contract
on a given tree, so the inverter takes the engine's own
n_steps; invert an 801-step price on a 401-step tree and sigma moves by more than the solver tolerance. Accuracy is bounded by discretisation, not byxtol. The search also starts above|r-q|√dt, below which the CRR tree is not arbitrage-free and refuses to price at all.implied_vol_americanreturns0.0for a price with no time value — a deep ITM American put is exercised now and is worthK-Sat every sigma, which is a property of the contract, not a solver failure. - ATM term structure
-
pricing.term_structurereducesoption_day_barsto one row per (date, root, expiry): parity forward, ATM strike, both leg IVs. Same chain the surface fits. Vol time is the hybrid (DEFAULT_DAYCOUNT): sessions/252 inside the calendar horizon, ACT/365 past it; each row stampsdaycountasbus/252oract/365. Money time (the rate tenor) stays ACT/365. Scheduled Tue–Sat 12:00 ET; archive rebuild viascripts/build_term_structure.py. A stale or crossed ATM print is written null, never as zero vol. - SVI surface
-
pricing.surfacefits raw SVI (Gatheral),w(k) = a + b(ρ(k−m) + √((k−m)² + σ²))overk = ln(K/F), per (date, root, expiry) on own-IV OTM strikes fromoption_day_bars— calls above the parity forward, puts below, same chain and same F as the ATM curve. SPX/SPXW only: SPY strikes invert on the American tree (implied_vol_american) and this fit path is European end to end — an SPY smile is follow-up, not a flag. OTM closes at or belowMIN_TIME_VALUE_FRACof the forward are dropped before inversion (an OTM close is time value; a near-worthless one is a tick-dominated stale print, and day bars carry no trade-age signal — that filter lives on snapshots vialast_trade_asof_ns). Box bounds plus the non-negative-total-variance domain constraint; deterministic data-derived seed;MAX_NFEVraised past scipy's default because real chains exhaust it long before the 1e-12 tolerances are met. The arbitrage guards are enforced inside the fit: a slice whose unconstrained optimum violates butterfly (g(k) ≥ 0on the padded grid) or dips below its predecessor's total variance (calendar, chained in expiry order on the guard's union grid) is refit by SLSQP under both as hard constraints, from feasibility-projected seeds, and the repair is kept only while it still explains the slice (REPAIR_MAX_REL_RMS) — past that the arbitrage is in the data andSurfaceArbitrageErrorraises from the unchanged post-fit guards.Surface.vol(K, T)interpolates linearly in total variance between bracketing expiries, flat outside, exact on a fitted expiry. Scheduled Tue–Sat 12:15 ET asmassive-surface(after the ATM curve at 12:00, beforecoverage_auditat 12:30). CLI:python -m pricing.surface --date … --underlying SPXW; archive:scripts/build_surface.py→vol_surface. Read back withload_surface/Surface.from_rows— do not refit from day bars. No VIX surface: those options are on the VX future, a different modelling question. BLAS threads are pinned to 1 byscripts/cronjob.shandscripts/build_surface.py: the fit is a constrained least-squares solve, and at 8 threads instead of 1 it lands elsewhere in an equally good basin — 397 of 720 values moved on 2026-09-04,svi_rhoby up to 2.24e-03 relative, withrms_errormoving less than 1e-09. Parameters that depend on how busy the box was cannot be diffed across a rebuild or reproduced by a backtest. The ATM curve is unaffected (scalar Brent, no BLAS).