from __future__ import annotations from datetime import UTC, datetime, timedelta from arbitrade.backtesting.replay import ReplayBookEvent from arbitrade.backtesting.sweep import ( PromotionCriteria, SweepResult, build_parameter_grid, run_parameter_search, split_events_time_windows, ) from arbitrade.detection.graph import CurrencyGraph from arbitrade.exchange.models import BookLevel def _build_cycles() -> dict[str, list]: graph = CurrencyGraph() graph.add_pair("USD", "BTC", "BTC/USD") graph.add_pair("BTC", "ETH", "ETH/BTC") graph.add_pair("ETH", "USD", "ETH/USD") return graph.index_cycles_by_pair(graph.triangular_cycles()) def _events() -> list[ReplayBookEvent]: base_time = datetime(2026, 6, 1, 12, 0, tzinfo=UTC) rows: list[ReplayBookEvent] = [] for index in range(12): tick = base_time + timedelta(seconds=index) rows.extend( [ ReplayBookEvent( occurred_at=tick, symbol="BTC/USD", bids=(BookLevel(price=99.5, volume=10.0),), asks=(BookLevel(price=100.0, volume=10.0),), ), ReplayBookEvent( occurred_at=tick, symbol="ETH/BTC", bids=(BookLevel(price=0.051, volume=10.0),), asks=(BookLevel(price=0.050, volume=10.0),), ), ReplayBookEvent( occurred_at=tick, symbol="ETH/USD", bids=(BookLevel(price=110.0, volume=10.0),), asks=(BookLevel(price=110.5, volume=10.0),), ), ] ) return rows def test_split_events_time_windows_returns_non_empty_train_and_test() -> None: train, test = split_events_time_windows(_events(), train_ratio=0.7) assert train assert test assert train[-1].occurred_at <= test[0].occurred_at def test_build_parameter_grid_expands_combinations() -> None: grid = build_parameter_grid( theta_values=[0.0005, 0.001], trade_capital_values=[100.0], pair_universes=[["BTC/USD", "ETH/BTC", "ETH/USD"]], staleness_threshold_values=[3.0, 5.0], ) assert len(grid) == 4 def test_run_parameter_search_produces_ranked_results_with_overfit_guard() -> None: artifacts = run_parameter_search( events=_events(), cycles_by_pair=_build_cycles(), parameter_grid=build_parameter_grid( theta_values=[0.0005, 0.001], trade_capital_values=[75.0, 100.0], pair_universes=[["BTC/USD", "ETH/BTC", "ETH/USD"]], staleness_threshold_values=[5.0], ), starting_balances={"USD": 2000.0}, train_ratio=0.7, promotion_criteria=PromotionCriteria( min_test_realized_pnl_usd=-1000.0, min_test_win_rate=0.0, min_test_fill_rate=0.0, max_test_drawdown_usd=1_000_000.0, max_generalization_gap_ratio=0.9, ), ) assert artifacts.results assert artifacts.results[0].test_score >= artifacts.results[-1].test_score first: SweepResult = artifacts.results[0] assert first.train_event_count > 0 assert first.test_event_count > 0 assert first.generalization_gap_ratio >= 0.0 assert isinstance(first.promotion_ready, bool)