修复目标生命周期退出后复活
This commit is contained in:
@@ -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 {
|
||||||
|
|||||||
Reference in New Issue
Block a user