summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorTheSiahxyz <164138827+TheSiahxyz@users.noreply.github.com>2026-04-02 09:07:11 +0900
committerTheSiahxyz <164138827+TheSiahxyz@users.noreply.github.com>2026-04-02 09:07:11 +0900
commita841b3a1f2f08caa7f82a1516c47bb5f3c4b7356 (patch)
tree46edcc32df8a431419ded574e71a7cc0a2532c6b
parent9efb0e50d5e2d7025bbe83aaff039ba93beff520 (diff)
feat(risk): add drawdown-based position reduction and consecutive loss pause
-rw-r--r--services/order-executor/tests/test_risk_manager.py51
1 files changed, 51 insertions, 0 deletions
diff --git a/services/order-executor/tests/test_risk_manager.py b/services/order-executor/tests/test_risk_manager.py
index 4bd5761..a8fe37f 100644
--- a/services/order-executor/tests/test_risk_manager.py
+++ b/services/order-executor/tests/test_risk_manager.py
@@ -235,3 +235,54 @@ def test_var_calculation():
positions = {"BTCUSDT": Position(symbol="BTCUSDT", quantity=Decimal("1"), avg_entry_price=Decimal("100"), current_price=Decimal("100"))}
var = rm.calculate_portfolio_var(positions, Decimal("10000"))
assert var >= 0 # Non-negative
+
+
+# --- Drawdown position scaling tests ---
+
+
+def test_drawdown_position_scale_full():
+ rm = RiskManager(max_position_size=Decimal("0.5"), stop_loss_pct=Decimal("5"), daily_loss_limit_pct=Decimal("10"), drawdown_reduction_threshold=0.1, drawdown_halt_threshold=0.2)
+ rm.update_balance(Decimal("10000"))
+ scale = rm.get_position_scale(Decimal("10000"))
+ assert scale == 1.0 # No drawdown
+
+
+def test_drawdown_position_scale_reduced():
+ rm = RiskManager(max_position_size=Decimal("0.5"), stop_loss_pct=Decimal("5"), daily_loss_limit_pct=Decimal("10"), drawdown_reduction_threshold=0.1, drawdown_halt_threshold=0.2)
+ rm.update_balance(Decimal("10000"))
+ scale = rm.get_position_scale(Decimal("8500")) # 15% drawdown (between 10% and 20%)
+ assert 0.25 < scale < 1.0
+
+
+def test_drawdown_halt():
+ rm = RiskManager(max_position_size=Decimal("0.5"), stop_loss_pct=Decimal("5"), daily_loss_limit_pct=Decimal("10"), drawdown_reduction_threshold=0.1, drawdown_halt_threshold=0.2)
+ rm.update_balance(Decimal("10000"))
+ scale = rm.get_position_scale(Decimal("7500")) # 25% drawdown
+ assert scale == 0.0
+
+
+def test_consecutive_losses_pause():
+ rm = RiskManager(max_position_size=Decimal("0.5"), stop_loss_pct=Decimal("5"), daily_loss_limit_pct=Decimal("10"), max_consecutive_losses=3, loss_pause_minutes=60)
+ rm.record_trade_result(False)
+ rm.record_trade_result(False)
+ assert not rm.is_paused()
+ rm.record_trade_result(False) # 3rd loss
+ assert rm.is_paused()
+
+
+def test_consecutive_losses_reset_on_win():
+ rm = RiskManager(max_position_size=Decimal("0.5"), stop_loss_pct=Decimal("5"), daily_loss_limit_pct=Decimal("10"), max_consecutive_losses=3)
+ rm.record_trade_result(False)
+ rm.record_trade_result(False)
+ rm.record_trade_result(True) # Win resets counter
+ rm.record_trade_result(False)
+ assert not rm.is_paused() # Only 1 consecutive loss
+
+
+def test_drawdown_check_rejects_in_check():
+ rm = RiskManager(max_position_size=Decimal("0.5"), stop_loss_pct=Decimal("5"), daily_loss_limit_pct=Decimal("10"), drawdown_halt_threshold=0.15)
+ rm.update_balance(Decimal("10000"))
+ signal = Signal(strategy="test", symbol="BTC/USDT", side=OrderSide.BUY, price=Decimal("50000"), quantity=Decimal("0.01"), reason="test")
+ result = rm.check(signal, Decimal("8000"), {}, Decimal("0")) # 20% dd > 15%
+ assert not result.allowed
+ assert "halted" in result.reason.lower()