perf: specialize stock snapshot sources
This commit is contained in:
@@ -9,8 +9,8 @@ use rhai::{AST, Dynamic, Engine, Map, Scope};
|
|||||||
use crate::broker::{MatchingType, RebalanceCashMode, SlippageModel};
|
use crate::broker::{MatchingType, RebalanceCashMode, SlippageModel};
|
||||||
use crate::cost::ChinaAShareCostModel;
|
use crate::cost::ChinaAShareCostModel;
|
||||||
use crate::data::{
|
use crate::data::{
|
||||||
DailyMarketSnapshot, EligibleUniverseSnapshot, PriceField, decision_free_float_cap_bn,
|
CandidateEligibility, DailyFactorSnapshot, DailyMarketSnapshot, DailySnapshotView, DataSet,
|
||||||
decision_market_cap_bn,
|
EligibleUniverseSnapshot, PriceField, decision_free_float_cap_bn, decision_market_cap_bn,
|
||||||
};
|
};
|
||||||
use crate::engine::BacktestError;
|
use crate::engine::BacktestError;
|
||||||
use crate::events::OrderSide;
|
use crate::events::OrderSide;
|
||||||
@@ -723,6 +723,71 @@ struct StockExpressionState {
|
|||||||
extra_text_factors: BTreeMap<String, String>,
|
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)]
|
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||||
enum StockFilterQuoteUsage {
|
enum StockFilterQuoteUsage {
|
||||||
DailyOnly,
|
DailyOnly,
|
||||||
@@ -3897,6 +3962,7 @@ impl PlatformExprStrategy {
|
|||||||
self.stock_state_with_factor_date_and_time(ctx, date, factor_date, symbol, None, true)
|
self.stock_state_with_factor_date_and_time(ctx, date, factor_date, symbol, None, true)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
fn selection_stock_state_with_factor_date(
|
fn selection_stock_state_with_factor_date(
|
||||||
&self,
|
&self,
|
||||||
ctx: &StrategyContext<'_>,
|
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(
|
fn stock_decision_rolling_mean(
|
||||||
&self,
|
&self,
|
||||||
ctx: &StrategyContext<'_>,
|
ctx: &StrategyContext<'_>,
|
||||||
@@ -4020,6 +4112,36 @@ impl PlatformExprStrategy {
|
|||||||
execution_time: Option<NaiveTime>,
|
execution_time: Option<NaiveTime>,
|
||||||
use_intraday_quote: bool,
|
use_intraday_quote: bool,
|
||||||
) -> Result<Arc<StockExpressionState>, BacktestError> {
|
) -> 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(|| {
|
let symbol_id = ctx.data.symbol_id(symbol).ok_or_else(|| {
|
||||||
BacktestError::Data(crate::data::DataSetError::MissingSnapshot {
|
BacktestError::Data(crate::data::DataSetError::MissingSnapshot {
|
||||||
kind: "symbol_index",
|
kind: "symbol_index",
|
||||||
@@ -4048,37 +4170,22 @@ impl PlatformExprStrategy {
|
|||||||
return Ok(Arc::clone(state));
|
return Ok(Arc::clone(state));
|
||||||
}
|
}
|
||||||
|
|
||||||
let market = ctx
|
let market = source.execution_market(symbol_id).ok_or_else(|| {
|
||||||
.data
|
|
||||||
.market_by_symbol_id(date, symbol_id)
|
|
||||||
.ok_or_else(|| {
|
|
||||||
BacktestError::Data(crate::data::DataSetError::MissingSnapshot {
|
BacktestError::Data(crate::data::DataSetError::MissingSnapshot {
|
||||||
kind: "market",
|
kind: "market",
|
||||||
date,
|
date,
|
||||||
symbol: symbol.to_string(),
|
symbol: symbol.to_string(),
|
||||||
})
|
})
|
||||||
})?;
|
})?;
|
||||||
let candidate = ctx
|
let candidate = source.execution_candidate(symbol_id).ok_or_else(|| {
|
||||||
.data
|
|
||||||
.candidate_by_symbol_id(date, symbol_id)
|
|
||||||
.ok_or_else(|| {
|
|
||||||
BacktestError::Data(crate::data::DataSetError::MissingSnapshot {
|
BacktestError::Data(crate::data::DataSetError::MissingSnapshot {
|
||||||
kind: "candidate",
|
kind: "candidate",
|
||||||
date,
|
date,
|
||||||
symbol: symbol.to_string(),
|
symbol: symbol.to_string(),
|
||||||
})
|
})
|
||||||
})?;
|
})?;
|
||||||
let feature_market = if factor_date == date {
|
let feature_market = source.feature_market(symbol_id).unwrap_or(market);
|
||||||
market
|
let factor = source.factor(symbol_id).ok_or_else(|| {
|
||||||
} 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 {
|
BacktestError::Data(crate::data::DataSetError::MissingSnapshot {
|
||||||
kind: "factor",
|
kind: "factor",
|
||||||
date: factor_date,
|
date: factor_date,
|
||||||
@@ -9874,6 +9981,8 @@ impl PlatformExprStrategy {
|
|||||||
universe_factor_date,
|
universe_factor_date,
|
||||||
5,
|
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
|
// The universe is already stably ordered by market cap. When the
|
||||||
// strategy asks for that exact ascending order and does not need a
|
// 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 {
|
if self.rank_reuses_market_cap_order() && self.config.daily_replacement_limit == 0 {
|
||||||
let mut selected = Vec::with_capacity(limit.min(universe.len()));
|
let mut selected = Vec::with_capacity(limit.min(universe.len()));
|
||||||
for candidate in universe {
|
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,
|
ctx,
|
||||||
date,
|
date,
|
||||||
stock_factor_date,
|
stock_factor_date,
|
||||||
&candidate.symbol,
|
&candidate.symbol,
|
||||||
|
&execution_day,
|
||||||
|
&factor_day,
|
||||||
)?;
|
)?;
|
||||||
let field_value = self.selection_field_value(&candidate, &stock);
|
let field_value = self.selection_field_value(&candidate, &stock);
|
||||||
if !field_value.is_finite() || field_value < band_low || field_value > band_high {
|
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_count = 0usize;
|
||||||
let mut missing_rank_examples = Vec::new();
|
let mut missing_rank_examples = Vec::new();
|
||||||
for candidate in universe {
|
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,
|
ctx,
|
||||||
date,
|
date,
|
||||||
stock_factor_date,
|
stock_factor_date,
|
||||||
&candidate.symbol,
|
&candidate.symbol,
|
||||||
|
&execution_day,
|
||||||
|
&factor_day,
|
||||||
)?;
|
)?;
|
||||||
let field_value = self.selection_field_value(&candidate, &stock);
|
let field_value = self.selection_field_value(&candidate, &stock);
|
||||||
if !field_value.is_finite() {
|
if !field_value.is_finite() {
|
||||||
@@ -10635,13 +10748,17 @@ impl PlatformExprStrategy {
|
|||||||
universe_factor_date,
|
universe_factor_date,
|
||||||
selection_risk_deferral,
|
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);
|
let quote_candidate_limit = self.quote_plan_candidate_limit(selection_limit);
|
||||||
for candidate in universe {
|
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,
|
ctx,
|
||||||
date,
|
date,
|
||||||
stock_factor_date,
|
stock_factor_date,
|
||||||
&candidate.symbol,
|
&candidate.symbol,
|
||||||
|
&execution_day,
|
||||||
|
&factor_day,
|
||||||
)?;
|
)?;
|
||||||
let field_value = self.selection_field_value(&candidate, &stock);
|
let field_value = self.selection_field_value(&candidate, &stock);
|
||||||
if !field_value.is_finite() {
|
if !field_value.is_finite() {
|
||||||
|
|||||||
Reference in New Issue
Block a user