diff --git a/MALKHUT/docs/OB_MICROSTRUCTURE_STUDY.md b/MALKHUT/docs/OB_MICROSTRUCTURE_STUDY.md index cbfd4a5..eceb2c4 100644 --- a/MALKHUT/docs/OB_MICROSTRUCTURE_STUDY.md +++ b/MALKHUT/docs/OB_MICROSTRUCTURE_STUDY.md @@ -392,3 +392,55 @@ The OB microstructure directly determines fill quality: The PerformanceMatrix tracks `avg_fill_value_score` per (regime, strategy, venue), enabling the system to learn: "In this regime, on this venue, this strategy achieves the best fill quality." + +## 14. Flight9/BLUE Fill Learnings (Fable, 2026-07-16) + +Real FLIGHT9/BLUE fill backfill — generalizable features, not hardcoded thresholds. + +### Markout = Quality + +Fill quality is measured by **post-fill markout** (price move N ticks after fill), +not just fill/no-fill. A maker fill at a good quoted price can still be a bad fill +if the market moves adversely after execution. The system scores fills by markout. + +**Generalizable:** score fills by post-fill markout. Model fill-CONDITIONAL-on-adverse-flow. + +### Fill = Queue × Flow Intensity + +Fill probability is driven by **trade-arrival intensity** (the tape), not the static book. +A resting maker fills only when trades print through its level for enough volume to clear +the queue ahead. The HftBacktestCWM queue model is the right substrate — it needs real +trade-flow intensity as input. + +**On testnet:** maker fill-rate ~0% (no flow). This is an artifact, not a signal. + +### Depth-for-Size, Not Spread + +Spread alone LIES: an asset with 1.5bp spread behind ~$977 of depth is unfillable at any +size. Generalizable: key fill viability on **depth-within-K-bps** relative to order +notional, not spread. + +### Measured Fees (Venue-Parameterized) + +| Venue | Maker | Taker | Sign | +|-------|-------|-------|------| +| BingX | 2.00 bp | 5.016 bp | NEGATIVE = DEBIT | + +Generalize: parameterize fee + sign per venue from measurement, never assume. + +### Realized Friction (F9 VST Fills) + +- Taker: ~20-27 bp adverse on thin/mid books +- Maker: saves ~3-4 bp WHEN it fills +- Maker fill-rate: ~0% on VST (no flow — testnet artifact) + +### Counterfactual Maker Fill (Real Binance Book) + +- ~50% fill on liquid books (at-touch upper bound) +- Queue + venue-thinness reduce it + +### Validation Methodology + +When validating against a crude fill sim, optimize on **RELATIVE lift** between two policies +through the SAME sim — fidelity bias cancels in the difference. Trust the ordering of +policies even when absolute fill rates are approximate. diff --git a/MALKHUT/malkhut/cwm/core.py b/MALKHUT/malkhut/cwm/core.py index 38a4155..4ef0663 100644 --- a/MALKHUT/malkhut/cwm/core.py +++ b/MALKHUT/malkhut/cwm/core.py @@ -583,9 +583,15 @@ class MinimalCryptoLOBCWM: if cumulative_levels > 0: from malkhut.training.slippage_calibration import expected_slippage_bps as _esb total_book_usd = sum(l.price * l.qty for l in (book_depth or ())) + bid_vol = sum(l.qty for l in (state.book.bids or ())) + ask_vol = sum(l.qty for l in (state.book.asks or ())) + total_vol = bid_vol + ask_vol + imbalance = abs(bid_vol - ask_vol) / max(total_vol, 1e-12) + flow_intensity = min(imbalance * 2.0, 1.0) expected_slippage_bps = _esb( state.venue.symbol, cumulative_levels, new_fill_price * new_fill_qty, total_book_usd, + trade_flow_intensity=flow_intensity, ) is_maker_fill = (our_action.order_type and our_action.order_type.value == "LIMIT") or our_action.post_only if isinstance(our_action, FulfilmentAction) else False price_improvement_bps = 0.0 diff --git a/MALKHUT/malkhut/cwm/hft_cwm.py b/MALKHUT/malkhut/cwm/hft_cwm.py index 06b3cea..6234647 100644 --- a/MALKHUT/malkhut/cwm/hft_cwm.py +++ b/MALKHUT/malkhut/cwm/hft_cwm.py @@ -503,12 +503,19 @@ class HftBacktestCWM: if cumulative_usd >= new_fill_price * new_fill_qty: break if cumulative_levels > 0: - # Calibrated slippage: per-asset model from VST data + # Calibrated slippage: per-asset model with flow intensity (Fable Flight9) from malkhut.training.slippage_calibration import expected_slippage_bps as _esb total_book_usd = sum(l.price * l.qty for l in (book_depth or ())) + # Estimate flow intensity from book imbalance (proxy for trade arrivals) + bid_vol = sum(l.qty for l in (prev_book.bids or ())) + ask_vol = sum(l.qty for l in (prev_book.asks or ())) + total_vol = bid_vol + ask_vol + imbalance = abs(bid_vol - ask_vol) / max(total_vol, 1e-12) + flow_intensity = min(imbalance * 2.0, 1.0) # high imbalance = more flow expected_slippage_bps = _esb( prev_state.venue.symbol, cumulative_levels, new_fill_price * new_fill_qty, total_book_usd, + trade_flow_intensity=flow_intensity, ) # Price improvement: how much better than best bid/ask? diff --git a/MALKHUT/malkhut/training/slippage_calibration.py b/MALKHUT/malkhut/training/slippage_calibration.py index fdee01d..4f3dd2e 100644 --- a/MALKHUT/malkhut/training/slippage_calibration.py +++ b/MALKHUT/malkhut/training/slippage_calibration.py @@ -64,8 +64,18 @@ class SlippageCalibration: 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.""" + """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. @@ -77,6 +87,9 @@ class SlippageCalibration: 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 @@ -123,10 +136,11 @@ class SlippageRegistry: 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) + return model.expected_slippage_bps(levels_consumed, order_usd, book_depth_usd, is_mainnet, trade_flow_intensity) # ============================================================================== @@ -219,6 +233,7 @@ def expected_slippage_bps( 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) + return REGISTRY.expected_slippage_bps(symbol, levels_consumed, order_usd, book_depth_usd, is_mainnet, trade_flow_intensity)