from __future__ import annotations import asyncio from datetime import UTC, datetime, timedelta from pathlib import Path from arbitrade.backtesting.replay import ( BacktestConfig, BacktestReplayEngine, ReplayBookEvent, load_replay_events, ) from arbitrade.detection.graph import CurrencyGraph from arbitrade.exchange.models import BookLevel def _build_cycles() -> tuple[dict[str, list], list[str]]: graph = CurrencyGraph() graph.add_pair("USD", "BTC", "BTC/USD") graph.add_pair("BTC", "ETH", "ETH/BTC") graph.add_pair("ETH", "USD", "ETH/USD") cycles = graph.triangular_cycles() return graph.index_cycles_by_pair(cycles), ["BTC/USD", "ETH/BTC", "ETH/USD"] def test_load_replay_events_orders_jsonl_by_timestamp(tmp_path: Path) -> None: path = tmp_path / "replay.jsonl" path.write_text( "\n".join( [ '{"timestamp":"2026-06-01T12:00:02Z","symbol":"ETH/USD","bids":[[100.0,1.0]],"asks":[[101.0,1.0]]}', '{"timestamp":"2026-06-01T12:00:01Z","symbol":"BTC/USD","bids":[[10.0,1.0]],"asks":[[11.0,1.0]]}', ] ), encoding="utf-8", ) events = load_replay_events(path) assert [event.symbol for event in events] == ["BTC/USD", "ETH/USD"] def test_backtest_replay_engine_runs_deterministically() -> None: cycles_by_pair, available_pairs = _build_cycles() started_at = datetime(2026, 6, 1, 12, 0, tzinfo=UTC) replay_events = [ ReplayBookEvent( occurred_at=started_at, symbol="BTC/USD", bids=(BookLevel(price=99.5, volume=10.0),), asks=(BookLevel(price=100.0, volume=10.0),), ), ReplayBookEvent( occurred_at=started_at + timedelta(seconds=1), symbol="ETH/BTC", bids=(BookLevel(price=0.051, volume=10.0),), asks=(BookLevel(price=0.050, volume=10.0),), ), ReplayBookEvent( occurred_at=started_at + timedelta(seconds=2), symbol="ETH/USD", bids=(BookLevel(price=110.0, volume=10.0),), asks=(BookLevel(price=110.5, volume=10.0),), ), ] engine = BacktestReplayEngine( cycles_by_pair=cycles_by_pair, available_pairs=available_pairs, config=BacktestConfig(trade_capital=100.0, slippage_bps=5.0, execution_latency_ms=10.0), started_at=started_at, ) report = asyncio.run(engine.run(replay_events, starting_balances={"USD": 1000.0})) assert report.processed_events == 3 assert report.opportunities_seen >= 0 assert report.trades_executed >= 0 assert isinstance(report.realized_pnl_usd, float) assert report.max_drawdown_usd >= 0.0 assert report.execution_latency_p50_ms is None assert report.execution_latency_p95_ms is None assert report.execution_latency_p99_ms is None