Files
arbitrade/tests/unit/test_backtesting_replay.py
T
2026-06-01 15:18:01 +02:00

84 lines
2.8 KiB
Python

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