diff --git a/crates/fidc-core/src/data.rs b/crates/fidc-core/src/data.rs index fa13d16..09ee95e 100644 --- a/crates/fidc-core/src/data.rs +++ b/crates/fidc-core/src/data.rs @@ -78,12 +78,6 @@ pub enum PriceField { Last, } -#[derive(Debug, Clone, Copy)] -pub struct MarketRollingCursor { - current_end: usize, - decision_end: usize, -} - #[derive(Debug, Clone, Serialize, Deserialize)] pub struct DailyMarketSnapshot { #[serde(with = "date_format")] @@ -554,18 +548,14 @@ impl AdjustedCloseSeries { } fn current_moving_average(&self, date: NaiveDate, lookback: usize) -> Option { + if lookback == 0 { + return None; + } let end = match self.dates.binary_search(&date) { Ok(index) => index + 1, Err(0) => return None, Err(index) => index, }; - self.current_moving_average_at_end(end, lookback) - } - - fn current_moving_average_at_end(&self, end: usize, lookback: usize) -> Option { - if lookback == 0 || end > self.dates.len() { - return None; - } if end < lookback { return None; } @@ -587,18 +577,14 @@ impl AdjustedCloseSeries { } fn decision_moving_average(&self, date: NaiveDate, lookback: usize) -> Option { + if lookback == 0 { + return None; + } let end = match self.dates.binary_search(&date) { Ok(index) => index, Err(0) => return None, Err(index) => index, }; - self.decision_moving_average_at_end(end, lookback) - } - - fn decision_moving_average_at_end(&self, end: usize, lookback: usize) -> Option { - if lookback == 0 || end > self.dates.len() { - return None; - } if end < lookback { return None; } @@ -868,10 +854,6 @@ impl SymbolPriceSeries { fn decision_volume_moving_average(&self, date: NaiveDate, lookback: usize) -> Option { let end = self.previous_completed_end_index(date)?; - self.decision_volume_moving_average_at_end(end, lookback) - } - - fn decision_volume_moving_average_at_end(&self, end: usize, lookback: usize) -> Option { self.valid_volume_window(end, lookback).map(|(start, end)| { normalize_rolling_factor( (self.valid_volume_sum_prefix[end] - self.valid_volume_sum_prefix[start]) @@ -883,10 +865,6 @@ impl SymbolPriceSeries { fn current_volume_moving_average(&self, date: NaiveDate, lookback: usize) -> Option { let end = self.end_index(date)?; - self.current_volume_moving_average_at_end(end, lookback) - } - - fn current_volume_moving_average_at_end(&self, end: usize, lookback: usize) -> Option { self.valid_volume_window(end, lookback).map(|(start, end)| { normalize_rolling_factor( (self.valid_volume_sum_prefix[end] - self.valid_volume_sum_prefix[start]) @@ -920,13 +898,6 @@ impl SymbolPriceSeries { Some((start, end)) } - fn rolling_cursor(&self, date: NaiveDate) -> Option { - Some(MarketRollingCursor { - current_end: self.end_index(date)?, - decision_end: self.previous_completed_end_index(date).unwrap_or(0), - }) - } - fn valid_volume_values(&self, end: usize, lookback: usize) -> Option> { let (start, end) = self.valid_volume_window(end, lookback)?; let values = self.volumes[start..end] @@ -1154,14 +1125,13 @@ pub struct DataSet { instruments: HashMap, calendar: TradingCalendar, market_by_date: BTreeMap>>, - market_row_index_by_date: Arc>>, + market_symbol_ids_by_date: Arc>>, factor_by_date: BTreeMap>>, - factor_row_index_by_date: Arc>>, factor_symbol_ids_by_date: Arc>>, factor_text_by_date: BTreeMap>, factor_text_index: HashMap<(NaiveDate, String, String), FactorTextValue>, candidate_by_date: BTreeMap>>, - candidate_row_index_by_date: Arc>>, + candidate_symbol_ids_by_date: Arc>>, corporate_actions_by_date: BTreeMap>, execution_quotes_by_date: HashMap>>, order_book_depth_index: HashMap<(NaiveDate, String), Vec>, @@ -1381,20 +1351,16 @@ impl DataSet { &factor_by_date, &candidate_by_date, ); - let market_row_index_by_date = - build_group_row_indices(&market_by_date, &symbol_id_by_code, |item| { - item.symbol.as_str() - }); - let factor_row_index_by_date = - build_group_row_indices(&factor_by_date, &symbol_id_by_code, |item| { + let market_symbol_ids_by_date = + build_group_symbol_ids(&market_by_date, &symbol_id_by_code, |item| { item.symbol.as_str() }); let factor_symbol_ids_by_date = build_group_symbol_ids(&factor_by_date, &symbol_id_by_code, |item| { item.symbol.as_str() }); - let candidate_row_index_by_date = - build_group_row_indices(&candidate_by_date, &symbol_id_by_code, |item| { + let candidate_symbol_ids_by_date = + build_group_symbol_ids(&candidate_by_date, &symbol_id_by_code, |item| { item.symbol.as_str() }); let mut market_series_by_symbol_id = vec![None; symbol_id_by_code.len()]; @@ -1425,14 +1391,13 @@ impl DataSet { instruments, calendar, market_by_date, - market_row_index_by_date: Arc::new(market_row_index_by_date), + market_symbol_ids_by_date: Arc::new(market_symbol_ids_by_date), factor_by_date, - factor_row_index_by_date: Arc::new(factor_row_index_by_date), factor_symbol_ids_by_date: Arc::new(factor_symbol_ids_by_date), factor_text_by_date, factor_text_index, candidate_by_date, - candidate_row_index_by_date: Arc::new(candidate_row_index_by_date), + candidate_symbol_ids_by_date: Arc::new(candidate_symbol_ids_by_date), corporate_actions_by_date, execution_quotes_by_date, order_book_depth_index, @@ -1500,9 +1465,9 @@ impl DataSet { date: NaiveDate, symbol_id: u32, ) -> Option<&DailyMarketSnapshot> { - find_arc_by_dense_row_index( + find_arc_by_symbol_id( self.market_by_date.get(&date)?, - self.market_row_index_by_date.get(&date)?, + self.market_symbol_ids_by_date.get(&date)?, symbol_id, ) } @@ -1517,15 +1482,6 @@ impl DataSet { .as_deref() } - pub fn market_rolling_cursor_by_symbol_id( - &self, - date: NaiveDate, - symbol_id: u32, - ) -> Option { - self.market_series_by_symbol_id(symbol_id)? - .rolling_cursor(date) - } - fn adjusted_close_series(&self, symbol: &str) -> Option<&AdjustedCloseSeries> { self.adjusted_close_series_by_symbol .get(symbol) @@ -1548,9 +1504,9 @@ impl DataSet { date: NaiveDate, symbol_id: u32, ) -> Option<&DailyFactorSnapshot> { - find_arc_by_dense_row_index( + find_arc_by_symbol_id( self.factor_by_date.get(&date)?, - self.factor_row_index_by_date.get(&date)?, + self.factor_symbol_ids_by_date.get(&date)?, symbol_id, ) } @@ -1565,9 +1521,9 @@ impl DataSet { date: NaiveDate, symbol_id: u32, ) -> Option<&CandidateEligibility> { - find_arc_by_dense_row_index( + find_arc_by_symbol_id( self.candidate_by_date.get(&date)?, - self.candidate_row_index_by_date.get(&date)?, + self.candidate_symbol_ids_by_date.get(&date)?, symbol_id, ) } @@ -2661,38 +2617,6 @@ impl DataSet { } } - pub fn market_decision_numeric_moving_average_at_cursor( - &self, - date: NaiveDate, - symbol_id: u32, - symbol: &str, - field: &str, - lookback: usize, - cursor: MarketRollingCursor, - ) -> Option { - let field = normalized_field(field); - match field.as_ref() { - "close" | "prev_close" | "stock_close" | "price" => self - .adjusted_close_series_by_symbol_id(symbol_id) - .and_then(|series| { - series.decision_moving_average_at_end(cursor.decision_end, lookback) - }), - "volume" | "stock_volume" => { - self.market_series_by_symbol_id(symbol_id) - .and_then(|series| { - series.decision_volume_moving_average_at_end(cursor.decision_end, lookback) - }) - } - _ => self.market_decision_numeric_moving_average_by_symbol_id( - date, - symbol_id, - symbol, - field.as_ref(), - lookback, - ), - } - } - pub fn market_current_numeric_moving_average( &self, date: NaiveDate, @@ -2748,38 +2672,6 @@ impl DataSet { } } - pub fn market_current_numeric_moving_average_at_cursor( - &self, - date: NaiveDate, - symbol_id: u32, - symbol: &str, - field: &str, - lookback: usize, - cursor: MarketRollingCursor, - ) -> Option { - let field = normalized_field(field); - match field.as_ref() { - "close" | "prev_close" | "stock_close" | "price" => self - .adjusted_close_series_by_symbol_id(symbol_id) - .and_then(|series| { - series.current_moving_average_at_end(cursor.current_end, lookback) - }), - "volume" | "stock_volume" => { - self.market_series_by_symbol_id(symbol_id) - .and_then(|series| { - series.current_volume_moving_average_at_end(cursor.current_end, lookback) - }) - } - _ => self.market_current_numeric_moving_average_by_symbol_id( - date, - symbol_id, - symbol, - field.as_ref(), - lookback, - ), - } - } - pub fn market_latest_back_adjusted_close(&self, date: NaiveDate, symbol: &str) -> Option { self.adjusted_close_series(symbol) .and_then(|series| series.latest_back_adjusted_close(date)) @@ -3468,40 +3360,17 @@ where .collect() } -fn build_group_row_indices( - groups: &BTreeMap>>, - symbol_id_by_code: &HashMap, - symbol_of: F, -) -> BTreeMap> -where - F: Fn(&T) -> &str + Copy, -{ - groups - .iter() - .map(|(date, rows)| { - let mut row_indices = vec![u32::MAX; symbol_id_by_code.len()]; - for (row_index, row) in rows.iter().enumerate() { - let symbol_id = *symbol_id_by_code - .get(symbol_of(row.as_ref())) - .expect("snapshot symbol missing from FIDC symbol index"); - let slot = &mut row_indices[symbol_id as usize]; - debug_assert_eq!(*slot, u32::MAX, "duplicate symbol in daily snapshot rows"); - *slot = u32::try_from(row_index) - .expect("daily snapshot row index exceeds u32 capacity"); - } - (*date, row_indices) - }) - .collect() -} - -fn find_arc_by_dense_row_index<'a, T>( +fn find_arc_by_symbol_id<'a, T>( rows: &'a [Arc], - row_indices: &[u32], + symbol_ids: &[u32], symbol_id: u32, ) -> Option<&'a T> { - let row_index = *row_indices.get(symbol_id as usize)?; - (row_index != u32::MAX) - .then_some(row_index as usize) + if rows.len() != symbol_ids.len() { + return None; + } + symbol_ids + .binary_search(&symbol_id) + .ok() .and_then(|index| rows.get(index)) .map(Arc::as_ref) } diff --git a/crates/fidc-core/src/platform_expr_strategy.rs b/crates/fidc-core/src/platform_expr_strategy.rs index b72771c..070a3bd 100644 --- a/crates/fidc-core/src/platform_expr_strategy.rs +++ b/crates/fidc-core/src/platform_expr_strategy.rs @@ -8,8 +8,8 @@ use rhai::{AST, Dynamic, Engine, Map, Scope}; use crate::broker::{MatchingType, RebalanceCashMode, SlippageModel}; use crate::cost::ChinaAShareCostModel; use crate::data::{ - DailyMarketSnapshot, EligibleUniverseSnapshot, MarketRollingCursor, PriceField, - decision_free_float_cap_bn, decision_market_cap_bn, + DailyMarketSnapshot, EligibleUniverseSnapshot, PriceField, decision_free_float_cap_bn, + decision_market_cap_bn, }; use crate::engine::BacktestError; use crate::events::OrderSide; @@ -616,8 +616,6 @@ struct DayExpressionState { struct StockExpressionState { symbol: String, symbol_id: u32, - rolling_date: NaiveDate, - rolling_cursor: MarketRollingCursor, market_cap: f64, market_cap_bn: f64, free_float_cap: f64, @@ -3616,21 +3614,14 @@ impl PlatformExprStrategy { ctx: &StrategyContext<'_>, date: NaiveDate, symbol_id: u32, - rolling_cursor: Option, symbol: &str, field: &str, lookback: usize, ) -> Option { - match rolling_cursor { - Some(cursor) => ctx.data.market_decision_numeric_moving_average_at_cursor( - date, symbol_id, symbol, field, lookback, cursor, - ), - None => ctx - .data - .market_decision_numeric_moving_average_by_symbol_id( - date, symbol_id, symbol, field, lookback, - ), - } + ctx.data + .market_decision_numeric_moving_average_by_symbol_id( + date, symbol_id, symbol, field, lookback, + ) } fn stock_current_rolling_mean( @@ -3638,19 +3629,13 @@ impl PlatformExprStrategy { ctx: &StrategyContext<'_>, date: NaiveDate, symbol_id: u32, - rolling_cursor: Option, symbol: &str, field: &str, lookback: usize, ) -> Option { - match rolling_cursor { - Some(cursor) => ctx.data.market_current_numeric_moving_average_at_cursor( - date, symbol_id, symbol, field, lookback, cursor, - ), - None => ctx.data.market_current_numeric_moving_average_by_symbol_id( - date, symbol_id, symbol, field, lookback, - ), - } + ctx.data.market_current_numeric_moving_average_by_symbol_id( + date, symbol_id, symbol, field, lookback, + ) } fn stock_state_at_time( @@ -3731,16 +3716,6 @@ impl PlatformExprStrategy { symbol: symbol.to_string(), }) })?; - let rolling_cursor = ctx - .data - .market_rolling_cursor_by_symbol_id(date, symbol_id) - .ok_or_else(|| { - BacktestError::Data(crate::data::DataSetError::MissingSnapshot { - kind: "market rolling cursor", - date, - symbol: symbol.to_string(), - }) - })?; { let mut cache_date = self.stock_state_cache_date.borrow_mut(); if *cache_date != Some(date) { @@ -3785,16 +3760,8 @@ impl PlatformExprStrategy { if !self.stock_rolling_requirements.requires(field, lookback) { return f64::NAN; } - self.stock_decision_rolling_mean( - ctx, - date, - symbol_id, - Some(rolling_cursor), - symbol, - field, - lookback, - ) - .unwrap_or(f64::NAN) + self.stock_decision_rolling_mean(ctx, date, symbol_id, symbol, field, lookback) + .unwrap_or(f64::NAN) }; let stock_ma_short = rolling("close", self.config.stock_short_ma_days); let stock_ma_mid = rolling("close", self.config.stock_mid_ma_days); @@ -3920,8 +3887,6 @@ impl PlatformExprStrategy { let state = StockExpressionState { symbol: symbol.to_string(), symbol_id, - rolling_date: date, - rolling_cursor, market_cap, market_cap_bn, free_float_cap, @@ -5409,7 +5374,6 @@ impl PlatformExprStrategy { ctx, day.date, stock.symbol_id, - (stock.rolling_date == day.date).then_some(stock.rolling_cursor), &stock.symbol, field, lookback, @@ -6074,7 +6038,6 @@ impl PlatformExprStrategy { ctx, day.date, stock.symbol_id, - (stock.rolling_date == day.date).then_some(stock.rolling_cursor), &stock.symbol, other, lookback, @@ -6124,7 +6087,6 @@ impl PlatformExprStrategy { ctx, day.date, stock.symbol_id, - (stock.rolling_date == day.date).then_some(stock.rolling_cursor), &stock.symbol, other, lookback,