""" Fuzz testing — random state/action sequences through CWM. Verifies CWM never crashes, always produces valid outputs, and preserves invariants under random stress. """ import random import math import pytest from malkhut.state import ( AccountState, FulfilmentPolicyParams, IntentKind, MarketWorldState, Mode, OpenOrderState, OrderBookState, PriceLevel, PositionState, Side, TradePathState, VenueRules, ) from malkhut.cwm.core import MinimalCryptoLOBCWM from malkhut.actions import ActionKind, FulfilmentAction, OrderType def _venue(): return VenueRules( exchange="bingx", symbol="BTCUSDT", tick_size=0.1, lot_size=0.001, min_qty=0.001, min_notional=5.0, maker_fee_bps=-0.2, taker_fee_bps=0.5, post_only_supported=True, reduce_only_supported=True, max_orders_per_second=100, max_cancels_per_minute=120, ) def _random_book(rng): bid = rng.uniform(100.0, 100000.0) ask = bid + rng.uniform(0.1, 100.0) return OrderBookState( ts_ns=rng.randint(1, 2**62), symbol="BTCUSDT", bids=(PriceLevel(bid, rng.uniform(0.001, 10.0)),), asks=(PriceLevel(ask, rng.uniform(0.001, 10.0)),), ) def _random_action(rng): kind = rng.choice(list(ActionKind)) side = rng.choice([Side.BUY, Side.SELL, None]) ot = rng.choice(list(OrderType)) offset = rng.randint(-20, 20) frac = rng.uniform(0.0, 1.0) ttl = rng.randint(0, 5000) cancel_id = f"r_{rng.randint(0, 1000)}" if kind in (ActionKind.CANCEL, ActionKind.CANCEL_REPLACE) else None return FulfilmentAction( kind=kind, side=side, order_type=ot, price_ticks_from_best=offset, qty_fraction=frac, ttl_ms=ttl, cancel_order_id=cancel_id, ) def _random_state(rng): book = _random_book(rng) equity = rng.uniform(100.0, 100000.0) pos_qty = rng.choice([0.0, rng.uniform(-0.5, 0.5)]) pos = None positions = {} if pos_qty != 0.0: pos = PositionState( symbol="BTCUSDT", qty=pos_qty, avg_entry=book.mid, unrealized_pnl=0.0, realized_pnl=0.0, liquidation_price=None, leverage=abs(pos_qty * book.mid) / max(equity, 1.0), side=Side.BUY if pos_qty > 0 else Side.SELL, ) positions["BTCUSDT"] = pos return MarketWorldState( ts_ns=rng.randint(1, 2**62), mode=Mode.REPLAY_NO_IMPACT, venue=_venue(), book=book, account=AccountState( ts_ns=rng.randint(1, 2**62), equity=equity, wallet_balance=equity, available_balance=equity, margin_used=0.0, total_notional=abs(pos_qty * book.mid), positions=positions, ), ) class TestCWMFuzz: def test_100_random_noop_transitions(self): cwm = MinimalCryptoLOBCWM() rng = random.Random(42) for _ in range(100): s = _random_state(rng) a = FulfilmentAction(ActionKind.NOOP, None, None, 0, 0.0, 0) r = cwm.transition(s, (a,)) assert r.ts_ns >= s.ts_ns assert r.account.equity >= 0 def test_100_random_action_transitions(self): cwm = MinimalCryptoLOBCWM() rng = random.Random(123) for _ in range(100): s = _random_state(rng) a = _random_action(rng) r = cwm.transition(s, (a,)) assert r.ts_ns >= s.ts_ns assert r.account.equity >= 0 def test_100_random_multi_counterparty(self): from malkhut.counterparties import ( ToxicTakerPolicy, PassiveMakerPolicy, NoiseTraderPolicy, ) cwm = MinimalCryptoLOBCWM() rng = random.Random(456) cps = [ToxicTakerPolicy(), PassiveMakerPolicy(), NoiseTraderPolicy()] for _ in range(100): s = _random_state(rng) a = _random_action(rng) cp_actions = tuple(cp.rollout_action(s, rng) for cp in cps) r = cwm.transition(s, (a, *cp_actions)) assert r.ts_ns >= s.ts_ns # Equity can go negative with aggressive counterparties (realistic) assert isinstance(r.account.equity, float) def test_50_sequential_transitions(self): cwm = MinimalCryptoLOBCWM() rng = random.Random(789) s = _random_state(rng) for i in range(50): a = FulfilmentAction(ActionKind.NOOP, None, None, 0, 0.0, 0) s = cwm.transition(s, (a,)) assert s.account.equity >= 0 def test_50_sequential_with_actions(self): cwm = MinimalCryptoLOBCWM() rng = random.Random(101) s = _random_state(rng) for i in range(50): a = _random_action(rng) s = cwm.transition(s, (a,)) # Equity can go negative (realistic: overleveraged position) assert isinstance(s.account.equity, float) def test_input_never_mutated(self): cwm = MinimalCryptoLOBCWM() rng = random.Random(202) for _ in range(50): s = _random_state(rng) orig_ts = s.ts_ns orig_equity = s.account.equity a = _random_action(rng) cwm.transition(s, (a,)) assert s.ts_ns == orig_ts assert s.account.equity == orig_equity def test_determinism_under_fuzz(self): cwm = MinimalCryptoLOBCWM() rng = random.Random(303) for _ in range(50): s = _random_state(rng) a = _random_action(rng) r1 = cwm.transition(s, (a,)) r2 = cwm.transition(s, (a,)) assert r1.ts_ns == r2.ts_ns assert r1.account.equity == r2.account.equity