""" Tests for tick-level backtest runner and queue-aware fill model. """ from sim.fills import QueueAwareFillModel, FillModelConfig, FillSimulator from sim.maker import MakerConfig from sim.engine import SimConfig class TestQueueAwareFillModel: def test_price_not_crossed(self): qm = QueueAwareFillModel() result = qm.check_fill( aggressor_side="buy", agg_size=0.01, agg_price=49900.0, our_price=50000.0, our_size=0.001, depth_ahead=0.0, ) assert not result["filled"] assert result["reason"] == "price_not_crossed" def test_fills_when_price_crossed_and_no_queue_ahead(self): qm = QueueAwareFillModel() result = qm.check_fill( aggressor_side="buy", agg_size=0.01, agg_price=50005.0, our_price=50000.0, our_size=0.001, depth_ahead=0.0, ) assert result["filled"] assert result["fill_size"] == 0.001 def test_does_not_fill_when_queue_not_reached(self): qm = QueueAwareFillModel() result = qm.check_fill( aggressor_side="buy", agg_size=0.001, agg_price=50005.0, our_price=50000.0, our_size=0.001, depth_ahead=0.005, ) assert not result["filled"] assert result["reason"] == "queue_not_reached" def test_partial_fill(self): qm = QueueAwareFillModel() result = qm.check_fill( aggressor_side="sell", agg_size=0.005, agg_price=49990.0, our_price=50000.0, our_size=0.003, depth_ahead=0.002, ) assert result["filled"] assert result["fill_size"] == 0.003 def test_sell_fill_price_match(self): qm = QueueAwareFillModel() result = qm.check_fill( aggressor_side="sell", agg_size=0.01, agg_price=49990.0, our_price=50000.0, our_size=0.001, depth_ahead=0.0, ) assert result["filled"] def test_estimate_depth_ahead_at_best(self): qm = QueueAwareFillModel() depth = qm.estimate_depth_ahead( our_price=50000.0, our_side="bid", best_bid=50000.0, best_ask=50002.0, bid_depth=2.0, ask_depth=1.0, ) assert depth == 1.0 def test_estimate_depth_ahead_not_at_best(self): qm = QueueAwareFillModel() depth = qm.estimate_depth_ahead( our_price=49999.0, our_side="bid", best_bid=50000.0, best_ask=50002.0, bid_depth=2.0, ask_depth=1.0, ) assert depth == float("inf") def test_fill_rate_tracking(self): qm = QueueAwareFillModel() qm.check_fill("buy", 0.01, 50005.0, 50000.0, 0.001, 0.0) qm.check_fill("buy", 0.001, 50005.0, 50000.0, 0.001, 0.005) qm.check_fill("buy", 0.01, 50005.0, 50000.0, 0.001, 0.0) assert qm.fill_count == 2 assert qm.skip_count == 1 assert qm.fill_rate() == 2 / 3 class TestSimConfigFeeTier: def test_from_fee_tier_default(self): cfg = SimConfig.from_fee_tier(vip_tier=0) assert cfg.maker_fee_pct == 0.00015 assert cfg.taker_fee_pct == 0.00045 def test_from_fee_tier_vip2(self): cfg = SimConfig.from_fee_tier(vip_tier=2) assert cfg.maker_fee_pct == 0.00008 assert cfg.taker_fee_pct == 0.00035 def test_from_fee_tier_with_staking(self): cfg = SimConfig.from_fee_tier(vip_tier=0, staking_tier="gold") assert cfg.maker_fee_pct < 0.00015 assert cfg.taker_fee_pct < 0.00045 def test_from_fee_tier_custom_params(self): cfg = SimConfig.from_fee_tier( vip_tier=0, max_inventory=0.01, initial_equity=50000.0, ) assert cfg.max_inventory == 0.01 assert cfg.initial_equity == 50000.0 class TestVPINGatedASMaker: def test_default_allows_quoting(self): from backtests.tick_runner import VPINGatedASMaker maker = VPINGatedASMaker(MakerConfig()) assert maker.allowed_to_quote() == (True, 1.0) def test_alarm_blocks_quoting(self): from backtests.tick_runner import VPINGatedASMaker maker = VPINGatedASMaker(MakerConfig(), vpin_threshold=0.3, vpin_alarm=0.5) maker._current_vpin = 0.55 assert maker.allowed_to_quote() == (False, 0.0) def test_threshold_reduces_size(self): from backtests.tick_runner import VPINGatedASMaker maker = VPINGatedASMaker(MakerConfig(), vpin_threshold=0.3, vpin_alarm=0.5) maker._current_vpin = 0.35 allowed, size_mult = maker.allowed_to_quote() assert allowed assert 0 < size_mult < 1.0 def test_returns_none_when_not_allowed(self): from backtests.tick_runner import VPINGatedASMaker maker = VPINGatedASMaker(MakerConfig(), vpin_threshold=0.3, vpin_alarm=0.5) maker._current_vpin = 0.55 maker.observe(100000.0) q = maker.quote(100000.0, 0.0, 0.0) assert q is None def test_returns_quote_when_allowed(self): from backtests.tick_runner import VPINGatedASMaker maker = VPINGatedASMaker(MakerConfig()) maker.observe(100000.0) maker.observe(100100.0) maker.observe(100050.0) q = maker.quote(100000.0, 0.0, 0.0) assert q is not None assert q.bid < q.ask assert q.bid_size > 0 class TestWQIPredictor: def test_initial_state(self): from strategies.wqi_predictor import WQIPredictor wqi = WQIPredictor() assert wqi.position == 0 assert len(wqi.trades) == 0 def test_no_signal_with_balanced_book(self): from strategies.wqi_predictor import WQIPredictor wqi = WQIPredictor() bids = [(100.0, 1.0), (99.0, 1.0)] asks = [(102.0, 1.0), (103.0, 1.0)] signal = wqi.feed_signal(bids, asks, 101.0) assert signal["action"] == "HOLD" def test_buy_signal_with_bid_heavy_book(self): from strategies.wqi_predictor import WQIPredictor wqi = WQIPredictor(z_entry=0.5, wqi_threshold=0.05) for _ in range(25): wqi.feed_signal([(100.0, 1.0), (99.0, 1.0)], [(102.0, 1.0), (103.0, 1.0)], 101.0) bids = [(100.0, 10.0), (99.0, 5.0)] asks = [(102.0, 1.0)] signal = wqi.feed_signal(bids, asks, 101.0) if signal["action"] == "BUY": assert wqi.position != 0 def test_exit_on_timeout(self): from strategies.wqi_predictor import WQIPredictor import time wqi = WQIPredictor(z_entry=0.01, z_exit=999.0, wqi_threshold=0.01, max_hold_seconds=0.001) for _ in range(25): wqi.feed_signal([(100.0, 1.0), (99.0, 1.0)], [(102.0, 1.0), (103.0, 1.0)], 101.0) bids = [(100.0, 10.0), (99.0, 5.0)] asks = [(102.0, 1.0)] wqi.feed_signal(bids, asks, 101.0) time.sleep(0.01) signal = wqi.feed_signal(bids, asks, 101.0) assert signal["action"] in ("HOLD", "EXIT") def test_summary_returns_zero_for_no_trades(self): from strategies.wqi_predictor import WQIPredictor wqi = WQIPredictor() s = wqi.summary() assert s["total_trades"] == 0 def test_reset_clears_state(self): from strategies.wqi_predictor import WQIPredictor wqi = WQIPredictor() bids = [(100.0, 1.0)] asks = [(102.0, 1.0)] wqi.feed_signal(bids, asks, 101.0) wqi.reset() assert wqi.position == 0 assert len(wqi.trades) == 0