from __future__ import annotations from collections.abc import AsyncIterator from contextlib import asynccontextmanager from datetime import UTC, datetime, timedelta import pytest from arbitrade.config.settings import get_settings from arbitrade.metrics import MetricsCalculator from arbitrade.storage.pg_store import PgStore pytestmark = pytest.mark.integration @asynccontextmanager async def _pg() -> AsyncIterator[PgStore]: s = get_settings() store = PgStore(s) try: await store.start() await store.migrate() yield store finally: await store.stop() @pytest.mark.asyncio async def test_metrics_calculator_summarizes_execution_data() -> None: async with _pg() as store: started = datetime.now(UTC) finished = started + timedelta(seconds=30) started_two = started + timedelta(minutes=1) finished_two = started_two + timedelta(seconds=90) async with store.pool.acquire() as conn: await conn.execute( """ INSERT INTO trades ( trade_ref, started_at, finished_at, status, realized_pnl, estimated_pnl, capital_used, cycle, leg_count ) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9), ($10, $11, $12, $13, $14, $15, $16, $17, $18) """, "trade-1", started, finished, "filled", 12.5, 10.0, 100.0, "USD->BTC->ETH->USD", 3, "trade-2", started_two, finished_two, "filled", - 4.5, -2.0, 200.0, "USD->ETH->BTC->USD", 3, ) await conn.execute( """ INSERT INTO opportunities (detected_at, cycle, gross_pct, net_pct, est_profit, executed) VALUES ($1, $2, $3, $4, $5, $6), ($7, $8, $9, $10, $11, $12), ($13, $14, $15, $16, $17, $18) """, started, "USD->BTC->ETH->USD", 4.0, 3.0, 0.03, True, started_two, "USD->ETH->BTC->USD", 2.0, 1.0, 0.01, False, started_two + timedelta( seconds=30), "USD->BTC->ETH->USD", 5.0, 4.0, 0.04, True, ) await conn.execute( """ INSERT INTO orders ( trade_ref, order_ref, leg_index, pair, side, volume, user_ref, status, filled_volume, avg_price, raw_response, recorded_at ) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12), ($13, $14, $15, $16, $17, $18, $19, $20, $21, $22, $23, $24) """, "trade-1", "order-1", 0, "BTC/USD", "buy", 2.0, 101, "closed", 2.0, 100.0, "{}", started, "trade-2", "order-2", 0, "ETH/USD", "sell", 4.0, 202, "closed", 3.0, 200.0, "{}", started_two, ) metrics = await MetricsCalculator(store).compute() assert metrics.realized_pnl_usd == 8.0 assert metrics.win_rate == 0.5 assert metrics.avg_trade_duration_seconds == 60.0 assert metrics.opportunities_per_minute == 2.0 assert metrics.fill_rate == 0.875 assert metrics.latency_p50_seconds == 60.0 assert metrics.latency_p95_seconds == 87.0 assert metrics.latency_p99_seconds == pytest.approx(89.4)