From d2cf0211946ef2a867f773a6c5eecead9d250f1c Mon Sep 17 00:00:00 2001 From: boris Date: Mon, 7 Sep 2026 03:29:08 +0800 Subject: [PATCH] =?UTF-8?q?Revert=20"=E7=BB=9F=E4=B8=80=E6=AD=A2=E7=9B=88?= =?UTF-8?q?=E9=80=80=E5=87=BA=E5=90=8E=E7=9A=84=E7=9B=AE=E6=A0=87=E6=9D=83?= =?UTF-8?q?=E9=87=8D=E9=87=8D=E5=88=86=E9=85=8D"?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This reverts commit 3f14d9de542f5ec916419d617074235b1a7ab390. --- .../fidc-core/src/platform_expr_strategy.rs | 240 +----------------- 1 file changed, 12 insertions(+), 228 deletions(-) diff --git a/crates/fidc-core/src/platform_expr_strategy.rs b/crates/fidc-core/src/platform_expr_strategy.rs index fcaae50..bb6de7b 100644 --- a/crates/fidc-core/src/platform_expr_strategy.rs +++ b/crates/fidc-core/src/platform_expr_strategy.rs @@ -178,121 +178,6 @@ impl PlatformPortfolioDrawdownController { } } -fn ordered_weight_bps_from_scales( - scales: &[(String, f64)], -) -> Result, BacktestError> { - if scales.is_empty() { - return Ok(Vec::new()); - } - if scales - .iter() - .any(|(_, scale)| !scale.is_finite() || *scale < 0.0) - { - return Err(BacktestError::Execution( - "target portfolio buy scale must be finite and non-negative".to_string(), - )); - } - let scale_total = scales.iter().map(|(_, scale)| *scale).sum::(); - if !scale_total.is_finite() || scale_total <= 0.0 { - return Err(BacktestError::Execution( - "target portfolio buy scale total must be positive".to_string(), - )); - } - let mut weights = scales - .iter() - .map(|(symbol, scale)| { - ( - symbol.clone(), - ((*scale / scale_total * 10_000.0) + 1e-9).floor() as u32, - ) - }) - .collect::>(); - let assigned = weights.iter().map(|(_, weight)| *weight).sum::(); - let remainder = 10_000_u32.saturating_sub(assigned); - let weight_count = weights.len(); - for index in 0..remainder as usize { - weights[index % weight_count].1 += 1; - } - Ok(weights) -} - -fn replenish_target_weight_bps( - original_weights: &[(String, u32)], - candidate_symbols: &[String], - excluded_symbols: &BTreeSet, - target_count: usize, -) -> Vec<(String, u32)> { - let original_by_symbol = original_weights.iter().cloned().collect::>(); - let mut active = original_weights - .iter() - .filter(|(symbol, weight)| *weight > 0 && !excluded_symbols.contains(symbol)) - .map(|(symbol, _)| symbol.clone()) - .collect::>(); - let mut seen = active.iter().cloned().collect::>(); - let mut promoted = Vec::new(); - for symbol in candidate_symbols { - if active.len() >= target_count { - break; - } - if excluded_symbols.contains(symbol) || !seen.insert(symbol.clone()) { - continue; - } - active.push(symbol.clone()); - promoted.push(symbol.clone()); - } - if active.is_empty() { - return Vec::new(); - } - - let mut result = active - .iter() - .map(|symbol| { - ( - symbol.clone(), - *original_by_symbol.get(symbol).unwrap_or(&0), - ) - }) - .collect::>(); - let assigned = result.iter().map(|(_, weight)| *weight).sum::(); - let missing = 10_000_u32.saturating_sub(assigned); - if missing == 0 { - return result; - } - let recipients = if promoted.is_empty() { - active - } else { - promoted - }; - let base_total = recipients - .iter() - .map(|symbol| *original_by_symbol.get(symbol).unwrap_or(&0)) - .sum::(); - let mut allocated = recipients - .iter() - .map(|symbol| { - if base_total > 0 { - (u64::from(missing) * u64::from(*original_by_symbol.get(symbol).unwrap_or(&0)) - / u64::from(base_total)) as u32 - } else { - missing / recipients.len() as u32 - } - }) - .collect::>(); - let allocated_total = allocated.iter().sum::(); - let recipient_count = allocated.len(); - for index in 0..missing.saturating_sub(allocated_total) as usize { - allocated[index % recipient_count] += 1; - } - let additions = recipients - .into_iter() - .zip(allocated) - .collect::>(); - for (symbol, weight) in &mut result { - *weight += additions.get(symbol).copied().unwrap_or(0); - } - result -} - #[derive(Debug, Clone, Copy, PartialEq, Eq)] enum SelectionRiskDeferral { None, @@ -12531,32 +12416,6 @@ impl Strategy for PlatformExprStrategy { } let stop_take_exit_signal_symbols = current_stop_take_exit_symbols.clone(); - let target_portfolio_weight_bps = - if self.config.target_portfolio_daily_enabled && selection_limit > 0 { - let mut scales = Vec::new(); - for symbol in stock_list.iter().take(selection_limit) { - let decision_stock = self.stock_state_with_factor_date( - ctx, - decision_date, - selection_factor_date, - symbol, - )?; - scales.push((symbol.clone(), self.buy_scale(ctx, &day, &decision_stock)?)); - } - let original_weights = ordered_weight_bps_from_scales(&scales)?; - let mut excluded_target_symbols = exit_symbols.clone(); - excluded_target_symbols.extend(stop_take_exit_signal_symbols.iter().cloned()); - replenish_target_weight_bps( - &original_weights, - &stock_list, - &excluded_target_symbols, - selection_limit, - ) - .into_iter() - .collect::>() - } else { - BTreeMap::new() - }; if self.config.rotation_enabled && self.config.daily_position_target_adjust_enabled @@ -12598,24 +12457,9 @@ impl Strategy for PlatformExprStrategy { &position.symbol, )?; let stock_scale = self.buy_scale(ctx, &day, &decision_stock)?; - let target_value = if self.config.target_portfolio_daily_enabled { - target_portfolio_weight_bps - .get(&position.symbol) - .map(|weight_bps| { - strategy_visible_total_value - * trading_ratio - * f64::from(*weight_bps) - / 10_000.0 - }) - .unwrap_or( - strategy_visible_total_value * trading_ratio - / selection_limit as f64 - * stock_scale, - ) - } else { - strategy_visible_total_value * trading_ratio / selection_limit as f64 - * stock_scale - }; + let target_value = strategy_visible_total_value * trading_ratio + / selection_limit as f64 + * stock_scale; if !target_value.is_finite() || target_value <= 0.0 { continue; } @@ -12714,24 +12558,9 @@ impl Strategy for PlatformExprStrategy { &symbol, )?; let stock_scale = self.buy_scale(ctx, &day, &decision_stock)?; - let target_value = if self.config.target_portfolio_daily_enabled { - target_portfolio_weight_bps - .get(&symbol) - .map(|weight_bps| { - strategy_visible_total_value - * trading_ratio - * f64::from(*weight_bps) - / 10_000.0 - }) - .unwrap_or( - strategy_visible_total_value * trading_ratio - / selection_limit as f64 - * stock_scale, - ) - } else { - strategy_visible_total_value * trading_ratio / selection_limit as f64 - * stock_scale - }; + let target_value = strategy_visible_total_value * trading_ratio + / selection_limit as f64 + * stock_scale; if !target_value.is_finite() || target_value <= 0.0 { continue; } @@ -13170,14 +12999,9 @@ impl Strategy for PlatformExprStrategy { &symbol, )?; let stock_scale = self.buy_scale(ctx, &day, &decision_stock)?; - let target_value = if let Some(weight_bps) = - target_portfolio_weight_bps.get(&symbol) - { - strategy_visible_total_value * trading_ratio * f64::from(*weight_bps) / 10_000.0 - } else { - strategy_visible_total_value * trading_ratio / selection_limit as f64 - * stock_scale - }; + let target_value = strategy_visible_total_value * trading_ratio + / selection_limit as f64 + * stock_scale; if !target_value.is_finite() || target_value <= 0.0 { continue; } @@ -13342,10 +13166,7 @@ impl Strategy for PlatformExprStrategy { if !rebalance_existing_positions { continue; } - let target_value = target_portfolio_weight_bps - .get(symbol) - .map(|weight_bps| target_budget * f64::from(*weight_bps) / 10_000.0) - .unwrap_or(target_budget / selection_limit as f64 * stock_scale); + let target_value = target_budget / selection_limit as f64 * stock_scale; let before_qty = projected .position(symbol) .map(|position| position.quantity) @@ -13397,10 +13218,7 @@ impl Strategy for PlatformExprStrategy { { continue; } - let target_value = target_portfolio_weight_bps - .get(symbol) - .map(|weight_bps| target_budget * f64::from(*weight_bps) / 10_000.0) - .unwrap_or(fixed_buy_cash * stock_scale); + let target_value = fixed_buy_cash * stock_scale; if target_value <= 0.0 { continue; } @@ -13626,8 +13444,7 @@ mod tests { PlatformScheduleFrequency, PlatformStopTakeReferencePriceMode, PlatformTradeAction, PlatformUniverseActionKind, RuntimeHelperResolution, SelectionRiskDeferral, StockFilterQuoteUsage, StockRollingField, StockSnapshotFieldRequirements, - framework_stock_rolling_factor_requirement, ordered_weight_bps_from_scales, - replenish_target_weight_bps, scheduled_position_exposure, + framework_stock_rolling_factor_requirement, scheduled_position_exposure, }; use crate::{ AlgoOrderStyle, BenchmarkSnapshot, CandidateEligibility, CorporateAction, @@ -13643,39 +13460,6 @@ mod tests { NaiveDate::from_ymd_opt(year, month, day).expect("valid date") } - #[test] - fn target_weight_replenishment_matches_integer_bps_pool_contract() { - let scales = (0..24) - .map(|index| { - ( - format!("S{index:02}"), - if index < 16 { 1.0008 } else { 0.9984 }, - ) - }) - .collect::>(); - let original = ordered_weight_bps_from_scales(&scales).expect("weights"); - let excluded = BTreeSet::from(["S01".to_string()]); - let candidates = scales - .iter() - .map(|(symbol, _)| symbol.clone()) - .collect::>(); - - let replenished = replenish_target_weight_bps(&original, &candidates, &excluded, 24); - let by_symbol = replenished.iter().cloned().collect::>(); - - assert_eq!(replenished.len(), 23); - assert_eq!( - replenished.iter().map(|(_, weight)| *weight).sum::(), - 10_000 - ); - assert_eq!(by_symbol.get("S00"), Some(&436)); - assert_eq!(by_symbol.get("S02"), Some(&436)); - assert_eq!(by_symbol.get("S03"), Some(&436)); - assert_eq!(by_symbol.get("S04"), Some(&435)); - assert_eq!(by_symbol.get("S16"), Some(&434)); - assert!(!by_symbol.contains_key("S01")); - } - fn single_symbol_platform_data(dates: &[NaiveDate], symbol: &str) -> DataSet { DataSet::from_components( vec![Instrument {