diff --git a/MALKHUT/malkhut/training/cma_trainer.py b/MALKHUT/malkhut/training/cma_trainer.py index 7596b25..76561bc 100644 --- a/MALKHUT/malkhut/training/cma_trainer.py +++ b/MALKHUT/malkhut/training/cma_trainer.py @@ -308,7 +308,11 @@ class Scenario: counterparties: Tuple[CounterpartyPolicy, ...] max_steps: int = 50 tags: Tuple[str, ...] = () - venue: str = "bingx" # exchange this scenario simulates (default: BingX for backward compat) + venue: str = "bingx" # exchange this scenario simulates + # Per-scenario friction overrides (None = use AssetProfile defaults) + maker_fee_bps: Optional[float] = None + taker_fee_bps: Optional[float] = None + adverse_cost_bps: Optional[float] = None # per-fill adverse selection cost class ScenarioFactory: @@ -328,9 +332,15 @@ class ScenarioFactory: """ def __init__(self, counterparties: Optional[Tuple[CounterpartyPolicy, ...]] = None, - exchange_id: str = "bingx") -> None: + exchange_id: str = "bingx", + maker_fee_bps: Optional[float] = None, + taker_fee_bps: Optional[float] = None, + adverse_cost_bps: Optional[float] = None) -> None: self.counterparties = counterparties or default_counterparty_ecology() self.exchange_id = exchange_id + self.maker_fee_bps = maker_fee_bps + self.taker_fee_bps = taker_fee_bps + self.adverse_cost_bps = adverse_cost_bps # --- Behavior-driven helpers --- @@ -374,10 +384,11 @@ class ScenarioFactory: @staticmethod def _behavior_state(symbol: str, spread_mult: float = 1.0, depth_fraction: float = 1.0, - exchange_id: str = "bingx") -> "MarketWorldState": + exchange_id: str = "bingx", + maker_fee_bps: Optional[float] = None, + taker_fee_bps: Optional[float] = None) -> "MarketWorldState": """Create a MarketWorldState from AssetBehavior with realistic params. - - Auto-compiles unknown assets from Binance API if needed. + Friction overrides take precedence over defaults. """ b = ScenarioFactory._get_behavior(symbol) mid = ScenarioFactory._behavior_mid(symbol) @@ -394,7 +405,9 @@ class ScenarioFactory: bid_qty, ask_qty = 1.0, 1.0 return ScenarioFactory._make_state(symbol, bid=bid, ask=ask, bid_qty=bid_qty, ask_qty=ask_qty, - exchange_id=exchange_id) + exchange_id=exchange_id, + maker_fee_bps=maker_fee_bps, + taker_fee_bps=taker_fee_bps) def build_suite( self, @@ -444,7 +457,7 @@ class ScenarioFactory: return Scenario( scenario_id=f"normal_{symbol}_{seed}", symbol=symbol, - initial_state=self._behavior_state(symbol, spread_mult=1.0, depth_fraction=1.0, exchange_id=self.exchange_id), + initial_state=self._behavior_state(symbol, spread_mult=1.0, depth_fraction=1.0, exchange_id=self.exchange_id), maker_fee_bps=self.maker_fee_bps, taker_fee_bps=self.taker_fee_bps, counterparties=self.counterparties, max_steps=steps, tags=("normal", "liquid"), @@ -455,7 +468,7 @@ class ScenarioFactory: return Scenario( scenario_id=f"thin_{symbol}_{seed}", symbol=symbol, - initial_state=self._behavior_state(symbol, spread_mult=1.5, depth_fraction=0.1, exchange_id=self.exchange_id), + initial_state=self._behavior_state(symbol, spread_mult=1.5, depth_fraction=0.1, exchange_id=self.exchange_id), maker_fee_bps=self.maker_fee_bps, taker_fee_bps=self.taker_fee_bps, counterparties=self.counterparties, max_steps=steps, tags=("thin", "illiquid"), @@ -466,7 +479,7 @@ class ScenarioFactory: return Scenario( scenario_id=f"wide_{symbol}_{seed}", symbol=symbol, - initial_state=self._behavior_state(symbol, spread_mult=100.0, depth_fraction=0.5, exchange_id=self.exchange_id), + initial_state=self._behavior_state(symbol, spread_mult=100.0, depth_fraction=0.5, exchange_id=self.exchange_id), maker_fee_bps=self.maker_fee_bps, taker_fee_bps=self.taker_fee_bps, counterparties=self.counterparties, max_steps=steps, tags=("wide", "volatile"), @@ -478,7 +491,7 @@ class ScenarioFactory: return Scenario( scenario_id=f"toxic_{symbol}_{seed}", symbol=symbol, - initial_state=self._behavior_state(symbol, spread_mult=2.0, depth_fraction=0.3, exchange_id=self.exchange_id), + initial_state=self._behavior_state(symbol, spread_mult=2.0, depth_fraction=0.3, exchange_id=self.exchange_id), maker_fee_bps=self.maker_fee_bps, taker_fee_bps=self.taker_fee_bps, counterparties=(ToxicTakerPolicy(sensitivity=0.3),), max_steps=steps, tags=("toxic", "adverse_selection"), @@ -489,7 +502,7 @@ class ScenarioFactory: return Scenario( scenario_id=f"chop_{symbol}_{seed}", symbol=symbol, - initial_state=self._behavior_state(symbol, spread_mult=0.5, depth_fraction=0.2, exchange_id=self.exchange_id), + initial_state=self._behavior_state(symbol, spread_mult=0.5, depth_fraction=0.2, exchange_id=self.exchange_id), maker_fee_bps=self.maker_fee_bps, taker_fee_bps=self.taker_fee_bps, counterparties=self.counterparties, max_steps=steps, tags=("chop", "noise"), @@ -501,7 +514,7 @@ class ScenarioFactory: return Scenario( scenario_id=f"flash_{symbol}_{seed}", symbol=symbol, - initial_state=self._behavior_state(symbol, spread_mult=3.0, depth_fraction=0.05, exchange_id=self.exchange_id), + initial_state=self._behavior_state(symbol, spread_mult=3.0, depth_fraction=0.05, exchange_id=self.exchange_id), maker_fee_bps=self.maker_fee_bps, taker_fee_bps=self.taker_fee_bps, counterparties=(ToxicTakerPolicy(sensitivity=0.3), ToxicTakerPolicy(sensitivity=0.4)), max_steps=steps, tags=("flash_crash", "thin_book"), @@ -513,7 +526,7 @@ class ScenarioFactory: return Scenario( scenario_id=f"vacuum_{symbol}_{seed}", symbol=symbol, - initial_state=self._behavior_state(symbol, spread_mult=5.0, depth_fraction=0.01, exchange_id=self.exchange_id), + initial_state=self._behavior_state(symbol, spread_mult=5.0, depth_fraction=0.01, exchange_id=self.exchange_id), maker_fee_bps=self.maker_fee_bps, taker_fee_bps=self.taker_fee_bps, counterparties=(ToxicTakerPolicy(sensitivity=0.2),), max_steps=steps, tags=("liquidity_vacuum", "extreme"), @@ -525,7 +538,7 @@ class ScenarioFactory: return Scenario( scenario_id=f"multi_toxic_{symbol}_{seed}", symbol=symbol, - initial_state=self._behavior_state(symbol, spread_mult=1.5, depth_fraction=0.3, exchange_id=self.exchange_id), + initial_state=self._behavior_state(symbol, spread_mult=1.5, depth_fraction=0.3, exchange_id=self.exchange_id), maker_fee_bps=self.maker_fee_bps, taker_fee_bps=self.taker_fee_bps, counterparties=( ToxicTakerPolicy(sensitivity=0.3), ToxicTakerPolicy(sensitivity=0.4), @@ -540,7 +553,7 @@ class ScenarioFactory: return Scenario( scenario_id=f"trend_{symbol}_{seed}", symbol=symbol, - initial_state=self._behavior_state(symbol, spread_mult=1.0, depth_fraction=0.8, exchange_id=self.exchange_id), + initial_state=self._behavior_state(symbol, spread_mult=1.0, depth_fraction=0.8, exchange_id=self.exchange_id), maker_fee_bps=self.maker_fee_bps, taker_fee_bps=self.taker_fee_bps, counterparties=self.counterparties, max_steps=steps, tags=("trending", "momentum"), @@ -551,7 +564,7 @@ class ScenarioFactory: return Scenario( scenario_id=f"revert_{symbol}_{seed}", symbol=symbol, - initial_state=self._behavior_state(symbol, spread_mult=20.0, depth_fraction=0.6, exchange_id=self.exchange_id), + initial_state=self._behavior_state(symbol, spread_mult=20.0, depth_fraction=0.6, exchange_id=self.exchange_id), maker_fee_bps=self.maker_fee_bps, taker_fee_bps=self.taker_fee_bps, counterparties=self.counterparties, max_steps=steps, tags=("mean_reverting", "wide_spread"), @@ -563,7 +576,7 @@ class ScenarioFactory: return Scenario( scenario_id=f"weekend_{symbol}_{seed}", symbol=symbol, - initial_state=self._behavior_state(symbol, spread_mult=5.0, depth_fraction=0.05, exchange_id=self.exchange_id), + initial_state=self._behavior_state(symbol, spread_mult=5.0, depth_fraction=0.05, exchange_id=self.exchange_id), maker_fee_bps=self.maker_fee_bps, taker_fee_bps=self.taker_fee_bps, counterparties=(self.counterparties[3],), max_steps=steps, tags=("weekend", "low_participation", "thin"), @@ -577,7 +590,7 @@ class ScenarioFactory: return Scenario( scenario_id=f"funding_{symbol}_{seed}", symbol=symbol, - initial_state=self._behavior_state(symbol, spread_mult=1.5, depth_fraction=0.3, exchange_id=self.exchange_id), + initial_state=self._behavior_state(symbol, spread_mult=1.5, depth_fraction=0.3, exchange_id=self.exchange_id), maker_fee_bps=self.maker_fee_bps, taker_fee_bps=self.taker_fee_bps, counterparties=(LiquidationFlowPolicy(trigger_bps=30.0), ToxicTakerPolicy(sensitivity=0.4)), max_steps=steps, tags=("funding_shock", "deleveraging"), @@ -591,7 +604,7 @@ class ScenarioFactory: return Scenario( scenario_id=f"cascade_{symbol}_{seed}", symbol=symbol, - initial_state=self._behavior_state(symbol, spread_mult=1.2, depth_fraction=0.2, exchange_id=self.exchange_id), + initial_state=self._behavior_state(symbol, spread_mult=1.2, depth_fraction=0.2, exchange_id=self.exchange_id), maker_fee_bps=self.maker_fee_bps, taker_fee_bps=self.taker_fee_bps, counterparties=( LiquidationFlowPolicy(trigger_bps=40.0), ToxicTakerPolicy(sensitivity=0.3), @@ -609,7 +622,7 @@ class ScenarioFactory: return Scenario( scenario_id=f"diverge_{symbol}_{seed}", symbol=symbol, - initial_state=self._behavior_state(symbol, spread_mult=1.5, depth_fraction=0.3, exchange_id=self.exchange_id), + initial_state=self._behavior_state(symbol, spread_mult=1.5, depth_fraction=0.3, exchange_id=self.exchange_id), maker_fee_bps=self.maker_fee_bps, taker_fee_bps=self.taker_fee_bps, counterparties=(ToxicTakerPolicy(sensitivity=0.3), self.counterparties[0]), max_steps=steps, tags=("divergence", "correlation_breakdown"), @@ -623,7 +636,7 @@ class ScenarioFactory: return Scenario( scenario_id=f"stale_{symbol}_{seed}", symbol=symbol, - initial_state=self._behavior_state(symbol, spread_mult=1.0, depth_fraction=0.6, exchange_id=self.exchange_id), + initial_state=self._behavior_state(symbol, spread_mult=1.0, depth_fraction=0.6, exchange_id=self.exchange_id), maker_fee_bps=self.maker_fee_bps, taker_fee_bps=self.taker_fee_bps, counterparties=(StaleQuoteAttackerPolicy(), LatencyArbPolicy(lead_threshold=0.4)), max_steps=steps, tags=("stale_quote", "latency_arb"), @@ -637,7 +650,7 @@ class ScenarioFactory: return Scenario( scenario_id=f"squeeze_{symbol}_{seed}", symbol=symbol, - initial_state=self._behavior_state(symbol, spread_mult=1.5, depth_fraction=0.3, exchange_id=self.exchange_id), + initial_state=self._behavior_state(symbol, spread_mult=1.5, depth_fraction=0.3, exchange_id=self.exchange_id), maker_fee_bps=self.maker_fee_bps, taker_fee_bps=self.taker_fee_bps, counterparties=(InventoryMarketMakerPolicy(max_inventory=0.05), ToxicTakerPolicy(sensitivity=0.4)), max_steps=steps, tags=("squeeze", "inventory_risk"), @@ -650,7 +663,7 @@ class ScenarioFactory: return Scenario( scenario_id=f"news_{symbol}_{seed}", symbol=symbol, - initial_state=self._behavior_state(symbol, spread_mult=10.0, depth_fraction=0.03, exchange_id=self.exchange_id), + initial_state=self._behavior_state(symbol, spread_mult=10.0, depth_fraction=0.03, exchange_id=self.exchange_id), maker_fee_bps=self.maker_fee_bps, taker_fee_bps=self.taker_fee_bps, counterparties=(ToxicTakerPolicy(sensitivity=0.2), ToxicTakerPolicy(sensitivity=0.3)), max_steps=steps, tags=("news_spike", "gap", "thin"), @@ -662,7 +675,7 @@ class ScenarioFactory: return Scenario( scenario_id=f"tighten_{symbol}_{seed}", symbol=symbol, - initial_state=self._behavior_state(symbol, spread_mult=0.3, depth_fraction=1.0, exchange_id=self.exchange_id), + initial_state=self._behavior_state(symbol, spread_mult=0.3, depth_fraction=1.0, exchange_id=self.exchange_id), maker_fee_bps=self.maker_fee_bps, taker_fee_bps=self.taker_fee_bps, counterparties=self.counterparties, max_steps=steps, tags=("spread_tightening", "competition"), @@ -675,7 +688,7 @@ class ScenarioFactory: return Scenario( scenario_id=f"stop_{symbol}_{seed}", symbol=symbol, - initial_state=self._behavior_state(symbol, spread_mult=1.2, depth_fraction=0.2, exchange_id=self.exchange_id), + initial_state=self._behavior_state(symbol, spread_mult=1.2, depth_fraction=0.2, exchange_id=self.exchange_id), maker_fee_bps=self.maker_fee_bps, taker_fee_bps=self.taker_fee_bps, counterparties=(ToxicTakerPolicy(sensitivity=0.3), ToxicTakerPolicy(sensitivity=0.5)), max_steps=steps, tags=("stop_hunting", "manipulation"), @@ -688,7 +701,7 @@ class ScenarioFactory: return Scenario( scenario_id=f"whale_{symbol}_{seed}", symbol=symbol, - initial_state=self._behavior_state(symbol, spread_mult=1.5, depth_fraction=0.3, exchange_id=self.exchange_id), + initial_state=self._behavior_state(symbol, spread_mult=1.5, depth_fraction=0.3, exchange_id=self.exchange_id), maker_fee_bps=self.maker_fee_bps, taker_fee_bps=self.taker_fee_bps, counterparties=(ToxicTakerPolicy(sensitivity=0.2),), max_steps=steps, tags=("whale", "large_order", "impact"), @@ -700,7 +713,7 @@ class ScenarioFactory: return Scenario( scenario_id=f"imbalance_{symbol}_{seed}", symbol=symbol, - initial_state=self._behavior_state(symbol, spread_mult=1.0, depth_fraction=0.8, exchange_id=self.exchange_id), + initial_state=self._behavior_state(symbol, spread_mult=1.0, depth_fraction=0.8, exchange_id=self.exchange_id), maker_fee_bps=self.maker_fee_bps, taker_fee_bps=self.taker_fee_bps, counterparties=self.counterparties, max_steps=steps, tags=("imbalance", "order_flow", "asymmetry"), @@ -713,7 +726,7 @@ class ScenarioFactory: return Scenario( scenario_id=f"withdraw_{symbol}_{seed}", symbol=symbol, - initial_state=self._behavior_state(symbol, spread_mult=1.3, depth_fraction=0.15, exchange_id=self.exchange_id), + initial_state=self._behavior_state(symbol, spread_mult=1.3, depth_fraction=0.15, exchange_id=self.exchange_id), maker_fee_bps=self.maker_fee_bps, taker_fee_bps=self.taker_fee_bps, counterparties=(PassiveMakerPolicy(join_probability=0.2), ToxicTakerPolicy(sensitivity=0.3)), max_steps=steps, tags=("withdrawal", "liquidity_dry", "stress"), @@ -726,7 +739,7 @@ class ScenarioFactory: return Scenario( scenario_id=f"wars_{symbol}_{seed}", symbol=symbol, - initial_state=self._behavior_state(symbol, spread_mult=1.0, depth_fraction=0.8, exchange_id=self.exchange_id), + initial_state=self._behavior_state(symbol, spread_mult=1.0, depth_fraction=0.8, exchange_id=self.exchange_id), maker_fee_bps=self.maker_fee_bps, taker_fee_bps=self.taker_fee_bps, counterparties=( PassiveMakerPolicy(join_probability=0.8), PassiveMakerPolicy(join_probability=0.7), @@ -743,7 +756,7 @@ class ScenarioFactory: return Scenario( scenario_id=f"arb_{symbol}_{seed}", symbol=symbol, - initial_state=self._behavior_state(symbol, spread_mult=0.5, depth_fraction=0.5, exchange_id=self.exchange_id), + initial_state=self._behavior_state(symbol, spread_mult=0.5, depth_fraction=0.5, exchange_id=self.exchange_id), maker_fee_bps=self.maker_fee_bps, taker_fee_bps=self.taker_fee_bps, counterparties=(LatencyArbPolicy(lead_threshold=0.3), ToxicTakerPolicy(sensitivity=0.4)), max_steps=steps, tags=("arbitrage", "cross_venue", "price_discovery"), @@ -755,7 +768,7 @@ class ScenarioFactory: return Scenario( scenario_id=f"flow_{symbol}_{seed}", symbol=symbol, - initial_state=self._behavior_state(symbol, spread_mult=1.0, depth_fraction=0.5, exchange_id=self.exchange_id), + initial_state=self._behavior_state(symbol, spread_mult=1.0, depth_fraction=0.5, exchange_id=self.exchange_id), maker_fee_bps=self.maker_fee_bps, taker_fee_bps=self.taker_fee_bps, counterparties=self.counterparties, max_steps=steps, tags=("order_flow", "institutional", "asymmetry"), @@ -768,7 +781,7 @@ class ScenarioFactory: return Scenario( scenario_id=f"volregime_{symbol}_{seed}", symbol=symbol, - initial_state=self._behavior_state(symbol, spread_mult=0.1, depth_fraction=0.7, exchange_id=self.exchange_id), + initial_state=self._behavior_state(symbol, spread_mult=0.1, depth_fraction=0.7, exchange_id=self.exchange_id), maker_fee_bps=self.maker_fee_bps, taker_fee_bps=self.taker_fee_bps, counterparties=(ToxicTakerPolicy(sensitivity=0.4),), max_steps=steps, tags=("volatility_regime", "transition", "adaptive"), @@ -781,7 +794,7 @@ class ScenarioFactory: return Scenario( scenario_id=f"pump_{symbol}_{seed}", symbol=symbol, - initial_state=self._behavior_state(symbol, spread_mult=1.2, depth_fraction=0.2, exchange_id=self.exchange_id), + initial_state=self._behavior_state(symbol, spread_mult=1.2, depth_fraction=0.2, exchange_id=self.exchange_id), maker_fee_bps=self.maker_fee_bps, taker_fee_bps=self.taker_fee_bps, counterparties=(ToxicTakerPolicy(sensitivity=0.2), ToxicTakerPolicy(sensitivity=0.3)), max_steps=steps, tags=("pump_dump", "manipulation", "coordinated"), @@ -794,7 +807,7 @@ class ScenarioFactory: return Scenario( scenario_id=f"iceberg_{symbol}_{seed}", symbol=symbol, - initial_state=self._behavior_state(symbol, spread_mult=1.5, depth_fraction=0.3, exchange_id=self.exchange_id), + initial_state=self._behavior_state(symbol, spread_mult=1.5, depth_fraction=0.3, exchange_id=self.exchange_id), maker_fee_bps=self.maker_fee_bps, taker_fee_bps=self.taker_fee_bps, counterparties=(ToxicTakerPolicy(sensitivity=0.3),), max_steps=steps, tags=("iceberg", "hidden_order", "gradual_impact"), @@ -808,7 +821,7 @@ class ScenarioFactory: return Scenario( scenario_id=f"margin_{symbol}_{seed}", symbol=symbol, - initial_state=self._behavior_state(symbol, spread_mult=1.3, depth_fraction=0.15, exchange_id=self.exchange_id), + initial_state=self._behavior_state(symbol, spread_mult=1.3, depth_fraction=0.15, exchange_id=self.exchange_id), maker_fee_bps=self.maker_fee_bps, taker_fee_bps=self.taker_fee_bps, counterparties=( LiquidationFlowPolicy(trigger_bps=30.0), ToxicTakerPolicy(sensitivity=0.3), @@ -825,7 +838,7 @@ class ScenarioFactory: return Scenario( scenario_id=f"oracle_{symbol}_{seed}", symbol=symbol, - initial_state=self._behavior_state(symbol, spread_mult=3.0, depth_fraction=0.05, exchange_id=self.exchange_id), + initial_state=self._behavior_state(symbol, spread_mult=3.0, depth_fraction=0.05, exchange_id=self.exchange_id), maker_fee_bps=self.maker_fee_bps, taker_fee_bps=self.taker_fee_bps, counterparties=(ToxicTakerPolicy(sensitivity=0.2), ToxicTakerPolicy(sensitivity=0.3)), max_steps=steps, tags=("oracle_manipulation", "flash_loan", "dex"), @@ -838,7 +851,7 @@ class ScenarioFactory: return Scenario( scenario_id=f"whale_retail_{symbol}_{seed}", symbol=symbol, - initial_state=self._behavior_state(symbol, spread_mult=1.5, depth_fraction=0.3, exchange_id=self.exchange_id), + initial_state=self._behavior_state(symbol, spread_mult=1.5, depth_fraction=0.3, exchange_id=self.exchange_id), maker_fee_bps=self.maker_fee_bps, taker_fee_bps=self.taker_fee_bps, counterparties=(ToxicTakerPolicy(sensitivity=0.3), NoiseTraderPolicy()), max_steps=steps, tags=("whale_vs_retail", "institutional", "retail"), @@ -851,7 +864,7 @@ class ScenarioFactory: return Scenario( scenario_id=f"arb_stress_{symbol}_{seed}", symbol=symbol, - initial_state=self._behavior_state(symbol, spread_mult=0.8, depth_fraction=0.3, exchange_id=self.exchange_id), + initial_state=self._behavior_state(symbol, spread_mult=0.8, depth_fraction=0.3, exchange_id=self.exchange_id), maker_fee_bps=self.maker_fee_bps, taker_fee_bps=self.taker_fee_bps, counterparties=(LatencyArbPolicy(lead_threshold=0.2), ToxicTakerPolicy(sensitivity=0.4)), max_steps=steps, tags=("cross_exchange", "arb_stress", "price_discovery"), @@ -864,7 +877,7 @@ class ScenarioFactory: return Scenario( scenario_id=f"decay_{symbol}_{seed}", symbol=symbol, - initial_state=self._behavior_state(symbol, spread_mult=1.0, depth_fraction=0.7, exchange_id=self.exchange_id), + initial_state=self._behavior_state(symbol, spread_mult=1.0, depth_fraction=0.7, exchange_id=self.exchange_id), maker_fee_bps=self.maker_fee_bps, taker_fee_bps=self.taker_fee_bps, counterparties=(PassiveMakerPolicy(join_probability=0.1), ToxicTakerPolicy(sensitivity=0.4)), max_steps=steps, tags=("decay", "liquidity_withdrawal", "gradual"), @@ -878,7 +891,7 @@ class ScenarioFactory: return Scenario( scenario_id=f"breakdown_{symbol}_{seed}", symbol=symbol, - initial_state=self._behavior_state(symbol, spread_mult=5.0, depth_fraction=0.15, exchange_id=self.exchange_id), + initial_state=self._behavior_state(symbol, spread_mult=5.0, depth_fraction=0.15, exchange_id=self.exchange_id), maker_fee_bps=self.maker_fee_bps, taker_fee_bps=self.taker_fee_bps, counterparties=( ToxicTakerPolicy(sensitivity=0.3), LatencyArbPolicy(lead_threshold=0.3), @@ -976,8 +989,12 @@ class ScenarioFactory: @staticmethod def _make_state(symbol: str, bid: float, ask: float, bid_qty: float, ask_qty: float, - exchange_id: str = "bingx") -> MarketWorldState: - """Create a market state using asset classification for realistic parameters.""" + exchange_id: str = "bingx", + maker_fee_bps: Optional[float] = None, + taker_fee_bps: Optional[float] = None) -> MarketWorldState: + """Create a market state using asset classification for realistic parameters. + Friction overrides take precedence over AssetProfile defaults. + """ from malkhut.training.asset_classification import get_asset_profile, ASSET_PROFILES profile = get_asset_profile(symbol) @@ -986,7 +1003,8 @@ class ScenarioFactory: exchange=exchange_id, symbol=symbol, tick_size=profile.tick_size, lot_size=profile.lot_size, min_qty=profile.lot_size, min_notional=5.0, - maker_fee_bps=profile.maker_fee_bps, taker_fee_bps=profile.taker_fee_bps, + maker_fee_bps=maker_fee_bps if maker_fee_bps is not None else profile.maker_fee_bps, + taker_fee_bps=taker_fee_bps if taker_fee_bps is not None else profile.taker_fee_bps, post_only_supported=True, reduce_only_supported=True, max_orders_per_second=100, max_cancels_per_minute=120, )