feat: add audit events and runtime state snapshots to database
- Introduced new tables for audit events and runtime state snapshots in the database schema. - Created data classes for AuditRecord and RuntimeStateRecord to represent the new entities. - Implemented AuditRepository and RuntimeStateRepository for inserting and retrieving records. - Enhanced the dashboard to include an audit trail section, displaying recent audit events. - Added tests for the new audit repository and runtime lifecycle functionalities. - Updated settings validation to ensure proper configuration for alerting features. - Integrated alert notifications across various components, including execution sequencer and loss limits.
This commit is contained in:
@@ -10,6 +10,31 @@ from arbitrade.detection.engine import OpportunityEvent
|
||||
from arbitrade.execution.sequencer import TriangularExecutionSequencer
|
||||
|
||||
|
||||
@dataclass(slots=True)
|
||||
class _FakeAlertNotifier:
|
||||
events: list[dict[str, str]] = field(default_factory=list)
|
||||
|
||||
async def notify(
|
||||
self,
|
||||
*,
|
||||
category: str,
|
||||
severity: str,
|
||||
title: str,
|
||||
message: str,
|
||||
details: dict[str, str] | None = None,
|
||||
) -> bool:
|
||||
self.events.append(
|
||||
{
|
||||
"category": category,
|
||||
"severity": severity,
|
||||
"title": title,
|
||||
"message": message,
|
||||
**(details or {}),
|
||||
}
|
||||
)
|
||||
return True
|
||||
|
||||
|
||||
@dataclass(slots=True)
|
||||
class _FakeRestClient:
|
||||
fail_at_call: int | None = None
|
||||
@@ -42,9 +67,11 @@ def _sample_event(cycle: str = "USD->BTC->ETH->USD") -> OpportunityEvent:
|
||||
@pytest.mark.asyncio
|
||||
async def test_triangular_sequencer_executes_legs_in_order() -> None:
|
||||
client = _FakeRestClient()
|
||||
notifier = _FakeAlertNotifier()
|
||||
sequencer = TriangularExecutionSequencer(
|
||||
client,
|
||||
available_pairs=["BTC/USD", "ETH/BTC", "ETH/USD"],
|
||||
alert_notifier=notifier,
|
||||
)
|
||||
|
||||
result = await sequencer.execute(_sample_event())
|
||||
@@ -53,14 +80,19 @@ async def test_triangular_sequencer_executes_legs_in_order() -> None:
|
||||
assert result.completed_legs == 3
|
||||
assert [call["pair"] for call in client.calls] == ["BTC/USD", "ETH/BTC", "ETH/USD"]
|
||||
assert [call["side"] for call in client.calls] == ["buy", "buy", "sell"]
|
||||
assert len(notifier.events) == 1
|
||||
assert notifier.events[0]["category"] == "trade"
|
||||
assert notifier.events[0]["title"] == "Trade execution completed"
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_triangular_sequencer_stops_on_failed_leg() -> None:
|
||||
client = _FakeRestClient(fail_at_call=2)
|
||||
notifier = _FakeAlertNotifier()
|
||||
sequencer = TriangularExecutionSequencer(
|
||||
client,
|
||||
available_pairs=["BTC/USD", "ETH/BTC", "ETH/USD"],
|
||||
alert_notifier=notifier,
|
||||
)
|
||||
|
||||
result = await sequencer.execute(_sample_event())
|
||||
@@ -69,6 +101,9 @@ async def test_triangular_sequencer_stops_on_failed_leg() -> None:
|
||||
assert result.completed_legs == 1
|
||||
assert result.failure_reason is not None
|
||||
assert len(client.calls) == 1
|
||||
assert len(notifier.events) == 1
|
||||
assert notifier.events[0]["category"] == "error"
|
||||
assert notifier.events[0]["title"] == "Trade execution failed"
|
||||
|
||||
|
||||
def test_triangular_sequencer_rejects_non_closed_cycle() -> None:
|
||||
|
||||
Reference in New Issue
Block a user