diff options
Diffstat (limited to 'tests/integration/test_order_execution_flow.py')
| -rw-r--r-- | tests/integration/test_order_execution_flow.py | 79 |
1 files changed, 79 insertions, 0 deletions
diff --git a/tests/integration/test_order_execution_flow.py b/tests/integration/test_order_execution_flow.py new file mode 100644 index 0000000..1ea0485 --- /dev/null +++ b/tests/integration/test_order_execution_flow.py @@ -0,0 +1,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 |
