perf: specialize stock snapshot sources

This commit is contained in:
boris
2026-09-05 03:32:38 +08:00
parent 224adf70d4
commit d79678d850
+159 -42
View File
@@ -9,8 +9,8 @@ use rhai::{AST, Dynamic, Engine, Map, Scope};
use crate::broker::{MatchingType, RebalanceCashMode, SlippageModel};
use crate::cost::ChinaAShareCostModel;
use crate::data::{
DailyMarketSnapshot, EligibleUniverseSnapshot, PriceField, decision_free_float_cap_bn,
decision_market_cap_bn,
CandidateEligibility, DailyFactorSnapshot, DailyMarketSnapshot, DailySnapshotView, DataSet,
EligibleUniverseSnapshot, PriceField, decision_free_float_cap_bn, decision_market_cap_bn,
};
use crate::engine::BacktestError;
use crate::events::OrderSide;
@@ -723,6 +723,71 @@ struct StockExpressionState {
extra_text_factors: BTreeMap<String, String>,
}
trait StockStateSnapshotSource<'a> {
fn execution_market(&self, symbol_id: u32) -> Option<&'a DailyMarketSnapshot>;
fn execution_candidate(&self, symbol_id: u32) -> Option<&'a CandidateEligibility>;
fn feature_market(&self, symbol_id: u32) -> Option<&'a DailyMarketSnapshot>;
fn factor(&self, symbol_id: u32) -> Option<&'a DailyFactorSnapshot>;
}
struct IndexedStockStateSnapshotSource<'a> {
data: &'a DataSet,
date: NaiveDate,
factor_date: NaiveDate,
}
impl<'a> StockStateSnapshotSource<'a> for IndexedStockStateSnapshotSource<'a> {
fn execution_market(&self, symbol_id: u32) -> Option<&'a DailyMarketSnapshot> {
self.data.market_by_symbol_id(self.date, symbol_id)
}
fn execution_candidate(&self, symbol_id: u32) -> Option<&'a CandidateEligibility> {
self.data.candidate_by_symbol_id(self.date, symbol_id)
}
fn feature_market(&self, symbol_id: u32) -> Option<&'a DailyMarketSnapshot> {
if self.factor_date == self.date {
return self.execution_market(symbol_id);
}
self.data
.market_by_symbol_id(self.factor_date, symbol_id)
.or_else(|| self.execution_market(symbol_id))
}
fn factor(&self, symbol_id: u32) -> Option<&'a DailyFactorSnapshot> {
self.data.factor_by_symbol_id(self.factor_date, symbol_id)
}
}
struct ViewStockStateSnapshotSource<'a, 'view> {
execution: &'view DailySnapshotView<'a>,
factor: &'view DailySnapshotView<'a>,
same_date: bool,
}
impl<'a> StockStateSnapshotSource<'a> for ViewStockStateSnapshotSource<'a, '_> {
fn execution_market(&self, symbol_id: u32) -> Option<&'a DailyMarketSnapshot> {
self.execution.market(symbol_id)
}
fn execution_candidate(&self, symbol_id: u32) -> Option<&'a CandidateEligibility> {
self.execution.candidate(symbol_id)
}
fn feature_market(&self, symbol_id: u32) -> Option<&'a DailyMarketSnapshot> {
if self.same_date {
return self.execution_market(symbol_id);
}
self.factor
.market(symbol_id)
.or_else(|| self.execution_market(symbol_id))
}
fn factor(&self, symbol_id: u32) -> Option<&'a DailyFactorSnapshot> {
self.factor.factor(symbol_id)
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
enum StockFilterQuoteUsage {
DailyOnly,
@@ -3897,6 +3962,7 @@ impl PlatformExprStrategy {
self.stock_state_with_factor_date_and_time(ctx, date, factor_date, symbol, None, true)
}
#[cfg(test)]
fn selection_stock_state_with_factor_date(
&self,
ctx: &StrategyContext<'_>,
@@ -3915,6 +3981,32 @@ impl PlatformExprStrategy {
)
}
fn selection_stock_state_with_factor_date_from_views<'a>(
&self,
ctx: &StrategyContext<'a>,
date: NaiveDate,
factor_date: NaiveDate,
symbol: &str,
execution_day: &DailySnapshotView<'a>,
factor_day: &DailySnapshotView<'a>,
) -> Result<Arc<StockExpressionState>, BacktestError> {
let source = ViewStockStateSnapshotSource {
execution: execution_day,
factor: factor_day,
same_date: factor_date == date,
};
let use_intraday_quote = self.selection_quote_usage != StockFilterQuoteUsage::DailyOnly;
self.stock_state_with_factor_date_and_time_from_source(
ctx,
date,
factor_date,
symbol,
None,
use_intraday_quote,
&source,
)
}
fn stock_decision_rolling_mean(
&self,
ctx: &StrategyContext<'_>,
@@ -4020,6 +4112,36 @@ impl PlatformExprStrategy {
execution_time: Option<NaiveTime>,
use_intraday_quote: bool,
) -> Result<Arc<StockExpressionState>, BacktestError> {
let source = IndexedStockStateSnapshotSource {
data: ctx.data,
date,
factor_date,
};
self.stock_state_with_factor_date_and_time_from_source(
ctx,
date,
factor_date,
symbol,
execution_time,
use_intraday_quote,
&source,
)
}
#[allow(clippy::too_many_arguments)]
fn stock_state_with_factor_date_and_time_from_source<'a, S>(
&self,
ctx: &StrategyContext<'a>,
date: NaiveDate,
factor_date: NaiveDate,
symbol: &str,
execution_time: Option<NaiveTime>,
use_intraday_quote: bool,
source: &S,
) -> Result<Arc<StockExpressionState>, BacktestError>
where
S: StockStateSnapshotSource<'a>,
{
let symbol_id = ctx.data.symbol_id(symbol).ok_or_else(|| {
BacktestError::Data(crate::data::DataSetError::MissingSnapshot {
kind: "symbol_index",
@@ -4048,43 +4170,28 @@ impl PlatformExprStrategy {
return Ok(Arc::clone(state));
}
let market = ctx
.data
.market_by_symbol_id(date, symbol_id)
.ok_or_else(|| {
BacktestError::Data(crate::data::DataSetError::MissingSnapshot {
kind: "market",
date,
symbol: symbol.to_string(),
})
})?;
let candidate = ctx
.data
.candidate_by_symbol_id(date, symbol_id)
.ok_or_else(|| {
BacktestError::Data(crate::data::DataSetError::MissingSnapshot {
kind: "candidate",
date,
symbol: symbol.to_string(),
})
})?;
let feature_market = if factor_date == date {
market
} else {
ctx.data
.market_by_symbol_id(factor_date, symbol_id)
.unwrap_or(market)
};
let factor = ctx
.data
.factor_by_symbol_id(factor_date, symbol_id)
.ok_or_else(|| {
BacktestError::Data(crate::data::DataSetError::MissingSnapshot {
kind: "factor",
date: factor_date,
symbol: symbol.to_string(),
})
})?;
let market = source.execution_market(symbol_id).ok_or_else(|| {
BacktestError::Data(crate::data::DataSetError::MissingSnapshot {
kind: "market",
date,
symbol: symbol.to_string(),
})
})?;
let candidate = source.execution_candidate(symbol_id).ok_or_else(|| {
BacktestError::Data(crate::data::DataSetError::MissingSnapshot {
kind: "candidate",
date,
symbol: symbol.to_string(),
})
})?;
let feature_market = source.feature_market(symbol_id).unwrap_or(market);
let factor = source.factor(symbol_id).ok_or_else(|| {
BacktestError::Data(crate::data::DataSetError::MissingSnapshot {
kind: "factor",
date: factor_date,
symbol: symbol.to_string(),
})
})?;
let intraday_same_day_factor = self.uses_intraday_execution_quotes()
&& factor_date == date
&& !ctx.is_lagged_execution();
@@ -9874,6 +9981,8 @@ impl PlatformExprStrategy {
universe_factor_date,
5,
);
let execution_day = ctx.data.daily_snapshot_view(date);
let factor_day = ctx.data.daily_snapshot_view(stock_factor_date);
// The universe is already stably ordered by market cap. When the
// strategy asks for that exact ascending order and does not need a
@@ -9882,11 +9991,13 @@ impl PlatformExprStrategy {
if self.rank_reuses_market_cap_order() && self.config.daily_replacement_limit == 0 {
let mut selected = Vec::with_capacity(limit.min(universe.len()));
for candidate in universe {
let stock = self.selection_stock_state_with_factor_date(
let stock = self.selection_stock_state_with_factor_date_from_views(
ctx,
date,
stock_factor_date,
&candidate.symbol,
&execution_day,
&factor_day,
)?;
let field_value = self.selection_field_value(&candidate, &stock);
if !field_value.is_finite() || field_value < band_low || field_value > band_high {
@@ -9913,11 +10024,13 @@ impl PlatformExprStrategy {
let mut missing_rank_count = 0usize;
let mut missing_rank_examples = Vec::new();
for candidate in universe {
let stock = self.selection_stock_state_with_factor_date(
let stock = self.selection_stock_state_with_factor_date_from_views(
ctx,
date,
stock_factor_date,
&candidate.symbol,
&execution_day,
&factor_day,
)?;
let field_value = self.selection_field_value(&candidate, &stock);
if !field_value.is_finite() {
@@ -10635,13 +10748,17 @@ impl PlatformExprStrategy {
universe_factor_date,
selection_risk_deferral,
);
let execution_day = ctx.data.daily_snapshot_view(date);
let factor_day = ctx.data.daily_snapshot_view(stock_factor_date);
let quote_candidate_limit = self.quote_plan_candidate_limit(selection_limit);
for candidate in universe {
let stock = self.selection_stock_state_with_factor_date(
let stock = self.selection_stock_state_with_factor_date_from_views(
ctx,
date,
stock_factor_date,
&candidate.symbol,
&execution_day,
&factor_day,
)?;
let field_value = self.selection_field_value(&candidate, &stock);
if !field_value.is_finite() {