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
|
"""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, MagicMock
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.fetch_balance = AsyncMock(return_value={"free": {"USDT": 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.fetch_balance = AsyncMock(return_value={"free": {"USDT": 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
|