"""Property-based tests: random order streams never violate book, reservation or conservation invariants, and the event log replays to the same final state.""" import numpy as np from hypothesis import given, settings, strategies as st from market_agents.exchange.accounts import Account from market_agents.exchange.engine import AssetSpec, Exchange, ExchangeConfig from market_agents.exchange.orders import Action, CancelIntent, OrderIntent, OrderType, Side def build(seed, n_agents=6, allow_short=False): ex = Exchange([AssetSpec(asset=0, name="A0", price_band_pct=0.9)], ExchangeConfig(taker_fee_bps=3.0, maker_fee_bps=-1.0, allow_short=allow_short, max_short_lots=50, order_ttl=7), np.random.default_rng(seed), {0: 1000}) for i in range(n_agents): ex.register(Account(agent_id=i, cash=200_000 * (i + 1), positions={0: 50 * (i + 1)}, short_allowed=allow_short), latency=0.05 + 0.3 * (i % 3)) return ex intent = st.tuples(st.integers(0, 5), st.sampled_from([Side.BUY, Side.SELL]), st.sampled_from([OrderType.LIMIT, OrderType.MARKET]), st.integers(1, 80), st.integers(500, 1500), st.booleans()) @settings(max_examples=60, deadline=None) @given(st.lists(intent, min_size=1, max_size=120), st.integers(0, 10_000)) def test_random_streams_keep_invariants(stream, seed): ex = build(seed) t = 0 for k, (agent, side, otype, qty, price, cancel) in enumerate(stream): if cancel and ex.orders: oid = max(ex.orders) ex.submit(ex.orders[oid].agent_id, Action(cancels=[CancelIntent(oid)]), float(t)) ex.submit(agent, Action(orders=[OrderIntent(0, side, otype, qty, None if otype is OrderType.MARKET else price)]), float(t)) if k % 3 == 2: ex.process_until(float(t + 1)) ex.end_of_tick(t) t += 1 ex.books[0].check_invariants() ex.ledger.check_conservation() ex.process_until(float(t + 5)) ex.books[0].check_invariants() ex.ledger.check_conservation() # reservations equal the sum over resting orders for acc in ex.ledger.accounts.values(): resting = ex.books[0].resting_for(acc.agent_id) assert acc.reserved_cash == sum(o.reserved_cash for o in resting) assert acc.reserved_qty.get(0, 0) == sum(o.reserved_qty for o in resting) assert acc.positions[0] >= 0 @settings(max_examples=25, deadline=None) @given(st.lists(intent, min_size=5, max_size=80), st.integers(0, 1000)) def test_determinism_and_replay(stream, seed): def run(): ex = build(seed) for k, (agent, side, otype, qty, price, cancel) in enumerate(stream): ex.submit(agent, Action(orders=[OrderIntent(0, side, otype, qty, None if otype is OrderType.MARKET else price)]), float(k // 4)) if k % 4 == 3: ex.process_until(float(k // 4 + 1)) ex.process_until(1e9) return ex a, b = run(), run() assert a.snapshot() == b.snapshot() assert [(e.kind, e.payload) for e in a.events] == [(e.kind, e.payload) for e in b.events]