1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
|
"""Integration test: signal -> risk manager -> order executor -> order event."""
import sys
from pathlib import Path
sys.path.insert(0, str(Path(__file__).resolve().parents[2] / "services" / "order-executor" / "src"))
import pytest
from decimal import Decimal
from unittest.mock import AsyncMock
from shared.models import Signal, OrderSide, OrderStatus
from order_executor.executor import OrderExecutor
from order_executor.risk_manager import RiskManager
@pytest.mark.asyncio
async def test_signal_to_order_flow():
"""A valid signal passes risk checks and produces a filled order."""
signal = Signal(
strategy="rsi",
symbol="BTC/USDT",
side=OrderSide.BUY,
price=Decimal("50000"),
quantity=Decimal("0.01"),
reason="RSI oversold",
)
exchange = AsyncMock()
exchange.get_buying_power = AsyncMock(return_value=Decimal("10000"))
risk_manager = RiskManager(
max_position_size=Decimal("0.5"),
stop_loss_pct=Decimal("5"),
daily_loss_limit_pct=Decimal("10"),
)
broker = AsyncMock()
db = AsyncMock()
notifier = AsyncMock()
executor = OrderExecutor(
exchange=exchange,
risk_manager=risk_manager,
broker=broker,
db=db,
notifier=notifier,
dry_run=True,
)
order = await executor.execute(signal)
assert order is not None
assert order.status == OrderStatus.FILLED
assert order.symbol == "BTC/USDT"
assert order.side == OrderSide.BUY
# Verify order was persisted and published
db.insert_order.assert_called_once()
broker.publish.assert_called_once()
notifier.send_order.assert_called_once()
@pytest.mark.asyncio
async def test_signal_rejected_by_risk_manager():
"""A signal that exceeds position size is rejected."""
signal = Signal(
strategy="rsi",
symbol="BTC/USDT",
side=OrderSide.BUY,
price=Decimal("50000"),
quantity=Decimal("100"), # Way too large
reason="test",
)
exchange = AsyncMock()
exchange.get_buying_power = AsyncMock(return_value=Decimal("1000"))
risk_manager = RiskManager(
max_position_size=Decimal("0.1"),
stop_loss_pct=Decimal("5"),
daily_loss_limit_pct=Decimal("10"),
)
executor = OrderExecutor(
exchange=exchange,
risk_manager=risk_manager,
broker=AsyncMock(),
db=AsyncMock(),
notifier=AsyncMock(),
dry_run=True,
)
order = await executor.execute(signal)
assert order is None # Rejected
|