fix(stock-pool): keep full stops stronger than simultaneous reductions

This commit is contained in:
boris
2026-09-12 14:21:33 +08:00
parent 848c1a514a
commit 4ac9ee5058
2 changed files with 64 additions and 5 deletions
+16 -5
View File
@@ -669,6 +669,13 @@ pub fn build_stock_pool_target_plan_with_fee_model(
}
}
}
// Validate source targets before a stronger stop/expiry can replace them.
// Otherwise an invalid ratio could be hidden by target consolidation.
for (symbol, target) in &constraints.position_target_bps {
if *target >= 10_000 {
return Err(format!("factor position target for {symbol} must be below 10000 bps"));
}
}
let mut effective_position_targets = constraints.position_target_bps.clone();
for (symbol, permission) in &constraints.automatic_permissions {
if permission.max_holding_exit {
@@ -863,6 +870,13 @@ pub fn build_stock_pool_target_plan_with_fee_model(
.then(|| symbol.clone())
})
.collect::<BTreeSet<_>>();
// A full stop is stricter than a simultaneous relative reduction. Merge
// the target before selecting its single owner, never emit a second exit.
for symbol in &global_stop_hits {
if let Some(target) = effective_position_targets.get_mut(symbol) {
*target = 0;
}
}
let mut quote_sell_exits = BTreeSet::new();
let mut sell_condition_denials = BTreeSet::new();
if rule.sell_trigger_mode == POOL_SELL_CONDITION {
@@ -1327,11 +1341,6 @@ pub fn build_stock_pool_target_plan_with_fee_model(
}
for (symbol, target_bps) in factor_position_target_bps {
if *target_bps >= 10_000 {
return Err(format!(
"factor position target for {symbol} must be below 10000 bps"
));
}
if !member_map.contains_key(symbol) && !current.contains_key(symbol) {
return Err(format!(
"factor position-action symbol {symbol} is outside candidates and managed holdings"
@@ -1418,6 +1427,8 @@ pub fn build_stock_pool_target_plan_with_fee_model(
"达到最长持有期,按配置退出"
} else if quote_sell_exits.contains(symbol) {
"卖出行情条件命中"
} else if stop_take_exits.contains(symbol) {
"止损/止盈触发,覆盖较弱的减仓目标"
} else if *target_bps == 0 {
"生产因子退出条件命中"
} else {