Book-level brain for an options market making desk on Deribit: vol surface, risk aggregation, quoting/skew, and hedge orchestration. This repo does not touch a wire. It consumes market data and positions through plain Rust types and produces desired quotes / hedge orders through plain Rust types. Wiring those to an actual exchange is somebody else's crate.
All five crates done and tested: vol-surface (30/30), book-risk (25/25), quoting-engine (33/33), hedge-orchestrator (18/18), pnl-explain (14/14), plus integration-tests (2/2) wiring all five together end to end. 122/122 across the workspace, clean on cargo fmt --check and cargo clippy --workspace --all-targets -- -D warnings. CI (.github/workflows/ci.yml) runs all of that on every push and PR to main. This closes out the core book: surface → risk → quotes → hedges → attribution. Next real work is wiring, not new crates, see "Production dependencies" below.
- vol-surface - raw SVI calibration per expiry, static (butterfly) and calendar no-arbitrage checks, interpolated surface queries.
VolSurface::buildnow actually calls the butterfly-arbitrage check per slice (it existed and was tested standalone, but wasn't wired intobuilduntil this pass),build_with_gridexposes the k-grid resolution for callers who need a wider/narrower scan than the default. - book-risk - Deribit inverse (coin-settled) option pricing and Greeks derived from Deribit's own published formula (not standard BSM, coin-denomination changes the delta/gamma/theta math), position-level scaling, book aggregation bucketed by expiry. All six Greeks closed-form: delta/gamma/vega/theta, plus vanna and volga, each cross-checked at least two independent ways (finite differences, put-call parity, and for vanna specifically, two different derivation paths that have to agree with each other).
ForwardCurveinterpolates between listed expiries and explicitly flags extrapolation instead of silently picking the nearest point. - quoting-engine - Avellaneda-Stoikov run in implied-vol space instead of price space: reservation vol skewed off book-risk's per-expiry vega and strike-localized (Gaussian-kernel-weighted) gamma, AS optimal spread converted to bid/ask vol, both priced through book-risk's inverse-option pricer, sanity-clamped against the mid-vol theoretical price, and rounded to Deribit's real tick schedule.
estimation.rsfitskappaandvol_of_volfrom observation data (fill-rate-vs-distance, ATM IV time series), the fitting logic lives in this repo even though the market-data pipeline to feed it doesn't. Output (QuoteEntry) is shaped to map directly onto a Mass QuoteQuoteEntry(Symbol/BidPx/OfferPx/BidSize/OfferSize). Toxicity is an external input slot (toxicity_score: f64), not computed here, that'sgame-theory-trading-strats' job. - hedge-orchestrator - delta hedge sizing against BTC-PERPETUAL derived directly from Deribit's documented inverse-contract PnL formula (exact, not approximated), a Whalley-Wilmott no-trade band around that hedge (approximated via the standard dollar-greek rescaling, flagged explicitly, see
no_trade_band.rs), and vega hedging with other options: a cost-benefit gate (evaluate) plus candidate selection (select_vega_hedge, sizes and cost-ranks a caller-supplied list of same-bucket options, picks the cheapest one that clears the bar). A genuinely quanto-corrected no-trade band (replacing the dollar-greek approximation) is intentionally not pursued here, that derivation is being kept proprietary rather than published in this repo. - pnl-explain - second-order Taylor attribution of realized PnL into delta/gamma/vega/vanna/volga contributions (first-order only in time, just theta, no charm cross-term) between two market snapshots, per position and summed to book level, with whatever the expansion doesn't explain falling out as an explicit
unexplained_pnlinstead of being hidden. Validated by checking the residual actually shrinks at the right rate (~1/8 when the move size halves) as evidence the Greeks feeding it are internally consistent, not just that the arithmetic runs.SnapshotHistoryis a bounded in-memory buffer ofMarketSnapshots keyed by Deribit instrument name (not by strike/expiry,expiry_yearsdecays with every new snapshot of the same instrument so it can't be part of a stable key), withattribute_from_historypulling two recorded points straight into the attribution andretain_sincefor pruning old entries so it doesn't grow forever in a long-running process. - integration-tests (
publish = false, not one of the five, dev-only) - the only place in the workspace that depends on all five crates at once. Nosrc/lib.rs, Cargo doesn't need one for a package that's only got an integration test target.tests/end_to_end.rsbuilds a surface, aggregates a book off it, quotes an instrument against that book, sizes a perp delta hedge and runs the no-trade band decision, evaluates a vega hedge candidate, and attributes PnL for the same positions that built the book, in one continuous run. Unit tests can't catch a sign-convention or type drift between crates, this is what would.
This engine assumes the following are already running and expects to be wired to them, it doesn't reimplement any of it:
- Market data feed:
feedhandler-core-rs, extended with a Deribit-specific normalizer for the options + perp + index feeds. - Order execution:
oms-order-management-systemandsor-enginefor routing and risk-checked execution, plus Deribit's Mass Quote endpoint directly forquoting-engine's output. - Options pricing / Greeks:
options-pricing-engine-rsfor anything beyond surface-fitting IV (this repo carries only the minimal Black-76 solver it needs internally for quote ingestion, don't use it as a general pricing library). - Flow toxicity:
game-theory-trading-strats' VPIN/Kyle's lambda feedsquoting-engine'stoxicity_scoreinput. - Delta hedge base:
gamma-scalperfor the execution side of whathedge-orchestratorsizes. - Pre-production validation:
realistic-mm-backtesterfor backtesting the quoting/hedge logic against realistic FIFO fills before anything touches live capital. - Durable snapshot storage:
pnl-explain::SnapshotHistoryis an in-memory buffer only, nothing persists it across restarts. Something upstream needs to feed it (record()) at whatever cadence the attribution runs on and, if history needs to survive a restart, write it to actual storage, this repo doesn't do disk/database I/O anywhere.
- Rust 2021, minimal dependencies on purpose:
vol-surfacehas none,book-riskdepends only onvol-surface(path dep, reuses itsnorm_cdf/norm_pdfinstead of duplicating them). Calibration is a from-scratch Nelder-Mead, noargmin/nalgebrafor 5 free parameters. cargo test --workspacefor unit tests,cargo build --releasebefore benchmarking anything.cargo fmt --all -- --checkandcargo clippy --workspace --all-targets -- -D warningsare both clean and both run in CI (.github/workflows/ci.yml,dtolnay/rust-toolchain+Swatinem/rust-cache, every push/PR tomain). The codebase wasn't run throughcargo fmtcontinuously while it was being written, so the first realfmt --checkhere found ~2500 lines of drift, that's a one-time cost of not having the CI job from day one, not an ongoing problem. Clippy's three real findings: a needless range-index loop in the Nelder-Mead shrink step,vega_hedge::evaluatetaking 10 args, and amatchthat should've been anif letin a test.evaluate's fix pulled its "carry" params intoVegaCarryParamsand reusedVegaHedgeCandidatefor the hedge-trade side instead of six loose scalars, which reads better regardless of the lint.book-risk's Greeks are coin-denominated, derived from Deribit's own published inverse-option Black-Scholes formula (linked ininverse_option.rs), not textbook BSM. The division by the forward that coin-settlement implies changes the delta/gamma formulas, this is documented and cross-checked (put-call parity + finite differences) in that module rather than asserted. Theta is closed-form too, derived by hand and cross-checked against finite differences and against call theta equaling put theta (put-call parity's RHS doesn't depend on T). Vanna and volga are in now: vanna derived two independent ways (d(delta)/dvol and d(vega)/dF) that had to land on the same closed form before it got trusted, volga cross-checked against the textbook vegad1d2/sigma identity as an external sanity check beyond just finite differences.pnl-explaindecomposes realized PnL via a second-order (F, vol) / first-order (time) Taylor expansion around a starting snapshot's Greeks, book-level attribution is just the sum of each position's own attribution rather than one expansion around aggregate book Greeks, which stays exact regardless of how the book's mix of strikes/expiries shifted between snapshots. The convergence-rate test (residual shrinks ~8x when the move size halves, matching second-order Taylor error) is doing real work here, it would fail if the Greeks feeding the attribution were inconsistent with the pricer, not just if the arithmetic were wrong.hedge-orchestrator's perp delta-hedge sizing is exact, derived directly from Deribit's own documented inverse-contract PnL formula and cross-checked against finite differences. Its no-trade band is not exact, applying Whalley-Wilmott to a coin-denominated book requires rescaling to "dollar greeks" first (documented inno_trade_band.rs), which is standard practice but is an approximation, not a from-scratch quanto-corrected derivation, that derivation is intentionally not in this repo (kept proprietary). Its option-fee model uses Deribit's real capped-fee mechanism (min of a rate-based fee and a cap fraction of premium); the rate/cap themselves are correctly caller-supplied config, the actual gap is production-wiring (an authenticated call to Deribit's account API), not something this pure-computation crate should reach for.select_vega_hedgenow picks among a caller-supplied set of same-expiry-bucket candidates by cost per unit of vega neutralized, cross-expiry (calendar/basis) hedge selection isn't modeled.- Remaining known gaps: none marked
TODOinline right now. The proprietary WW derivation above is the one open item, kept out of this repo on purpose rather than tracked as code debt. vol-surface's calibration has both a single-startcalibrate_slice(for warm-starting from a prior tick's params) andcalibrate_slice_robust(multi-start: the caller's guess, a generic crypto-vol anchor, and jittered variants of both, keeps the best fit). Tested against a case where the single-start version fails outright but the robust version recovers a good fit. The IV solver's bounds are aVolBoundsconfig struct instead of hardcoded,VolBounds::CRYPTO_DEFAULTkeeps the old 1e-4..5.0 range as an explicit named default.VolSurface::implied_volreturns 0.0 forT <= 0instead of dividing by a non-positive T.ForwardCurve::forward_forreturns aForwardLookupwith amatch_kind(Exact/Interpolated/Extrapolated { nearest_listed_expiry_years }) instead of silently snapping to the nearest listed point.quoting-engine's tick rounding mirrors Deribit's actualtick_size/tick_size_stepsschema frompublic/get_instrument. Deliberately not modeled: Deribit's order-price bandwidth clamp against their portfolio margin risk matrix (a documented "minimum trading bandwidth constant" of 0.015). That bandwidth applies to a risk-matrix price-bucket move that isn't public, applying the 0.015 to the wrong base quantity would produce a plausible-looking but wrong number, so it's left undone rather than faked, see the comment intick.rs. A separate, honestly-differentsanity_clampis wired intobuild_quoteas an internal fat-finger guard (bounds the quoted price to withinmax_price_deviationof the mid-vol theoretical price), it doesn't claim to be Deribit's mechanism, it's just not trusting a bug upstream to be caught by the exchange.book-risknow tracks per-position (log-moneyness, gamma) alongside the per-expiry bucket totals, soquoting-enginecan skew a quote off gamma concentrated near that specific strike (BookRisk::local_gamma_near, Gaussian kernel in log-moneyness, restricted to the same expiry bucket) instead of the whole tenor's average. Vega skew stays bucket-level on purpose, vega risk is genuinely more diffuse across a tenor than gamma is.- Four previously-hardcoded values are now config instead:
calibrate_slice_robust's generic multi-start anchor and per-parameter jitter scale (CRYPTO_GENERIC_ANCHOR,PerturbationScale),quoting-engine's inventory-throttle size floor (QuoteRequest::size_floor_fraction), andVolSurface's no-arb scan grid (build_with_grid). None of them changed behavior when called with their old values,build()and the default-arg test fixtures still work exactly as before. max_bucket_vegainquoting-engineis caller-supplied on purpose, not something this crate can sync with a real MMP group's limit itself, that needs an authenticated call to Deribit's private API, which belongs in production wiring, not in a pure-computation crate.