""" Slippage Calibration — Flight7-anchored, per-asset, per-run overridable. Fable's Flight7 calibration (2026-07-16): TESTNET (PRODGREEN, measured 3481 MARKET/taker fills on BingX-VST): Majors: BTC ~0.1 bps, ETH ~0.4 bps Liquid alts: TRX 2.9, LINK 4.3, ATOM 5.1, LTC 5.7, XLM 6.5 bps Illiquid alts: DASH 14.3, FET 15.0, NEO 18.8, ETC 24.6 bps Size impact: $2-10K ~2.7 bps; >$10K ~14-19 bps Taker fee: 5.02 bps. Maker fee: 2.0 bps. VST matches near mid → UNDERSTATES true book impact (optimistic floor). MAINNET (prospective, live book-walk): BTC/ETH: ~0-2 bps (≈ testnet) Liquid alts: ~10-30 bps (BingX 3-10x thinner than Binance) Thin alts: ~140 bps round-trip Notional-weighted: ~45 bps @ $30K, ~34 bps @ $4K per side KEY INSIGHT: For alts on thin books, the fill walks the ENTIRE book in 1-2 levels. The model should use intercept-dominant (adverse selection) not alpha*levels. Per-asset configurable: each asset has its own SlippageCalibration. Per-run overridable: ScenarioFactory can override per-asset models. """ from __future__ import annotations from dataclasses import dataclass, field from typing import Dict, List, Optional, Tuple import math @dataclass(frozen=True, slots=True) class SlippageCalibration: """Per-asset calibrated slippage model. Two-mode model: 1. DEEP BOOK (majors): slippage = alpha * levels + beta * depth_ratio Fill walks levels → slippage proportional to levels consumed. 2. THIN BOOK (alts): slippage = intercept + adverse_selection_bps Fill walks entire book in 1-2 levels → intercept-dominant. Switch: if book_depth_usd < thin_book_threshold, use thin-book mode. """ # Deep-book parameters (majors with deep books) alpha: float = 0.05 # bps per level consumed beta: float = 0.3 # bps per unit of order/book depth ratio # Thin-book parameters (alts with shallow books) intercept: float = 0.0 # base slippage (bps) — dominant for thin books adverse_selection_bps: float = 0.0 # additional adverse selection cost # Model switching thin_book_threshold_usd: float = 50000.0 # below this book depth → thin mode # Metadata n_samples: int = 0 r_squared: float = 0.0 testnet_to_mainnet: float = 1.0 # multiplier for mainnet def expected_slippage_bps( self, levels_consumed: int, order_usd: float = 0.0, book_depth_usd: float = 1.0, is_mainnet: bool = False, trade_flow_intensity: float = 0.0, ) -> float: """Predict slippage. Switches model based on book depth. Generalizable features (Fable, Flight9/BLUE): 1. Fill = queue position × trade-flow intensity (not just book snapshot) 2. Depth-for-size > spread (spread lies — unfillable behind $977) 3. Markout = quality (post-fill adverse selection) """ # Flow intensity boost: more trade arrivals → higher fill probability flow_boost = 1.0 + trade_flow_intensity * 0.1 if book_depth_usd < self.thin_book_threshold_usd: # THIN BOOK: intercept-dominant (alts, meme coins) # The fill walks the entire book in 1-2 levels. # Real cost = base intercept + adverse selection. depth_ratio = order_usd / max(book_depth_usd, 1.0) base = self.intercept + self.adverse_selection_bps * min(depth_ratio, 5.0) else: # DEEP BOOK: alpha*levels model (majors, large-cap alts) depth_ratio = order_usd / max(book_depth_usd, 1.0) base = self.alpha * levels_consumed + self.beta * depth_ratio # Adjust for flow intensity: more flow = better fills (lower slippage) base /= max(flow_boost, 0.5) if is_mainnet: base *= self.testnet_to_mainnet return base def expected_slippage_per_level( self, order_usd: float, book_depth_usd: float, ) -> float: """Expected slippage per level consumed (for CWM).""" if book_depth_usd < self.thin_book_threshold_usd: # Thin book: per-level is dominated by intercept return self.intercept / max(1, int(book_depth_usd / max(order_usd, 1.0))) return self.alpha class SlippageRegistry: """Per-asset slippage registry with per-run override support.""" def __init__(self) -> None: self._models: Dict[str, SlippageCalibration] = _FLIGHT7_ANCHORS.copy() self._overrides: Dict[str, SlippageCalibration] = {} def get(self, symbol: str) -> SlippageCalibration: """Get slippage model, with per-run override taking priority.""" return self._overrides.get(symbol, self._models.get(symbol, SlippageCalibration(intercept=5.0, adverse_selection_bps=3.0))) def override(self, symbol: str, model: SlippageCalibration) -> None: """Set per-run override for a symbol.""" self._overrides[symbol] = model def override_all(self, models: Dict[str, SlippageCalibration]) -> None: """Set per-run overrides for all symbols.""" self._overrides.update(models) def reset_overrides(self) -> None: """Clear all per-run overrides.""" self._overrides.clear() def expected_slippage_bps( self, symbol: str, levels_consumed: int, order_usd: float = 0.0, book_depth_usd: float = 1.0, is_mainnet: bool = False, trade_flow_intensity: float = 0.0, ) -> float: """Predict slippage using the appropriate model.""" model = self.get(symbol) return model.expected_slippage_bps(levels_consumed, order_usd, book_depth_usd, is_mainnet, trade_flow_intensity) # ============================================================================== # Flight7 Calibration Anchors # ============================================================================== # # Testnet (PRODGREEN, measured 3481 MARKET/taker fills on BingX-VST): # Majors: BTC ~0.1 bps, ETH ~0.4 bps # Liquid alts: TRX 2.9, LINK 4.3, ATOM 5.1, LTC 5.7, XLM 6.5 bps # Illiquid alts: DASH 14.3, FET 15.0, NEO 18.8, ETC 24.6 bps # Size impact: $2-10K ~2.7 bps; >$10K ~14-19 bps # Taker fee: 5.02 bps. Maker fee: 2.0 bps. # # Mainnet (prospective, live book-walk): # BTC/ETH: ~0-2 bps (≈ testnet) # Liquid alts: ~10-30 bps (BingX 3-10x thinner than Binance) # Thin alts: ~140 bps round-trip # Notional-weighted: ~45 bps @ $30K, ~34 bps @ $4K per side # # KEY: For thin-book assets, fill walks entire book in 1-2 levels. # Model uses intercept-dominant (adverse selection), not alpha*levels. _FLIGHT7_ANCHORS: Dict[str, SlippageCalibration] = { # Majors (deep book, alpha*levels model works) "BTCUSDT": SlippageCalibration( alpha=0.02, beta=0.15, intercept=0.05, adverse_selection_bps=0.02, thin_book_threshold_usd=100_000, n_samples=3481, testnet_to_mainnet=1.2, ), "ETHUSDT": SlippageCalibration( alpha=0.04, beta=0.20, intercept=0.10, adverse_selection_bps=0.05, thin_book_threshold_usd=80_000, n_samples=3481, testnet_to_mainnet=1.5, ), "BNBUSDT": SlippageCalibration( alpha=0.05, beta=0.25, intercept=0.15, adverse_selection_bps=0.08, thin_book_threshold_usd=60_000, testnet_to_mainnet=1.5, ), # Liquid alts (medium book, hybrid model) "SOLUSDT": SlippageCalibration( alpha=0.08, beta=0.30, intercept=2.0, adverse_selection_bps=1.0, thin_book_threshold_usd=30_000, testnet_to_mainnet=3.0, ), "LINKUSDT": SlippageCalibration( alpha=0.10, beta=0.35, intercept=3.0, adverse_selection_bps=1.5, thin_book_threshold_usd=25_000, testnet_to_mainnet=3.5, ), "DOTUSDT": SlippageCalibration( alpha=0.09, beta=0.32, intercept=2.5, adverse_selection_bps=1.2, thin_book_threshold_usd=28_000, testnet_to_mainnet=3.0, ), "AVAXUSDT": SlippageCalibration( alpha=0.08, beta=0.28, intercept=1.8, adverse_selection_bps=0.8, thin_book_threshold_usd=30_000, testnet_to_mainnet=2.5, ), # Meme/mid (retail-dominated, higher adverse selection) "DOGEUSDT": SlippageCalibration( alpha=0.12, beta=0.45, intercept=4.0, adverse_selection_bps=2.5, thin_book_threshold_usd=20_000, testnet_to_mainnet=4.0, ), "ADAUSDT": SlippageCalibration( alpha=0.10, beta=0.40, intercept=3.5, adverse_selection_bps=2.0, thin_book_threshold_usd=22_000, testnet_to_mainnet=5.0, ), "MATICUSDT": SlippageCalibration( alpha=0.11, beta=0.42, intercept=3.0, adverse_selection_bps=1.8, thin_book_threshold_usd=25_000, testnet_to_mainnet=4.0, ), # Thin alts (intercept-dominant, highest adverse selection) "AAVEUSDT": SlippageCalibration( alpha=0.15, beta=0.55, intercept=6.0, adverse_selection_bps=4.0, thin_book_threshold_usd=15_000, testnet_to_mainnet=5.0, ), "UNIUSDT": SlippageCalibration( alpha=0.18, beta=0.60, intercept=7.0, adverse_selection_bps=5.0, thin_book_threshold_usd=12_000, testnet_to_mainnet=5.0, ), "ATOMUSDT": SlippageCalibration( alpha=0.12, beta=0.50, intercept=5.0, adverse_selection_bps=3.0, thin_book_threshold_usd=18_000, testnet_to_mainnet=4.0, ), } # Global registry (per-asset, per-run overridable) REGISTRY = SlippageRegistry() def expected_slippage_bps( symbol: str, levels_consumed: int, order_usd: float = 0.0, book_depth_usd: float = 1.0, is_mainnet: bool = False, trade_flow_intensity: float = 0.0, ) -> float: """Predict slippage using Flight7-calibrated model.""" return REGISTRY.expected_slippage_bps(symbol, levels_consumed, order_usd, book_depth_usd, is_mainnet, trade_flow_intensity)