fix: risk engine blocking sell orders on over-concentrated positions

Two bugs: (1) trading engine omitted estimated_value from sell order
jobs, causing risk engine to compute 0 reduction; (2) risk engine
applied position size limits to sells, trapping users in positions
they couldn't exit. Sells now always pass position value/pct checks.
This commit is contained in:
Celes Renata
2026-04-22 02:07:24 +00:00
parent 3b49aa2fa2
commit f251c53f92
2 changed files with 34 additions and 13 deletions
+29 -12
View File
@@ -302,16 +302,24 @@ def _check_max_position_size(
new_total_value = max(existing_value - order.estimated_value, 0.0)
else:
new_total_value = existing_value + order.estimated_value
# Sell orders always pass position value check — they reduce exposure
if order.action == "sell":
value_result = RiskCheckResult.PASS
value_verb = "within (sell reduces exposure)"
elif new_total_value <= limits.max_position_value:
value_result = RiskCheckResult.PASS
value_verb = "within"
else:
value_result = RiskCheckResult.FAIL
value_verb = "exceeds"
checks.append(RiskCheckDetail(
check_name="max_position_value",
result=(
RiskCheckResult.PASS
if new_total_value <= limits.max_position_value
else RiskCheckResult.FAIL
),
result=value_result,
message=(
f"Position value {new_total_value:.2f} "
f"{'within' if new_total_value <= limits.max_position_value else 'exceeds'} "
f"{value_verb} "
f"limit {limits.max_position_value:.2f}"
),
threshold=limits.max_position_value,
@@ -323,16 +331,25 @@ def _check_max_position_size(
position_pct = new_total_value / state.portfolio_value
else:
position_pct = 1.0 if new_total_value > 0 else 0.0
# Sell orders that reduce concentration should always pass — blocking a
# sell on an over-concentrated position prevents the user from fixing it.
if order.action == "sell":
pct_result = RiskCheckResult.PASS
pct_verb = "within (sell reduces exposure)"
elif position_pct <= limits.max_position_pct:
pct_result = RiskCheckResult.PASS
pct_verb = "within"
else:
pct_result = RiskCheckResult.FAIL
pct_verb = "exceeds"
checks.append(RiskCheckDetail(
check_name="max_position_pct",
result=(
RiskCheckResult.PASS
if position_pct <= limits.max_position_pct
else RiskCheckResult.FAIL
),
result=pct_result,
message=(
f"Position {position_pct:.4f} of portfolio "
f"{'within' if position_pct <= limits.max_position_pct else 'exceeds'} "
f"{pct_verb} "
f"limit {limits.max_position_pct:.4f}"
),
threshold=limits.max_position_pct,