From c55caaf79b257cbcbf514238cfe78edf141ac753 Mon Sep 17 00:00:00 2001 From: boris Date: Sat, 29 Aug 2026 06:01:02 +0800 Subject: [PATCH] =?UTF-8?q?=E4=BC=98=E5=8C=96=E7=AD=89=E4=BB=B7=E5=B8=82?= =?UTF-8?q?=E5=80=BC=E6=8E=92=E5=BA=8F=E7=9A=84=E9=80=89=E8=82=A1=E8=B7=AF?= =?UTF-8?q?=E5=BE=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../fidc-core/src/platform_expr_strategy.rs | 69 ++++++++++++++----- 1 file changed, 51 insertions(+), 18 deletions(-) diff --git a/crates/fidc-core/src/platform_expr_strategy.rs b/crates/fidc-core/src/platform_expr_strategy.rs index 20fcabf..0b9a982 100644 --- a/crates/fidc-core/src/platform_expr_strategy.rs +++ b/crates/fidc-core/src/platform_expr_strategy.rs @@ -9219,6 +9219,15 @@ impl PlatformExprStrategy { .unwrap_or_else(|| self.field_value(candidate))) } + fn rank_reuses_market_cap_order(&self) -> bool { + self.config.rank_expr.trim().is_empty() + && !self.config.rank_desc + && matches!( + self.config.rank_by.trim(), + "market_cap" | "market_cap_bn" + ) + } + fn can_sell_position(&self, ctx: &StrategyContext<'_>, date: NaiveDate, symbol: &str) -> bool { self.can_sell_position_at_time(ctx, date, symbol, None) } @@ -9429,24 +9438,26 @@ impl PlatformExprStrategy { } candidates.push((candidate, stock, rank_value)); } - candidates.sort_by(|lhs, rhs| { - let lhs_value = lhs.2; - let rhs_value = rhs.2; - let ordering = if self.config.rank_desc { - rhs_value - .partial_cmp(&lhs_value) - .unwrap_or(std::cmp::Ordering::Equal) - } else { - lhs_value - .partial_cmp(&rhs_value) - .unwrap_or(std::cmp::Ordering::Equal) - }; - if ordering == std::cmp::Ordering::Equal { - lhs.0.symbol.cmp(&rhs.0.symbol) - } else { - ordering - } - }); + if !self.rank_reuses_market_cap_order() { + candidates.sort_by(|lhs, rhs| { + let lhs_value = lhs.2; + let rhs_value = rhs.2; + let ordering = if self.config.rank_desc { + rhs_value + .partial_cmp(&lhs_value) + .unwrap_or(std::cmp::Ordering::Equal) + } else { + lhs_value + .partial_cmp(&rhs_value) + .unwrap_or(std::cmp::Ordering::Equal) + }; + if ordering == std::cmp::Ordering::Equal { + lhs.0.symbol.cmp(&rhs.0.symbol) + } else { + ordering + } + }); + } let mut selected = Vec::new(); for (candidate, stock, _) in candidates { @@ -13749,6 +13760,28 @@ mod tests { ); } + #[test] + fn market_cap_ascending_rank_reuses_only_equivalent_universe_order() { + let mut cfg = PlatformExprStrategyConfig::microcap_rotation(); + cfg.rank_by = "market_cap".to_string(); + let strategy = PlatformExprStrategy::new(cfg.clone()); + assert!(strategy.rank_reuses_market_cap_order()); + + cfg.rank_by = "market_cap_bn".to_string(); + assert!(PlatformExprStrategy::new(cfg.clone()).rank_reuses_market_cap_order()); + + cfg.rank_desc = true; + assert!(!PlatformExprStrategy::new(cfg.clone()).rank_reuses_market_cap_order()); + + cfg.rank_desc = false; + cfg.rank_expr = "close".to_string(); + assert!(!PlatformExprStrategy::new(cfg.clone()).rank_reuses_market_cap_order()); + + cfg.rank_expr.clear(); + cfg.rank_by = "free_float_cap".to_string(); + assert!(!PlatformExprStrategy::new(cfg).rank_reuses_market_cap_order()); + } + #[test] fn current_rolling_helpers_do_not_load_factor_maps_or_decision_rollings() { let mut cfg = PlatformExprStrategyConfig::microcap_rotation();