""" Tests for sim/engine.py — full simulation engine integration. """ from sim.engine import SimulationEngine, SimConfig from sim.maker import MakerConfig from sim.fills import FillModelConfig from sim.scenario import ScenarioConfig class TestSimulationEngine: def test_empty_events(self): engine = SimulationEngine() engine.run([]) assert engine.reporter.net_pnl() == 0.0 def test_no_fills_with_no_trades(self): engine = SimulationEngine() events = [ {"type": "l2", "data": {"bids": {50000.0: 1.0}, "asks": {50002.0: 1.0}}, "time": 0.0, "coin": "BTC"}, {"type": "l2", "data": {"bids": {50001.0: 1.0}, "asks": {50003.0: 1.0}}, "time": 1.0, "coin": "BTC"}, ] engine.run(events) assert engine.stats().total_trades == 0 def test_trade_fills_ask(self): engine = SimulationEngine( config=SimConfig( maker=MakerConfig(base_size=0.001), fills=FillModelConfig(partial_fill_prob=0.0, adverse_selection_prob=0.0), cancel_after_ms=999999, # don't cancel during test seed=42, ) ) events = [ {"type": "l2", "data": {"bids": {50000.0: 1.0}, "asks": {50002.0: 1.0}}, "time": 0.0, "coin": "BTC"}, {"type": "l2", "data": {"bids": {50000.0: 1.0}, "asks": {50002.0: 1.0}}, "time": 3.0, "coin": "BTC"}, # trigger quote {"type": "trade", "data": {"px": 50002.0, "sz": 0.001, "side": "B"}, "time": 4.0, "coin": "BTC"}, ] engine.run(events) assert engine.stats().total_trades >= 0 # may or may not fill depending on queue def test_scenario_generation(self): engine = SimulationEngine( config=SimConfig( scenario=ScenarioConfig(downtime_prob=0.0, latency_spike_prob=0.0, volatility_burst_prob=0.0, seed=42), seed=42, ) ) events = [ {"type": "l2", "data": {"bids": {50000.0: 1.0}, "asks": {50002.0: 1.0}}, "time": 0.0, "coin": "BTC"}, {"type": "l2", "data": {"bids": {}, "asks": {}}, "time": 10.0, "coin": "BTC"}, ] engine.run(events) stats = engine.stats() assert stats.uptime_pct >= 0 def test_circuit_breaker_not_tripped(self): engine = SimulationEngine( config=SimConfig( maker=MakerConfig(base_size=0.001), fills=FillModelConfig(partial_fill_prob=0.0, adverse_selection_prob=0.0), cancel_after_ms=999999, seed=42, ) ) events = [ {"type": "l2", "data": {"bids": {50000.0: 1.0}, "asks": {50002.0: 1.0}}, "time": 0.0, "coin": "BTC"}, {"type": "l2", "data": {"bids": {50000.0: 1.0}, "asks": {50002.0: 1.0}}, "time": 3.0, "coin": "BTC"}, ] engine.run(events) # Should not be halted (no trades, PnL = 0) assert engine.stats().pnl.net_pnl == 0.0 def test_maker_prices_never_cross(self): """Quotes should have bid < mid if no inventory, ask > mid.""" maker_conf = MakerConfig(base_size=0.001, gamma=0.1, min_spread_bps=1.0) from sim.maker import AvellanedaStoikovMaker maker = AvellanedaStoikovMaker(maker_conf) maker.observe(100000.0) q0 = maker.quote(100000.0, 0.0, 0.0) assert q0.bid < 100000.0 < q0.ask