From 3b49aa2fa272e03fe5c0fb498c5ee84197033302 Mon Sep 17 00:00:00 2001 From: Celes Renata Date: Tue, 21 Apr 2026 20:25:02 +0000 Subject: [PATCH] fix: risk engine now allows sells on over-concentrated positions --- services/risk/engine.py | 5 ++++- tests/test_risk_engine.py | 18 ++++++++++++++++++ 2 files changed, 22 insertions(+), 1 deletion(-) diff --git a/services/risk/engine.py b/services/risk/engine.py index f0a9dc9..25d6532 100644 --- a/services/risk/engine.py +++ b/services/risk/engine.py @@ -298,7 +298,10 @@ def _check_max_position_size( # Check max position value existing_value = state.positions_by_symbol.get(order.ticker, 0.0) - new_total_value = existing_value + order.estimated_value + if order.action == "sell": + new_total_value = max(existing_value - order.estimated_value, 0.0) + else: + new_total_value = existing_value + order.estimated_value checks.append(RiskCheckDetail( check_name="max_position_value", result=( diff --git a/tests/test_risk_engine.py b/tests/test_risk_engine.py index 1a2e483..8a44ed7 100644 --- a/tests/test_risk_engine.py +++ b/tests/test_risk_engine.py @@ -254,6 +254,24 @@ def test_position_pct_exceeded(): assert any(c.check_name == "max_position_pct" and c.result == RiskCheckResult.FAIL for c in result.checks) +def test_sell_on_over_limit_position_allowed(): + """Selling an over-concentrated position should pass risk checks.""" + config = _make_config(position_limits=PositionLimits(max_position_pct=0.05)) + state = _make_state( + portfolio_value=100_000, + positions_by_symbol={"AVGO": 5200.0}, # 5.2% — over the 5% limit + ) + order = ProposedOrder( + ticker="AVGO", sector="Technology", action="sell", + estimated_value=5200.0, quantity=13, + ) + result = evaluate_order(order, config, state) + pct_check = next(c for c in result.checks if c.check_name == "max_position_pct") + assert pct_check.result == RiskCheckResult.PASS, ( + f"Sell on over-limit position should pass, got: {pct_check.message}" + ) + + def test_max_shares_exceeded(): config = _make_config(position_limits=PositionLimits(max_shares_per_order=100)) order = ProposedOrder(ticker="AAPL", sector="Technology", estimated_value=1000, quantity=200)