修复目标生命周期退出后复活

This commit is contained in:
boris
2026-09-07 04:39:16 +08:00
parent df29c8d3ec
commit fdd0dd0525
+74 -3
View File
@@ -2419,8 +2419,16 @@ impl PlatformExprStrategy {
.entry(symbol.clone()) .entry(symbol.clone())
.or_insert(0); .or_insert(0);
self.position_holding_days_last_counted self.position_holding_days_last_counted
.entry(symbol) .entry(symbol.clone())
.or_insert(signal_date); .or_insert(signal_date);
if let Some(target_order) = &mut self.last_target_order
&& !target_order.contains(&symbol)
{
target_order.push(symbol.clone());
}
if let Some(target_selection) = &mut self.last_target_selection {
target_selection.insert(symbol);
}
} }
} }
@@ -2428,6 +2436,12 @@ impl PlatformExprStrategy {
self.position_entry_dates.remove(symbol); self.position_entry_dates.remove(symbol);
self.position_holding_days.remove(symbol); self.position_holding_days.remove(symbol);
self.position_holding_days_last_counted.remove(symbol); self.position_holding_days_last_counted.remove(symbol);
if let Some(target_order) = &mut self.last_target_order {
target_order.retain(|target| target != symbol);
}
if let Some(target_selection) = &mut self.last_target_selection {
target_selection.remove(symbol);
}
} }
fn max_holding_days_exceeded(&self, symbol: &str) -> Option<i64> { fn max_holding_days_exceeded(&self, symbol: &str) -> Option<i64> {
@@ -13252,11 +13266,16 @@ impl Strategy for PlatformExprStrategy {
} }
} }
let blocked_exit_slots = exit_symbols
.iter()
.filter(|symbol| !Self::projected_position_is_flat(&projected, symbol))
.count();
let daily_target_count = selection_limit.saturating_sub(blocked_exit_slots);
let daily_target_portfolio_scales = resolved_target_scales( let daily_target_portfolio_scales = resolved_target_scales(
&original_target_scales, &original_target_scales,
&candidate_target_scales, &candidate_target_scales,
&same_day_sold_symbols, &exit_symbols,
selection_limit, daily_target_count,
self.config.redistribute_target_weights_after_exit, self.config.redistribute_target_weights_after_exit,
)?; )?;
if daily_top_up_active && self.config.target_portfolio_daily_enabled { if daily_top_up_active && self.config.target_portfolio_daily_enabled {
@@ -13785,6 +13804,58 @@ mod tests {
assert!((redistributed.iter().map(|(_, scale)| *scale).sum::<f64>() - 3.0).abs() < 1e-12); assert!((redistributed.iter().map(|(_, scale)| *scale).sum::<f64>() - 3.0).abs() < 1e-12);
} }
#[test]
fn model_target_lifecycle_removes_exits_and_appends_replacements() {
let date = d(2025, 1, 3);
let mut strategy = PlatformExprStrategy::new(PlatformExprStrategyConfig::generic());
strategy.last_target_order = Some(vec![
"KEEP".to_string(),
"EXITED".to_string(),
"RETRY".to_string(),
]);
strategy.last_target_selection = Some(BTreeSet::from([
"KEEP".to_string(),
"EXITED".to_string(),
"RETRY".to_string(),
]));
strategy
.position_entry_dates
.insert("EXITED".to_string(), date);
strategy.forget_position_entry_date("EXITED");
assert_eq!(
strategy.last_target_order.as_deref(),
Some(["KEEP".to_string(), "RETRY".to_string()].as_slice())
);
assert!(
!strategy
.last_target_selection
.as_ref()
.expect("target selection")
.contains("EXITED")
);
strategy.remember_position_entry_date("REPLACEMENT", date);
assert_eq!(
strategy.last_target_order.as_deref(),
Some(
[
"KEEP".to_string(),
"RETRY".to_string(),
"REPLACEMENT".to_string(),
]
.as_slice()
)
);
assert!(
strategy
.last_target_selection
.as_ref()
.expect("target selection")
.contains("REPLACEMENT")
);
}
fn single_symbol_platform_data(dates: &[NaiveDate], symbol: &str) -> DataSet { fn single_symbol_platform_data(dates: &[NaiveDate], symbol: &str) -> DataSet {
DataSet::from_components( DataSet::from_components(
vec![Instrument { vec![Instrument {