Add benchmark detection script and related tests; refactor settings and update project scripts

This commit is contained in:
2026-06-01 11:01:53 +02:00
parent a89886186f
commit 9d8a8a8a45
6 changed files with 262 additions and 20 deletions
+19
View File
@@ -0,0 +1,19 @@
import pytest
from arbitrade.detection.benchmark import run_incremental_detection_benchmark
def test_incremental_detection_benchmark_returns_metrics() -> None:
result = run_incremental_detection_benchmark(iterations=500)
assert result.iterations == 500
assert result.total_ms > 0.0
assert result.avg_ms > 0.0
assert result.p50_ms > 0.0
assert result.p95_ms > 0.0
assert result.max_ms >= result.p95_ms
def test_incremental_detection_benchmark_rejects_invalid_iterations() -> None:
with pytest.raises(ValueError, match="iterations"):
run_incremental_detection_benchmark(iterations=0)
@@ -0,0 +1,119 @@
from __future__ import annotations
import pytest
from arbitrade.detection.engine import IncrementalCycleDetector
from arbitrade.detection.graph import CurrencyGraph
from arbitrade.exchange.models import BookLevel
from arbitrade.market_data.order_book import OrderBook
def _make_book(*, bid: float, ask: float) -> OrderBook:
book = OrderBook()
book.apply_bids([BookLevel(price=bid, volume=2_000.0)])
book.apply_asks([BookLevel(price=ask, volume=2_000.0)])
return book
def _make_book_levels(
*, bids: list[tuple[float, float]], asks: list[tuple[float, float]]
) -> OrderBook:
book = OrderBook()
book.apply_bids([BookLevel(price=price, volume=volume)
for price, volume in bids])
book.apply_asks([BookLevel(price=price, volume=volume)
for price, volume in asks])
return book
def test_synthetic_single_cycle_known_exact_rates() -> None:
asset_pairs = {
"XXBTZUSD": {"wsname": "BTC/USD"},
"XETHXXBT": {"wsname": "ETH/BTC"},
"XETHZUSD": {"wsname": "ETH/USD"},
}
graph = CurrencyGraph.from_kraken_asset_pairs(asset_pairs)
detector = IncrementalCycleDetector(
graph.index_cycles_by_pair(graph.triangular_cycles()),
fee_rate=0.001,
max_depth_levels=1,
)
books = {
"BTC/USD": _make_book(bid=99.9, ask=100.0),
"ETH/BTC": _make_book(bid=0.049, ask=0.05),
"ETH/USD": _make_book(bid=5.20, ask=5.21),
}
scores = detector.score_updated_pair("ETH/BTC", books)
assert len(scores) == 1
score = scores[0]
# Known path result: 1 USD -> 0.01 BTC -> 0.2 ETH -> 1.04 USD gross.
assert score.gross_rate == pytest.approx(1.04)
assert score.net_rate == pytest.approx(1.04 * (1 - 0.001) ** 3)
def test_synthetic_two_cycles_known_filtering_and_pair_index() -> None:
asset_pairs = {
"XXBTZUSD": {"wsname": "BTC/USD"},
"XETHXXBT": {"wsname": "ETH/BTC"},
"XETHZUSD": {"wsname": "ETH/USD"},
"XLTCXXBT": {"wsname": "LTC/BTC"},
"XLTCZUSD": {"wsname": "LTC/USD"},
}
graph = CurrencyGraph.from_kraken_asset_pairs(asset_pairs)
detector = IncrementalCycleDetector(
graph.index_cycles_by_pair(graph.triangular_cycles()),
min_profit_threshold=0.02,
max_depth_levels=3,
)
books = {
# ETH cycle expected to pass threshold (~4% gross)
"BTC/USD": _make_book(bid=99.9, ask=100.0),
"ETH/BTC": _make_book(bid=0.049, ask=0.05),
"ETH/USD": _make_book(bid=5.20, ask=5.21),
# LTC cycle expected to fail threshold (<2% gross)
"LTC/BTC": _make_book(bid=0.01005, ask=0.0101),
"LTC/USD": _make_book(bid=1.01, ask=1.011),
}
scores = detector.score_updated_pair("BTC/USD", books)
assert len(scores) == 1
assert scores[0].cycle.currencies == ("BTC", "ETH", "USD")
assert (scores[0].net_rate - 1.0) >= 0.02
def test_synthetic_depth_scenario_known_no_result_when_not_fillable() -> None:
asset_pairs = {
"XXBTZUSD": {"wsname": "BTC/USD"},
"XETHXXBT": {"wsname": "ETH/BTC"},
"XETHZUSD": {"wsname": "ETH/USD"},
}
graph = CurrencyGraph.from_kraken_asset_pairs(asset_pairs)
detector = IncrementalCycleDetector(
graph.index_cycles_by_pair(graph.triangular_cycles()),
max_depth_levels=2,
)
books = {
# Not enough ask-side BTC/USD capacity inside top-2 levels for initial USD->BTC conversion.
"BTC/USD": _make_book_levels(
bids=[(99.9, 10.0)],
asks=[(100.0, 0.003), (101.0, 0.003)],
),
"ETH/BTC": _make_book_levels(
bids=[(0.049, 10.0)],
asks=[(0.05, 10.0)],
),
"ETH/USD": _make_book_levels(
bids=[(5.20, 10.0)],
asks=[(5.21, 10.0)],
),
}
scores = detector.score_updated_pair("BTC/USD", books)
assert scores == []