perf: avoid caching transient selection states

This commit is contained in:
boris
2026-09-05 04:59:50 +08:00
parent 0af4cd7f68
commit 29faf7932e
+75 -20
View File
@@ -4008,7 +4008,7 @@ impl PlatformExprStrategy {
} }
#[allow(clippy::too_many_arguments)] #[allow(clippy::too_many_arguments)]
fn selection_stock_state_with_factor_date_from_views_by_symbol_id<'a>( fn uncached_selection_stock_state_from_views_by_symbol_id<'a>(
&self, &self,
ctx: &StrategyContext<'a>, ctx: &StrategyContext<'a>,
date: NaiveDate, date: NaiveDate,
@@ -4017,14 +4017,15 @@ impl PlatformExprStrategy {
symbol: &str, symbol: &str,
execution_day: &DailySnapshotView<'a>, execution_day: &DailySnapshotView<'a>,
factor_day: &DailySnapshotView<'a>, factor_day: &DailySnapshotView<'a>,
) -> Result<Arc<StockExpressionState>, BacktestError> { ) -> Result<StockExpressionState, BacktestError> {
let source = ViewStockStateSnapshotSource { let source = ViewStockStateSnapshotSource {
execution: execution_day, execution: execution_day,
factor: factor_day, factor: factor_day,
same_date: factor_date == date, same_date: factor_date == date,
}; };
let use_intraday_quote = self.selection_quote_usage != StockFilterQuoteUsage::DailyOnly; let use_intraday_quote = self.selection_quote_usage != StockFilterQuoteUsage::DailyOnly;
self.stock_state_with_factor_date_and_time_from_source_by_symbol_id( let calendar_index = self.prepare_stock_state_cache_date(ctx, date);
self.build_stock_state_with_factor_date_and_time_from_source_by_symbol_id(
ctx, ctx,
date, date,
factor_date, factor_date,
@@ -4032,6 +4033,7 @@ impl PlatformExprStrategy {
symbol, symbol,
None, None,
use_intraday_quote, use_intraday_quote,
calendar_index,
&source, &source,
) )
} }
@@ -4205,14 +4207,36 @@ impl PlatformExprStrategy {
where where
S: StockStateSnapshotSource<'a>, S: StockStateSnapshotSource<'a>,
{ {
let shared_symbol = ctx.data.shared_symbol_by_id(symbol_id).ok_or_else(|| { let calendar_index = self.prepare_stock_state_cache_date(ctx, date);
BacktestError::Data(crate::data::DataSetError::MissingSnapshot { let cache_key = (factor_date, symbol_id, execution_time, use_intraday_quote);
kind: "symbol_reverse_index", if let Some(state) = self.stock_state_cache.borrow().get(&cache_key) {
return Ok(Arc::clone(state));
}
let state = Arc::new(
self.build_stock_state_with_factor_date_and_time_from_source_by_symbol_id(
ctx,
date, date,
symbol: symbol.to_string(), factor_date,
}) symbol_id,
})?; symbol,
let calendar_index = { execution_time,
use_intraday_quote,
calendar_index,
source,
)?,
);
self.stock_state_cache
.borrow_mut()
.insert(cache_key, Arc::clone(&state));
Ok(state)
}
fn prepare_stock_state_cache_date(
&self,
ctx: &StrategyContext<'_>,
date: NaiveDate,
) -> Option<usize> {
let mut cache_date = self.stock_state_cache_date.borrow_mut(); let mut cache_date = self.stock_state_cache_date.borrow_mut();
if *cache_date != Some(date) { if *cache_date != Some(date) {
self.stock_state_cache.borrow_mut().clear(); self.stock_state_cache.borrow_mut().clear();
@@ -4220,12 +4244,32 @@ impl PlatformExprStrategy {
*self.stock_state_cache_calendar_index.borrow_mut() = ctx.data.calendar_index(date); *self.stock_state_cache_calendar_index.borrow_mut() = ctx.data.calendar_index(date);
} }
*self.stock_state_cache_calendar_index.borrow() *self.stock_state_cache_calendar_index.borrow()
};
let cache_key = (factor_date, symbol_id, execution_time, use_intraday_quote);
if let Some(state) = self.stock_state_cache.borrow().get(&cache_key) {
return Ok(Arc::clone(state));
} }
#[allow(clippy::too_many_arguments)]
fn build_stock_state_with_factor_date_and_time_from_source_by_symbol_id<'a, S>(
&self,
ctx: &StrategyContext<'a>,
date: NaiveDate,
factor_date: NaiveDate,
symbol_id: u32,
symbol: &str,
execution_time: Option<NaiveTime>,
use_intraday_quote: bool,
calendar_index: Option<usize>,
source: &S,
) -> Result<StockExpressionState, BacktestError>
where
S: StockStateSnapshotSource<'a>,
{
let shared_symbol = ctx.data.shared_symbol_by_id(symbol_id).ok_or_else(|| {
BacktestError::Data(crate::data::DataSetError::MissingSnapshot {
kind: "symbol_reverse_index",
date,
symbol: symbol.to_string(),
})
})?;
let market = source.execution_market(symbol_id).ok_or_else(|| { let market = source.execution_market(symbol_id).ok_or_else(|| {
BacktestError::Data(crate::data::DataSetError::MissingSnapshot { BacktestError::Data(crate::data::DataSetError::MissingSnapshot {
kind: "market", kind: "market",
@@ -4406,7 +4450,7 @@ impl PlatformExprStrategy {
BTreeMap::new() BTreeMap::new()
}; };
let state = Arc::new(StockExpressionState { let state = StockExpressionState {
symbol: shared_symbol, symbol: shared_symbol,
symbol_id, symbol_id,
market_cap, market_cap,
@@ -4483,10 +4527,7 @@ impl PlatformExprStrategy {
} else { } else {
BTreeMap::new() BTreeMap::new()
}, },
}); };
self.stock_state_cache
.borrow_mut()
.insert(cache_key, Arc::clone(&state));
Ok(state) Ok(state)
} }
@@ -10115,7 +10156,7 @@ impl PlatformExprStrategy {
.factor(symbol_id) .factor(symbol_id)
.expect("market-cap order references missing factor row"); .expect("market-cap order references missing factor row");
let symbol = factor.symbol.as_str(); let symbol = factor.symbol.as_str();
let stock = self.selection_stock_state_with_factor_date_from_views_by_symbol_id( let stock = self.uncached_selection_stock_state_from_views_by_symbol_id(
ctx, ctx,
date, date,
stock_factor_date, stock_factor_date,
@@ -13101,6 +13142,20 @@ mod tests {
}; };
let strategy = PlatformExprStrategy::new(PlatformExprStrategyConfig::microcap_rotation()); let strategy = PlatformExprStrategy::new(PlatformExprStrategyConfig::microcap_rotation());
let first_context = context(dates[0], 0); let first_context = context(dates[0], 0);
let first_day = data.daily_snapshot_view(dates[0]);
let uncached = strategy
.uncached_selection_stock_state_from_views_by_symbol_id(
&first_context,
dates[0],
dates[0],
data.symbol_id(symbol).unwrap(),
symbol,
&first_day,
&first_day,
)
.expect("uncached first state");
assert_eq!(uncached.close, 10.0);
assert!(strategy.stock_state_cache.borrow().is_empty());
let first = strategy let first = strategy
.stock_state(&first_context, dates[0], symbol) .stock_state(&first_context, dates[0], symbol)
.expect("first state"); .expect("first state");