Files
arbitrade/tests/unit/test_execution_persistence.py
T
zwitschi 93f4f62d42 feat: Implement idempotency and recovery mechanisms for order execution
- Add IdempotencyKeyFactory for generating unique user references based on execution legs.
- Introduce OrderReconciler to reconcile order statuses with historical data.
- Implement PartialFillRecovery to handle partial fills by canceling orders and placing hedges.
- Create TriangularExecutionSequencer for executing triangular arbitrage strategies.
- Enhance storage with new tables for trades, orders, and PnL events.
- Develop AsyncExecutionWriter for asynchronous writing of execution records to the database.
- Add unit tests for execution persistence, sequencer behavior, fill monitoring, and idempotency checks.
- Update KrakenRestClient to ensure proper payloads for order placement and querying.
2026-06-01 11:59:13 +02:00

91 lines
2.7 KiB
Python

from __future__ import annotations
from dataclasses import dataclass
from datetime import UTC, datetime
import pytest
from arbitrade.config.settings import Settings
from arbitrade.detection.engine import OpportunityEvent
from arbitrade.execution.sequencer import TriangularExecutionSequencer
from arbitrade.storage.db import DuckDBStore
from arbitrade.storage.executions import AsyncExecutionWriter
from arbitrade.storage.repositories import OrderRepository, PnLRepository, TradeRepository
@dataclass(slots=True)
class _FakeRestClient:
calls: int = 0
async def place_market_order(self, *, pair: str, side: str, volume: float) -> dict[str, object]:
self.calls += 1
return {"txid": [f"tx-{self.calls}"], "status": "submitted"}
def _sample_event() -> OpportunityEvent:
return OpportunityEvent(
detected_at=datetime.now(UTC),
cycle="USD->BTC->ETH->USD",
updated_pair="BTC/USD",
gross_rate=1.04,
net_rate=1.03,
gross_pct=4.0,
net_pct=3.0,
est_profit=0.03,
)
@pytest.mark.asyncio
async def test_execution_writer_persists_trade_order_and_pnl(tmp_path) -> None:
settings = Settings(_env_file=None, DUCKDB_PATH=tmp_path / "exec.duckdb")
store = DuckDBStore(settings)
store.migrate()
writer = AsyncExecutionWriter(
TradeRepository(store),
OrderRepository(store),
PnLRepository(store),
max_queue_size=10,
)
await writer.start()
client = _FakeRestClient()
sequencer = TriangularExecutionSequencer(
client,
available_pairs=["BTC/USD", "ETH/BTC", "ETH/USD"],
execution_writer=writer,
)
result = await sequencer.execute(_sample_event())
await writer.stop()
assert result.success
assert client.calls == 3
with store.connect() as conn:
trades = conn.execute(
"SELECT trade_ref, status, estimated_pnl, capital_used, cycle, leg_count FROM trades"
).fetchall()
orders = conn.execute(
"SELECT trade_ref, order_ref, leg_index, pair, side, volume, status "
"FROM orders ORDER BY leg_index"
).fetchall()
pnls = conn.execute(
"SELECT trade_ref, kind, pnl_usd, source FROM pnl_events").fetchall()
assert len(trades) == 1
assert trades[0][1] == "filled"
assert trades[0][2] == 0.03
assert trades[0][3] == 1.0
assert trades[0][4] == "USD->BTC->ETH->USD"
assert trades[0][5] == 3
assert len(orders) == 3
assert orders[0][2] == 0
assert orders[1][2] == 1
assert orders[2][2] == 2
assert orders[0][6] == "submitted"
assert len(pnls) == 1
assert pnls[0][1] == "estimated"
assert pnls[0][2] == 0.03