Skip to content

Latest commit

 

History

40 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

options-market-making-engine

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.

Status

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.

Crates

  • vol-surface - raw SVI calibration per expiry, static (butterfly) and calendar no-arbitrage checks, interpolated surface queries. VolSurface::build now actually calls the butterfly-arbitrage check per slice (it existed and was tested standalone, but wasn't wired into build until this pass), build_with_grid exposes 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). ForwardCurve interpolates 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.rs fits kappa and vol_of_vol from 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 Quote QuoteEntry (Symbol/BidPx/OfferPx/BidSize/OfferSize). Toxicity is an external input slot (toxicity_score: f64), not computed here, that's game-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_pnl instead 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. SnapshotHistory is a bounded in-memory buffer of MarketSnapshots keyed by Deribit instrument name (not by strike/expiry, expiry_years decays with every new snapshot of the same instrument so it can't be part of a stable key), with attribute_from_history pulling two recorded points straight into the attribution and retain_since for 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. No src/lib.rs, Cargo doesn't need one for a package that's only got an integration test target. tests/end_to_end.rs builds 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.

Production dependencies (not in this repo)

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-system and sor-engine for routing and risk-checked execution, plus Deribit's Mass Quote endpoint directly for quoting-engine's output.
  • Options pricing / Greeks: options-pricing-engine-rs for 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 feeds quoting-engine's toxicity_score input.
  • Delta hedge base: gamma-scalper for the execution side of what hedge-orchestrator sizes.
  • Pre-production validation: realistic-mm-backtester for backtesting the quoting/hedge logic against realistic FIFO fills before anything touches live capital.
  • Durable snapshot storage: pnl-explain::SnapshotHistory is 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.

Dev notes

  • Rust 2021, minimal dependencies on purpose: vol-surface has none, book-risk depends only on vol-surface (path dep, reuses its norm_cdf/norm_pdf instead of duplicating them). Calibration is a from-scratch Nelder-Mead, no argmin/nalgebra for 5 free parameters.
  • cargo test --workspace for unit tests, cargo build --release before benchmarking anything. cargo fmt --all -- --check and cargo clippy --workspace --all-targets -- -D warnings are both clean and both run in CI (.github/workflows/ci.yml, dtolnay/rust-toolchain + Swatinem/rust-cache, every push/PR to main). The codebase wasn't run through cargo fmt continuously while it was being written, so the first real fmt --check here 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::evaluate taking 10 args, and a match that should've been an if let in a test. evaluate's fix pulled its "carry" params into VegaCarryParams and reused VegaHedgeCandidate for 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 in inverse_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-explain decomposes 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 in no_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_hedge now 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 TODO inline 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-start calibrate_slice (for warm-starting from a prior tick's params) and calibrate_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 a VolBounds config struct instead of hardcoded, VolBounds::CRYPTO_DEFAULT keeps the old 1e-4..5.0 range as an explicit named default. VolSurface::implied_vol returns 0.0 for T <= 0 instead of dividing by a non-positive T. ForwardCurve::forward_for returns a ForwardLookup with a match_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 actual tick_size/tick_size_steps schema from public/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 in tick.rs. A separate, honestly-different sanity_clamp is wired into build_quote as an internal fat-finger guard (bounds the quoted price to within max_price_deviation of 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-risk now tracks per-position (log-moneyness, gamma) alongside the per-expiry bucket totals, so quoting-engine can 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), and VolSurface'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_vega in quoting-engine is 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.

About

Options market making engine for Deribit inverse (coin-settled) BTC/ETH options. Arb-free SVI vol surface, coin-denominated Greeks derived from Deribit's own inverse pricing formula, Avellaneda-Stoikov quoting in vol-space, Whalley-Wilmott delta hedging via the perpetual. Rust workspace, 111 tests.

Topics

Resources

Stars

4 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages