diff --git a/MALKHUT/malkhut/cwm/core.py b/MALKHUT/malkhut/cwm/core.py index adb4acb..2780bed 100644 --- a/MALKHUT/malkhut/cwm/core.py +++ b/MALKHUT/malkhut/cwm/core.py @@ -568,8 +568,20 @@ class MinimalCryptoLOBCWM: # ── Fill Quality computation ──────────────────────────────────────── mid = state.book.mid if state.book.bids and state.book.asks else 0.0 slippage_bps = 0.0 + expected_slippage_bps = 0.0 if new_fill_qty > 0 and mid > 0 and new_fill_price > 0: slippage_bps = abs(new_fill_price - mid) / mid * 10_000 + # Conditional: expected slippage from book depth + book_depth = state.book.asks if isinstance(our_action, FulfilmentAction) and our_action.side == Side.BUY else state.book.bids if isinstance(our_action, FulfilmentAction) else () + cumulative_usd = 0.0 + cumulative_levels = 0 + for level in (book_depth or ()): + cumulative_usd += level.price * level.qty + cumulative_levels += 1 + if cumulative_usd >= new_fill_price * new_fill_qty: + break + if cumulative_levels > 0: + expected_slippage_bps = cumulative_levels * 0.1 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 if new_fill_qty > 0 and isinstance(our_action, FulfilmentAction) and our_action.post_only and our_action.side: @@ -594,7 +606,8 @@ class MinimalCryptoLOBCWM: fill_value = 0.0 if new_fill_qty > 0: quality = price_improvement_bps if is_maker_fill else max(0.0, spread_bps - slippage_bps) - fill_value = quality - abs(post_fill_adverse) * 0.5 + slippage_surprise = slippage_bps - expected_slippage_bps + fill_value = quality - abs(post_fill_adverse) * 0.5 - max(0.0, slippage_surprise) * 0.3 fq = FillQuality( filled=new_fill_qty > 0, @@ -602,6 +615,7 @@ class MinimalCryptoLOBCWM: fill_price=new_fill_price, requested_qty=our_action.qty_fraction * state.account.available_balance / max(mid, 1e-12) if isinstance(our_action, FulfilmentAction) and our_action.qty_fraction > 0 and mid > 0 else 0.0, slippage_bps=slippage_bps, + expected_slippage_bps=expected_slippage_bps, price_improvement_bps=price_improvement_bps, levels_consumed=0, is_maker_fill=is_maker_fill, diff --git a/MALKHUT/malkhut/cwm/hft_cwm.py b/MALKHUT/malkhut/cwm/hft_cwm.py index 65cd409..35b4313 100644 --- a/MALKHUT/malkhut/cwm/hft_cwm.py +++ b/MALKHUT/malkhut/cwm/hft_cwm.py @@ -483,11 +483,28 @@ class HftBacktestCWM: mid = prev_book.mid if prev_book.bids and prev_book.asks else 0.0 spread_bps = prev_book.spread_bps if prev_book.bids and prev_book.asks else 0.0 - # Slippage: how far from mid did we fill? + # ── CONDITIONAL SLIPPAGE (not constant) ────────────────────────── + # Slippage depends on: order size, book depth, levels consumed slippage_bps = 0.0 + expected_slippage_bps = 0.0 if filled and mid > 0 and new_fill_price > 0: + # Actual slippage: how far from mid did we fill? slippage_bps = abs(new_fill_price - mid) / mid * 10_000 + # Expected slippage from book depth model (power-law) + # Walk levels until we accumulate fill_qty + book_depth = prev_book.asks if action.side == Side.BUY else prev_book.bids + cumulative_usd = 0.0 + cumulative_levels = 0 + for level in (book_depth or ()): + level_usd = level.price * level.qty + cumulative_usd += level_usd + cumulative_levels += 1 + if cumulative_usd >= new_fill_price * new_fill_qty: + break + if cumulative_levels > 0: + expected_slippage_bps = cumulative_levels * 0.1 # ~0.1 bps per level consumed + # Price improvement: how much better than best bid/ask? price_improvement_bps = 0.0 if filled and action.post_only and action.side: @@ -525,11 +542,13 @@ class HftBacktestCWM: else: rolling_fill_rate = 0.0 - # Composite fill value score + # Composite fill value score — conditioned on expected slippage fill_value = 0.0 if filled: quality = price_improvement_bps if is_maker else max(0.0, spread_bps - slippage_bps) - fill_value = quality - abs(post_fill_adverse) * 0.5 + # Adjust by slippage surprise: actual vs expected + slippage_surprise = slippage_bps - expected_slippage_bps # positive = worse than expected + fill_value = quality - abs(post_fill_adverse) * 0.5 - max(0.0, slippage_surprise) * 0.3 return FillQuality( filled=filled, @@ -537,6 +556,7 @@ class HftBacktestCWM: fill_price=new_fill_price, requested_qty=action.qty_fraction * prev_state.account.available_balance / max(mid, 1e-12) if action.qty_fraction > 0 and mid > 0 else 0.0, slippage_bps=slippage_bps, + expected_slippage_bps=expected_slippage_bps, price_improvement_bps=price_improvement_bps, levels_consumed=levels_consumed, is_maker_fill=is_maker, diff --git a/MALKHUT/malkhut/state.py b/MALKHUT/malkhut/state.py index 63714b5..a7ca763 100644 --- a/MALKHUT/malkhut/state.py +++ b/MALKHUT/malkhut/state.py @@ -283,6 +283,9 @@ class FillQuality: # How close to mid did we fill? (for aggressive: positive = slipped) slippage_bps: float = 0.0 + # Expected slippage from book depth model (conditional on actual book state) + expected_slippage_bps: float = 0.0 + # For passive fills: how much better than best bid/ask? (positive = improvement) price_improvement_bps: float = 0.0