diff --git a/crates/fidc-core/src/platform_expr_strategy.rs b/crates/fidc-core/src/platform_expr_strategy.rs index 6c455fd..ce73dbe 100644 --- a/crates/fidc-core/src/platform_expr_strategy.rs +++ b/crates/fidc-core/src/platform_expr_strategy.rs @@ -986,25 +986,6 @@ impl PlatformExprStrategy { .collect() } - fn reserve_unresolved_delisted_slots( - ranked_selection: Vec, - unresolved_symbols: &BTreeSet, - selection_limit: usize, - ) -> Vec { - if selection_limit == 0 || unresolved_symbols.is_empty() { - return ranked_selection; - } - let reserved_count = unresolved_symbols.len().min(selection_limit); - let active_limit = selection_limit.saturating_sub(reserved_count); - let mut selected = ranked_selection - .into_iter() - .filter(|symbol| !unresolved_symbols.contains(symbol)) - .take(active_limit) - .collect::>(); - selected.extend(unresolved_symbols.iter().take(reserved_count).cloned()); - selected - } - fn effective_rebalance_cash_mode(&self) -> RebalanceCashMode { if self.config.matching_type == MatchingType::MinuteLast { RebalanceCashMode::SellThenBuy @@ -9262,7 +9243,7 @@ impl Strategy for PlatformExprStrategy { let mut risk_decisions = Vec::new(); let mut replacement_reference_source = "disabled"; let mut replacement_reference_count = 0usize; - let mut stock_list = if self.config.rotation_enabled && !in_skip_window { + let stock_list = if self.config.rotation_enabled && !in_skip_window { let selection_buffer_rank = self.selection_candidate_limit(ctx, &day, selection_limit)?; let ranked_selection_limit = if self.config.daily_replacement_limit > 0 { @@ -9319,14 +9300,9 @@ impl Strategy for PlatformExprStrategy { }; let unresolved_delisted_symbols = Self::unresolved_delisted_position_symbols(ctx); if !unresolved_delisted_symbols.is_empty() { - stock_list = Self::reserve_unresolved_delisted_slots( - stock_list, - &unresolved_delisted_symbols, - selection_limit, - ); selection_notes.push(format!( - "unresolved_delisted_slots reserved={} symbols={} settlement_action=missing valuation_policy=zero no_order=true", - unresolved_delisted_symbols.len().min(selection_limit), + "unresolved_delisted_actual_slots reserved={} symbols={} settlement_action=missing valuation_policy=zero no_order=true model_targets_unchanged=true", + unresolved_delisted_symbols.len(), unresolved_delisted_symbols .iter() .cloned() @@ -11091,40 +11067,6 @@ mod tests { ); } - #[test] - fn unresolved_delisted_position_reserves_target_slot_without_replacement() { - let ranked = vec![ - "000001.SZ".to_string(), - "000002.SZ".to_string(), - "000003.SZ".to_string(), - "000004.SZ".to_string(), - ]; - let unresolved = BTreeSet::from(["000999.SZ".to_string()]); - - let selected = - PlatformExprStrategy::reserve_unresolved_delisted_slots(ranked, &unresolved, 3); - - assert_eq!( - selected, - vec![ - "000001.SZ".to_string(), - "000002.SZ".to_string(), - "000999.SZ".to_string(), - ] - ); - } - - #[test] - fn unresolved_delisted_positions_exhaust_target_slots_before_new_candidates() { - let ranked = vec!["000001.SZ".to_string(), "000002.SZ".to_string()]; - let unresolved = BTreeSet::from(["000998.SZ".to_string(), "000999.SZ".to_string()]); - - let selected = - PlatformExprStrategy::reserve_unresolved_delisted_slots(ranked, &unresolved, 1); - - assert_eq!(selected, vec!["000998.SZ".to_string()]); - } - #[test] fn platform_rebalance_keeps_unresolved_delisted_position_without_orders_or_replacement() { let previous_date = d(2025, 1, 2); @@ -11282,10 +11224,17 @@ mod tests { let decision = strategy.on_day(&ctx).expect("platform decision"); assert!(decision.diagnostics.iter().any(|note| { - note.contains("unresolved_delisted_slots reserved=1") + note.contains("unresolved_delisted_actual_slots reserved=1") && note.contains(unresolved_symbol) && note.contains("no_order=true") + && note.contains("model_targets_unchanged=true") })); + assert!( + decision + .diagnostics + .iter() + .any(|note| note == "selected_symbols=000001.SZ,000002.SZ") + ); assert!(!decision.order_intents.iter().any(|intent| { matches!( intent,