Fable's Flight9/BLUE generalizable features incorporated: 1. Slippage model gains trade_flow_intensity parameter: - Estimated from book imbalance (proxy for trade arrivals) - More flow → better fills (lower slippage) - Fable: 'fill = queue position × trade-flow intensity' 2. Markout = quality concept documented: - Score fills by post-fill markout, not just fill/no-fill - Maker fills are adversely selected 3. Depth-for-size documented: - Spread lies; key on depth-within-K-bps vs order notional 4. Measured fees: - BingX maker=2.00bp, taker=5.016bp (over 1,455 fills) - BingX commission = NEGATIVE (debit) 5. OB study updated with Flight9 learnings
240 lines
9.6 KiB
Python
240 lines
9.6 KiB
Python
"""
|
||
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)
|