""" Mutation-litmus tests for fee sensitivity. LITMUS: if changing fees 10x does NOT change score, the reward ignores fees. These tests MUST go RED before fee fix, GREEN after. """ import pytest from malkhut.training.cma_trainer import ScenarioFactory, PolicyEvaluator from malkhut.cwm.core import MinimalCryptoLOBCWM from malkhut.state import FulfilmentPolicyParams from malkhut.training.asset_classification import ( ASSET_PROFILES, _profile, Sector, TokenRole, SupplyModel, ConsensusFamily, SmartContractCapability, MarketCapTier, VolatilityProfile, LiquidityProfile, DerivativeAccess, ) def _baseline_params(): return FulfilmentPolicyParams( version='litmus', ucb_c=1.414, max_sims=4, max_depth=1, rollout_depth=1, root_temperature=0.5, min_root_entropy=0.25, quote_offsets_ticks=(0, 1), quote_size_fractions=(0.25, 0.50), passive_ttl_ms=200, aggressive_ttl_ms=50, maker_edge_min_bps=0.5, cross_spread_edge_min_bps=5.0, adverse_toxicity_cancel_threshold=0.5, queue_churn_cancel_threshold=0.5, mae_tail_cut_bps=50.0, mfe_giveback_cut_fraction=0.5, max_time_in_loss_s=300.0, failed_recovery_cut_count=3, recovery_velocity_min_bps_per_s=0.0, max_symbol_notional_fraction=0.20, max_single_order_notional_fraction=0.05, reduce_when_global_up_fraction=0.30, session_profit_lock_fraction=0.02, w_expected_pnl=1.0, w_fill_probability=0.5, w_adverse_selection=2.0, w_queue_priority=0.5, w_inventory_risk=1.5, w_tail_loss=5.0, w_fee_quality=0.5, w_time_decay=0.3, w_policy_entropy=0.5, robust_tail_weight=2.0, toxic_counterparty_weight=3.0, low_liquidity_weight=2.0, latency_stress_weight=1.0, ) def _make_btc_with_fees(taker_fee, maker_fee): return _profile( symbol="BTCUSDT", sectors=[Sector.CURRENCY], token_roles=[TokenRole.STORE_OF_VALUE], supply_model=SupplyModel.FIXED_CAP, consensus=ConsensusFamily.POW, smart_contracts=SmartContractCapability.NONE, market_cap_tier=MarketCapTier.MEGA, volatility_profile=VolatilityProfile.LOW, liquidity_profile=LiquidityProfile.DEEP, derivative_access=DerivativeAccess.PERPS_AND_OPTIONS, tick_size=0.1, lot_size=0.001, price_decimals=1, maker_fee_bps=maker_fee, taker_fee_bps=taker_fee, typical_spread_bps=0.3, typical_depth_usd=5_000_000, typical_daily_volume_usd=30_000_000_000, coingecko_id="bitcoin", cmc_id=1, has_funding=True, has_options=True, ) class TestFeeMutationLitmus: """LITMUS: fee change MUST affect score. These tests go RED if fees are ignored.""" def test_taker_fee_10x_changes_score(self): """Change fee BEFORE building scenarios — score must differ.""" factory = ScenarioFactory() evaluator = PolicyEvaluator(cwm_factory=MinimalCryptoLOBCWM) params = _baseline_params() # Correct fees: build scenarios THEN evaluate ASSET_PROFILES["BTCUSDT"] = _make_btc_with_fees(taker_fee=5.0, maker_fee=2.0) suite_correct = factory.build_suite(symbols=('BTCUSDT',), steps_per_scenario=3) score_correct, _ = evaluator.evaluate_candidate( params=params, scenarios=suite_correct, rng_seed=42, workers=0) # Wrong fees: rebuild scenarios with wrong fees ASSET_PROFILES["BTCUSDT"] = _make_btc_with_fees(taker_fee=0.1, maker_fee=0.0) suite_wrong = factory.build_suite(symbols=('BTCUSDT',), steps_per_scenario=3) score_wrong, _ = evaluator.evaluate_candidate( params=params, scenarios=suite_wrong, rng_seed=42, workers=0) # Restore ASSET_PROFILES["BTCUSDT"] = _make_btc_with_fees(taker_fee=5.0, maker_fee=2.0) print(f' Correct fees score: {score_correct:.0f}') print(f' Wrong fees score: {score_wrong:.0f}') assert score_correct != score_wrong, ( "LITMUS FAILED: Fee change did NOT affect score. Reward ignores fees.") def test_zero_fees_vs_correct_fees(self): """Zero fees vs 5bps — must produce different scores.""" factory = ScenarioFactory() evaluator = PolicyEvaluator(cwm_factory=MinimalCryptoLOBCWM) params = _baseline_params() ASSET_PROFILES["BTCUSDT"] = _make_btc_with_fees(taker_fee=5.0, maker_fee=2.0) suite_correct = factory.build_suite(symbols=('BTCUSDT',), steps_per_scenario=3) score_correct, _ = evaluator.evaluate_candidate( params=params, scenarios=suite_correct, rng_seed=42, workers=0) ASSET_PROFILES["BTCUSDT"] = _make_btc_with_fees(taker_fee=0.0, maker_fee=0.0) suite_zero = factory.build_suite(symbols=('BTCUSDT',), steps_per_scenario=3) score_zero, _ = evaluator.evaluate_candidate( params=params, scenarios=suite_zero, rng_seed=42, workers=0) ASSET_PROFILES["BTCUSDT"] = _make_btc_with_fees(taker_fee=5.0, maker_fee=2.0) print(f' Correct fees score: {score_correct:.0f}') print(f' Zero fees score: {score_zero:.0f}') assert score_correct != score_zero, ( "LITMUS FAILED: Zero fees and 5bps produce same score.")