@@ -380,6 +380,29 @@ def _snapshot_metrics(
380380 }, observed , total_equity , set ()
381381
382382
383+ def _static_gate_total_equity (portfolio_snapshot : Any ) -> float | None :
384+ """Return the equity needed to normalize value targets in the live gate.
385+
386+ The non-evidence ``apply_risk_gate`` path accepts both weight and value
387+ targets. A value target has no meaningful concentration without the same
388+ account equity used by the strategy invocation, so an absent or malformed
389+ snapshot must never turn it into a zero-weight target.
390+ """
391+ try :
392+ if isinstance (portfolio_snapshot , Mapping ):
393+ raw_total_equity = portfolio_snapshot .get ("total_equity" )
394+ elif isinstance (portfolio_snapshot , PortfolioSnapshot ):
395+ raw_total_equity = portfolio_snapshot .total_equity
396+ else :
397+ return None
398+ except Exception :
399+ return None
400+ total_equity = _finite_number (raw_total_equity )
401+ if total_equity is None or total_equity <= 0.0 :
402+ return None
403+ return total_equity
404+
405+
383406def _exact_numeric_mapping (value : Any , expected : Mapping [str , float ]) -> bool :
384407 normalized = _canonical_numeric_mapping (value , maximum = 1.0 )
385408 if normalized is None or set (normalized ) != set (expected ):
@@ -1291,6 +1314,7 @@ def _apply_risk_gate_static(
12911314 max_single_weight : Any ,
12921315 max_positions : Any ,
12931316 max_total_exposure : Any ,
1317+ portfolio_snapshot : Any ,
12941318 engine_action : Any ,
12951319 engine_failed : bool ,
12961320) -> StrategyDecision :
@@ -1464,13 +1488,12 @@ def _apply_risk_gate_static(
14641488 )
14651489
14661490 positions = raw_positions if type (raw_positions ) is tuple else ()
1491+ value_target_total_equity = _static_gate_total_equity (portfolio_snapshot )
14671492 weights : list [tuple [PositionTarget , float ]] = []
14681493 if static_rejection is None :
14691494 for position in positions :
14701495 raw_weight = position .target_weight
1471- if raw_weight is None :
1472- weight = 0.0
1473- else :
1496+ if raw_weight is not None :
14741497 normalized_weight = _finite_number (raw_weight )
14751498 if normalized_weight is None :
14761499 if type (raw_weight ) is str :
@@ -1485,6 +1508,22 @@ def _apply_risk_gate_static(
14851508 )
14861509 break
14871510 weight = abs (normalized_weight )
1511+ else :
1512+ target_value = _finite_number (position .target_value )
1513+ if target_value is None or value_target_total_equity is None :
1514+ static_rejection = (
1515+ "rejected:invalid_decision_exposure" ,
1516+ "金额目标缺少有效账户净值" ,
1517+ )
1518+ break
1519+ normalized_weight = target_value / value_target_total_equity
1520+ if not math .isfinite (normalized_weight ) or normalized_weight < 0.0 :
1521+ static_rejection = (
1522+ "rejected:invalid_decision_exposure" ,
1523+ f"{ position .symbol } 金额目标无效" ,
1524+ )
1525+ break
1526+ weight = normalized_weight
14881527 if weight > 0.0 :
14891528 weights .append ((position , weight ))
14901529
@@ -1660,6 +1699,7 @@ def apply_risk_gate(
16601699 max_single_weight = max_single_weight ,
16611700 max_positions = max_positions ,
16621701 max_total_exposure = max_total_exposure ,
1702+ portfolio_snapshot = portfolio_snapshot ,
16631703 engine_action = engine_action ,
16641704 engine_failed = engine_failed ,
16651705 )
0 commit comments