summaryrefslogtreecommitdiff
path: root/services/order-executor/tests/test_risk_manager.py
diff options
context:
space:
mode:
Diffstat (limited to 'services/order-executor/tests/test_risk_manager.py')
-rw-r--r--services/order-executor/tests/test_risk_manager.py72
1 files changed, 72 insertions, 0 deletions
diff --git a/services/order-executor/tests/test_risk_manager.py b/services/order-executor/tests/test_risk_manager.py
new file mode 100644
index 0000000..f6b5545
--- /dev/null
+++ b/services/order-executor/tests/test_risk_manager.py
@@ -0,0 +1,72 @@
+"""Tests for RiskManager."""
+from decimal import Decimal
+
+import pytest
+
+from shared.models import OrderSide, Position, Signal
+from order_executor.risk_manager import RiskManager
+
+
+def make_signal(side: OrderSide, price: str, quantity: str, symbol: str = "BTC/USDT") -> Signal:
+ return Signal(
+ strategy="test",
+ symbol=symbol,
+ side=side,
+ price=Decimal(price),
+ quantity=Decimal(quantity),
+ reason="test signal",
+ )
+
+
+def make_risk_manager(
+ max_position_size: str = "0.1",
+ stop_loss_pct: str = "5.0",
+ daily_loss_limit_pct: str = "10.0",
+) -> RiskManager:
+ return RiskManager(
+ max_position_size=Decimal(max_position_size),
+ stop_loss_pct=Decimal(stop_loss_pct),
+ daily_loss_limit_pct=Decimal(daily_loss_limit_pct),
+ )
+
+
+def test_risk_check_passes_normal_order():
+ """Small BUY order with enough balance should be allowed."""
+ rm = make_risk_manager()
+ signal = make_signal(side=OrderSide.BUY, price="100", quantity="0.5")
+ # cost = 50, balance = 10000, position_value = 0 => (0+50)/10000 = 0.5% < 10%
+ result = rm.check(signal, balance=Decimal("10000"), positions={}, daily_pnl=Decimal("0"))
+ assert result.allowed is True
+ assert result.reason == "OK"
+
+
+def test_risk_check_rejects_exceeding_position_size():
+ """5 BTC at $50,000 = $250,000 order cost on $10,000,000 balance exceeds 10% limit."""
+ rm = make_risk_manager(max_position_size="0.1")
+ signal = make_signal(side=OrderSide.BUY, price="50000", quantity="5")
+ # cost = 250000, balance = 1000000 => 250000/1000000 = 25% > 10%
+ # balance is sufficient (250000 < 1000000) but position size is exceeded
+ result = rm.check(signal, balance=Decimal("1000000"), positions={}, daily_pnl=Decimal("0"))
+ assert result.allowed is False
+ assert result.reason == "Position size exceeded"
+
+
+def test_risk_check_rejects_daily_loss_exceeded():
+ """Daily PnL of -1100 on 10000 balance = -11%, exceeding -10% limit."""
+ rm = make_risk_manager(daily_loss_limit_pct="10.0")
+ signal = make_signal(side=OrderSide.BUY, price="100", quantity="0.1")
+ result = rm.check(
+ signal, balance=Decimal("10000"), positions={}, daily_pnl=Decimal("-1100")
+ )
+ assert result.allowed is False
+ assert result.reason == "Daily loss limit exceeded"
+
+
+def test_risk_check_rejects_insufficient_balance():
+ """Order cost of 500 exceeds available balance of 100."""
+ rm = make_risk_manager()
+ signal = make_signal(side=OrderSide.BUY, price="100", quantity="5")
+ # cost = 500, balance = 100
+ result = rm.check(signal, balance=Decimal("100"), positions={}, daily_pnl=Decimal("0"))
+ assert result.allowed is False
+ assert result.reason == "Insufficient balance"