diff --git a/MALKHUT/malkhut/planner/action_menu.py b/MALKHUT/malkhut/planner/action_menu.py index 83025e8..a4ba3ec 100644 --- a/MALKHUT/malkhut/planner/action_menu.py +++ b/MALKHUT/malkhut/planner/action_menu.py @@ -120,30 +120,50 @@ def build_our_actions( post_only=intent.prefer_maker, reduce_only=intent.reduce_only, )) - # Aggressive crossing — urgency-driven maker/taker decision - # Below threshold: passive only (maker). Above: aggressive (taker). - # The system learns the cheapest/fastest path as a function of urgency. + # Aggressive crossing — urgency + fee+slippage threshold driven maker/taker decision + # The system learns: if (fee + slippage) < threshold → execute (pay the friction) + # Threshold is movable (CMA-ES optimizable), tested at 2-3 bps per leg. + estimated_fee_bps = state.venue.taker_fee_bps # what taker would pay + estimated_slippage_bps = 0.5 # rough estimate; calibrated model refines in CWM + estimated_friction = estimated_fee_bps + estimated_slippage_bps + if intent.urgency > params.urgency_taker_threshold: - # High urgency: MARKET for immediate fill - for frac in (0.05, 0.10, 0.25): - actions.append(FulfilmentAction( - kind=ActionKind.CROSS_SPREAD, side=side, - order_type=OrderType.MARKET, price_ticks_from_best=0, - qty_fraction=frac, ttl_ms=params.aggressive_ttl_ms, - reduce_only=intent.reduce_only, - metadata={"urgency": intent.urgency, "mode": "taker_market"}, - )) + # High urgency: full taker if friction is acceptable + if estimated_friction <= params.execution_friction_threshold_bps or intent.urgency > 0.9: + for frac in (0.05, 0.10, 0.25): + actions.append(FulfilmentAction( + kind=ActionKind.CROSS_SPREAD, side=side, + order_type=OrderType.MARKET, price_ticks_from_best=0, + qty_fraction=frac, ttl_ms=params.aggressive_ttl_ms, + reduce_only=intent.reduce_only, + metadata={"urgency": intent.urgency, "mode": "taker_market", + "friction_bps": estimated_friction}, + )) + else: + # Friction too high even at high urgency → IOC partial + for frac in (0.03, 0.06): + actions.append(FulfilmentAction( + kind=ActionKind.CROSS_SPREAD, side=side, + order_type=OrderType.LIMIT, price_ticks_from_best=0, + qty_fraction=frac, ttl_ms=params.aggressive_ttl_ms, + time_in_force="IOC", + reduce_only=intent.reduce_only, + metadata={"urgency": intent.urgency, "mode": "taker_ioc", + "friction_bps": estimated_friction}, + )) elif intent.urgency > params.urgency_taker_threshold * 0.5: - # Medium urgency: LIMIT + IOC (partial fill OK) - for frac in (0.03, 0.06): - actions.append(FulfilmentAction( - kind=ActionKind.CROSS_SPREAD, side=side, - order_type=OrderType.LIMIT, price_ticks_from_best=0, - qty_fraction=frac, ttl_ms=params.aggressive_ttl_ms, - time_in_force="IOC", - reduce_only=intent.reduce_only, - metadata={"urgency": intent.urgency, "mode": "taker_ioc"}, - )) + # Medium urgency: IOC partial if friction is low + if estimated_friction <= params.execution_friction_threshold_bps * 1.5: + for frac in (0.03, 0.06): + actions.append(FulfilmentAction( + kind=ActionKind.CROSS_SPREAD, side=side, + order_type=OrderType.LIMIT, price_ticks_from_best=0, + qty_fraction=frac, ttl_ms=params.aggressive_ttl_ms, + time_in_force="IOC", + reduce_only=intent.reduce_only, + metadata={"urgency": intent.urgency, "mode": "taker_ioc", + "friction_bps": estimated_friction}, + )) # Low urgency: passive only (maker) — no taker actions added # Path-risk exits diff --git a/MALKHUT/malkhut/state.py b/MALKHUT/malkhut/state.py index 9bccbc9..fef898d 100644 --- a/MALKHUT/malkhut/state.py +++ b/MALKHUT/malkhut/state.py @@ -397,3 +397,7 @@ class FulfilmentPolicyParams: # Urgency-driven maker/taker decision (CMA-ES optimizable) urgency_taker_threshold: float = 0.65 # above this urgency, prefer taker urgency_taker_penalty_bps: float = 2.0 # penalty for taker at low urgency + + # Fee+slippage execution threshold (movable, CMA-ES optimizable) + # If (fee + slippage) < threshold → system tends to EXECUTE (pay the friction) + execution_friction_threshold_bps: float = 3.0 # per leg, test 2-3 bps diff --git a/MALKHUT/malkhut/tests/test_hft_cwm.py b/MALKHUT/malkhut/tests/test_hft_cwm.py index fb11e09..e63465b 100644 --- a/MALKHUT/malkhut/tests/test_hft_cwm.py +++ b/MALKHUT/malkhut/tests/test_hft_cwm.py @@ -107,6 +107,7 @@ def _params() -> FulfilmentPolicyParams: low_liquidity_weight=2.0, latency_stress_weight=1.0, wait_to_retry_ms=0, chase_enabled=False, chase_offset_ticks=1, chase_max_retries=0, urgency_taker_threshold=0.65, urgency_taker_penalty_bps=2.0, + execution_friction_threshold_bps=3.0, ) def _place(side: Side = Side.BUY, price_ticks: int = 0, qty: float = 0.10, diff --git a/MALKHUT/malkhut/training/cma_trainer.py b/MALKHUT/malkhut/training/cma_trainer.py index d34fe25..7596b25 100644 --- a/MALKHUT/malkhut/training/cma_trainer.py +++ b/MALKHUT/malkhut/training/cma_trainer.py @@ -95,6 +95,7 @@ class CMAParameterCodec: ParamSpec("chase_max_retries", "int", 0, 5), ParamSpec("urgency_taker_threshold", "float", 0.1, 0.9), ParamSpec("urgency_taker_penalty_bps", "float", 0.0, 10.0), + ParamSpec("execution_friction_threshold_bps", "float", 0.5, 10.0), ) def initial_vector(self, baseline: FulfilmentPolicyParams) -> list[float]: @@ -157,6 +158,7 @@ class CMAParameterCodec: chase_max_retries=vals.get("chase_max_retries", 0), urgency_taker_threshold=vals.get("urgency_taker_threshold", 0.65), urgency_taker_penalty_bps=vals.get("urgency_taker_penalty_bps", 2.0), + execution_friction_threshold_bps=vals.get("execution_friction_threshold_bps", 3.0), )