区分股票池退出后重新入场

This commit is contained in:
boris
2026-09-07 04:51:27 +08:00
parent 4d8761cc3c
commit b4ec5da808
2 changed files with 46 additions and 6 deletions
+38 -5
View File
@@ -653,6 +653,7 @@ pub struct PlatformExprStrategyConfig {
pub delayed_limit_open_exit_time: Option<NaiveTime>, pub delayed_limit_open_exit_time: Option<NaiveTime>,
pub release_slot_on_exit_signal: bool, pub release_slot_on_exit_signal: bool,
pub redistribute_target_weights_after_exit: bool, pub redistribute_target_weights_after_exit: bool,
pub reenter_exited_targets: bool,
pub explicit_action_stage: PlatformExplicitActionStage, pub explicit_action_stage: PlatformExplicitActionStage,
pub explicit_action_schedule: Option<PlatformRebalanceSchedule>, pub explicit_action_schedule: Option<PlatformRebalanceSchedule>,
pub subscription_guard_required: bool, pub subscription_guard_required: bool,
@@ -729,6 +730,7 @@ impl PlatformExprStrategyConfig {
delayed_limit_open_exit_time: None, delayed_limit_open_exit_time: None,
release_slot_on_exit_signal: false, release_slot_on_exit_signal: false,
redistribute_target_weights_after_exit: false, redistribute_target_weights_after_exit: false,
reenter_exited_targets: false,
explicit_action_stage: PlatformExplicitActionStage::OnDay, explicit_action_stage: PlatformExplicitActionStage::OnDay,
explicit_action_schedule: None, explicit_action_schedule: None,
subscription_guard_required: false, subscription_guard_required: false,
@@ -2436,11 +2438,13 @@ 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 { if !self.config.reenter_exited_targets {
target_order.retain(|target| target != 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); if let Some(target_selection) = &mut self.last_target_selection {
target_selection.remove(symbol);
}
} }
} }
@@ -13863,6 +13867,35 @@ mod tests {
); );
} }
#[test]
fn stock_pool_target_lifecycle_keeps_exits_for_next_day_reentry() {
let date = d(2025, 1, 3);
let mut config = PlatformExprStrategyConfig::generic();
config.reenter_exited_targets = true;
let mut strategy = PlatformExprStrategy::new(config);
strategy.last_target_order = Some(vec!["KEEP".to_string(), "EXITED".to_string()]);
strategy.last_target_selection =
Some(BTreeSet::from(["KEEP".to_string(), "EXITED".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(), "EXITED".to_string()].as_slice())
);
assert!(
strategy
.last_target_selection
.as_ref()
.expect("target selection")
.contains("EXITED")
);
assert!(!strategy.position_entry_dates.contains_key("EXITED"));
}
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 {
@@ -1022,6 +1022,8 @@ pub struct StrategyExpressionTradingConfig {
#[serde(default)] #[serde(default)]
pub redistribute_target_weights_after_exit: Option<bool>, pub redistribute_target_weights_after_exit: Option<bool>,
#[serde(default)] #[serde(default)]
pub reenter_exited_targets: Option<bool>,
#[serde(default)]
pub subscription_guard_required: Option<bool>, pub subscription_guard_required: Option<bool>,
#[serde(default)] #[serde(default)]
pub subscriptions: Vec<String>, pub subscriptions: Vec<String>,
@@ -2299,6 +2301,9 @@ pub fn platform_expr_config_from_spec(
if let Some(enabled) = trading.redistribute_target_weights_after_exit { if let Some(enabled) = trading.redistribute_target_weights_after_exit {
cfg.redistribute_target_weights_after_exit = enabled; cfg.redistribute_target_weights_after_exit = enabled;
} }
if let Some(enabled) = trading.reenter_exited_targets {
cfg.reenter_exited_targets = enabled;
}
if let Some(enabled) = trading.delayed_limit_open_exit { if let Some(enabled) = trading.delayed_limit_open_exit {
cfg.delayed_limit_open_exit_enabled = enabled; cfg.delayed_limit_open_exit_enabled = enabled;
if enabled { if enabled {
@@ -3490,7 +3495,8 @@ mod tests {
"rebalanceExistingPositions": true, "rebalanceExistingPositions": true,
"holdUntilExit": true, "holdUntilExit": true,
"releaseSlotOnExitSignal": true, "releaseSlotOnExitSignal": true,
"redistributeTargetWeightsAfterExit": true "redistributeTargetWeightsAfterExit": true,
"reenterExitedTargets": true
} }
} }
}); });
@@ -3505,6 +3511,7 @@ mod tests {
assert!(cfg.hold_until_exit_enabled); assert!(cfg.hold_until_exit_enabled);
assert!(cfg.release_slot_on_exit_signal); assert!(cfg.release_slot_on_exit_signal);
assert!(cfg.redistribute_target_weights_after_exit); assert!(cfg.redistribute_target_weights_after_exit);
assert!(cfg.reenter_exited_targets);
} }
#[test] #[test]