malkhut(tests): 1140 test functions across 46 test files
CWM (103): core mechanics, exhaustive edge cases, numba, exchange mechanics Replay (118): exhaustive verification, microstructure, trajectory Training (190): asset classification, phase0 extensive, pipeline, exhaustive DSL (102): v2 syntax, expanded, new features ASEx (33): validate-before-mutate, single-writer Planner (48): MCTS, alternatives, hooks Counterparties (19): 9 adversarial agent policies Clock (30): event-driven reactor BingX (28): venue adapter IPC (8): Zinc SHM Storage (9): ClickHouse Risk (4): hard invariants State (17): frozen dataclass invariants Integration: E2E, concurrency, sync/async seams, hypothesis, fuzz, adversarial
This commit is contained in:
157
MALKHUT/malkhut/tests/test_fuzz.py
Normal file
157
MALKHUT/malkhut/tests/test_fuzz.py
Normal file
@@ -0,0 +1,157 @@
|
||||
"""
|
||||
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
|
||||
Reference in New Issue
Block a user