diff --git a/crates/fidc-core/src/data.rs b/crates/fidc-core/src/data.rs index bd6432a..09ee95e 100644 --- a/crates/fidc-core/src/data.rs +++ b/crates/fidc-core/src/data.rs @@ -487,6 +487,7 @@ struct SymbolPriceSeries { last_prefix: Vec, valid_volume_sum_prefix: Vec, valid_volume_count_prefix: Vec, + valid_volume_start_by_count: Vec, } #[derive(Debug, Clone)] @@ -706,6 +707,14 @@ impl SymbolPriceSeries { + usize::from(valid), ); } + let valid_volume_count = valid_volume_count_prefix + .last() + .copied() + .unwrap_or_default(); + let mut valid_volume_start_by_count = vec![0usize; valid_volume_count + 1]; + for (index, count) in valid_volume_count_prefix.iter().copied().enumerate() { + valid_volume_start_by_count[count] = index; + } Self { symbol, @@ -735,6 +744,7 @@ impl SymbolPriceSeries { last_prefix, valid_volume_sum_prefix, valid_volume_count_prefix, + valid_volume_start_by_count, } } @@ -883,9 +893,8 @@ impl SymbolPriceSeries { return None; } let target_count = valid_count - lookback; - let start = self.valid_volume_count_prefix[..=end] - .partition_point(|count| *count <= target_count) - .saturating_sub(1); + let start = *self.valid_volume_start_by_count.get(target_count)?; + debug_assert!(start <= end); Some((start, end)) } @@ -2326,6 +2335,20 @@ impl DataSet { .unwrap_or_default() } + pub fn factor_snapshot_rows_on(&self, date: NaiveDate) -> &[Arc] { + self.factor_by_date + .get(&date) + .map(Vec::as_slice) + .unwrap_or(&[]) + } + + pub fn factor_symbol_ids_on(&self, date: NaiveDate) -> &[u32] { + self.factor_symbol_ids_by_date + .get(&date) + .map(Vec::as_slice) + .unwrap_or(&[]) + } + pub fn factor_text_snapshots_on(&self, date: NaiveDate) -> Vec<&FactorTextValue> { self.factor_text_by_date .get(&date) diff --git a/crates/fidc-core/src/platform_expr_strategy.rs b/crates/fidc-core/src/platform_expr_strategy.rs index decb883..070a3bd 100644 --- a/crates/fidc-core/src/platform_expr_strategy.rs +++ b/crates/fidc-core/src/platform_expr_strategy.rs @@ -7951,7 +7951,10 @@ impl PlatformExprStrategy { selection_risk_deferral: SelectionRiskDeferral, ) -> Vec { let mut rows = Vec::new(); - for factor in ctx.data.factor_snapshots_on(factor_date) { + let factor_rows = ctx.data.factor_snapshot_rows_on(factor_date); + let factor_symbol_ids = ctx.data.factor_symbol_ids_on(factor_date); + debug_assert_eq!(factor_rows.len(), factor_symbol_ids.len()); + for (factor, symbol_id) in factor_rows.iter().zip(factor_symbol_ids.iter().copied()) { if factor.market_cap_bn <= 0.0 || !factor.market_cap_bn.is_finite() { continue; } @@ -7959,14 +7962,15 @@ impl PlatformExprStrategy { continue; } let synthetic_candidate; - let candidate = if let Some(candidate) = ctx.data.candidate(date, &factor.symbol) { - candidate - } else { - synthetic_candidate = - crate::data::missing_candidate_risk_state(date, &factor.symbol); - &synthetic_candidate - }; - let Some(market) = ctx.data.market(date, &factor.symbol) else { + let candidate = + if let Some(candidate) = ctx.data.candidate_by_symbol_id(date, symbol_id) { + candidate + } else { + synthetic_candidate = + crate::data::missing_candidate_risk_state(date, &factor.symbol); + &synthetic_candidate + }; + let Some(market) = ctx.data.market_by_symbol_id(date, symbol_id) else { continue; }; if let Some(_reason) = @@ -8021,19 +8025,23 @@ impl PlatformExprStrategy { selection_risk_deferral: SelectionRiskDeferral, ) -> Vec { let mut decisions = Vec::new(); - for factor in ctx.data.factor_snapshots_on(factor_date) { + let factor_rows = ctx.data.factor_snapshot_rows_on(factor_date); + let factor_symbol_ids = ctx.data.factor_symbol_ids_on(factor_date); + debug_assert_eq!(factor_rows.len(), factor_symbol_ids.len()); + for (factor, symbol_id) in factor_rows.iter().zip(factor_symbol_ids.iter().copied()) { if ctx.has_dynamic_universe() && !ctx.dynamic_universe_contains(&factor.symbol) { continue; } let synthetic_candidate; - let candidate = if let Some(candidate) = ctx.data.candidate(date, &factor.symbol) { - candidate - } else { - synthetic_candidate = - crate::data::missing_candidate_risk_state(date, &factor.symbol); - &synthetic_candidate - }; - let Some(market) = ctx.data.market(date, &factor.symbol) else { + let candidate = + if let Some(candidate) = ctx.data.candidate_by_symbol_id(date, symbol_id) { + candidate + } else { + synthetic_candidate = + crate::data::missing_candidate_risk_state(date, &factor.symbol); + &synthetic_candidate + }; + let Some(market) = ctx.data.market_by_symbol_id(date, symbol_id) else { continue; }; if let Some(decision) = ChinaAShareRiskControl::selection_rejection_decision_with_config(