perf: project stock snapshot fields by strategy

This commit is contained in:
boris
2026-09-05 02:02:01 +08:00
parent 5f7321da58
commit 7f17fa1fb4
+99 -8
View File
@@ -742,6 +742,13 @@ struct StockRollingRequirements {
fields: BTreeSet<(StockRollingField, usize)>, fields: BTreeSet<(StockRollingField, usize)>,
} }
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
struct StockSnapshotFieldRequirements {
amount: bool,
touched_upper_limit: bool,
touched_lower_limit: bool,
}
impl StockRollingRequirements { impl StockRollingRequirements {
fn require(&mut self, field: StockRollingField, lookback: usize) { fn require(&mut self, field: StockRollingField, lookback: usize) {
if lookback > 0 { if lookback > 0 {
@@ -1046,6 +1053,7 @@ pub struct PlatformExprStrategy {
selection_quote_usage: StockFilterQuoteUsage, selection_quote_usage: StockFilterQuoteUsage,
stock_rolling_requirements: StockRollingRequirements, stock_rolling_requirements: StockRollingRequirements,
stock_current_rolling_mean_required: bool, stock_current_rolling_mean_required: bool,
stock_snapshot_field_requirements: StockSnapshotFieldRequirements,
stock_extra_factors_required: bool, stock_extra_factors_required: bool,
stock_extra_factor_identifiers: BTreeSet<String>, stock_extra_factor_identifiers: BTreeSet<String>,
stock_extra_factor_map_required: bool, stock_extra_factor_map_required: bool,
@@ -1325,6 +1333,8 @@ impl PlatformExprStrategy {
let stock_rolling_requirements = Self::stock_rolling_requirements_for_config(&config); let stock_rolling_requirements = Self::stock_rolling_requirements_for_config(&config);
let stock_current_rolling_mean_required = let stock_current_rolling_mean_required =
Self::stock_current_rolling_mean_required_for_config(&config); Self::stock_current_rolling_mean_required_for_config(&config);
let stock_snapshot_field_requirements =
Self::stock_snapshot_field_requirements_for_config(&config);
let stock_extra_factors_required = let stock_extra_factors_required =
Self::stock_extra_factors_required_for_config(&config, &prelude_declared_identifiers); Self::stock_extra_factors_required_for_config(&config, &prelude_declared_identifiers);
let stock_extra_factor_identifiers = let stock_extra_factor_identifiers =
@@ -1368,6 +1378,7 @@ impl PlatformExprStrategy {
selection_quote_usage, selection_quote_usage,
stock_rolling_requirements, stock_rolling_requirements,
stock_current_rolling_mean_required, stock_current_rolling_mean_required,
stock_snapshot_field_requirements,
stock_extra_factors_required, stock_extra_factors_required,
stock_extra_factor_identifiers, stock_extra_factor_identifiers,
stock_extra_factor_map_required, stock_extra_factor_map_required,
@@ -4137,7 +4148,9 @@ impl PlatformExprStrategy {
let stock_volume_ma20 = volume_rolling(2); let stock_volume_ma20 = volume_rolling(2);
let stock_volume_ma60 = volume_rolling(3); let stock_volume_ma60 = volume_rolling(3);
let stock_volume_ma100 = volume_rolling(4); let stock_volume_ma100 = volume_rolling(4);
let touched_upper_limit = if intraday_same_day_factor { let touched_upper_limit = if !self.stock_snapshot_field_requirements.touched_upper_limit {
false
} else if intraday_same_day_factor {
!market.paused !market.paused
&& (market.is_at_upper_limit_price(market.close) && (market.is_at_upper_limit_price(market.close)
|| market.is_at_upper_limit_price(market.open) || market.is_at_upper_limit_price(market.open)
@@ -4156,7 +4169,9 @@ impl PlatformExprStrategy {
|| feature_market.is_at_upper_limit_price(feature_market.open) || feature_market.is_at_upper_limit_price(feature_market.open)
|| feature_market.is_at_upper_limit_price(feature_market.day_open)) || feature_market.is_at_upper_limit_price(feature_market.day_open))
}; };
let touched_lower_limit = if intraday_same_day_factor { let touched_lower_limit = if !self.stock_snapshot_field_requirements.touched_lower_limit {
false
} else if intraday_same_day_factor {
!market.paused !market.paused
&& (market.is_at_lower_limit_price(market.close) && (market.is_at_lower_limit_price(market.close)
|| market.is_at_lower_limit_price(market.open) || market.is_at_lower_limit_price(market.open)
@@ -4175,7 +4190,9 @@ impl PlatformExprStrategy {
|| feature_market.is_at_lower_limit_price(feature_market.open) || feature_market.is_at_lower_limit_price(feature_market.open)
|| feature_market.is_at_lower_limit_price(feature_market.day_open)) || feature_market.is_at_lower_limit_price(feature_market.day_open))
}; };
let amount = if intraday_same_day_factor { let amount = if !self.stock_snapshot_field_requirements.amount {
0.0
} else if intraday_same_day_factor {
f64::NAN f64::NAN
} else { } else {
factor.extra_factors.get("amount").copied().unwrap_or(0.0) factor.extra_factors.get("amount").copied().unwrap_or(0.0)
@@ -10052,6 +10069,43 @@ impl PlatformExprStrategy {
false false
} }
fn stock_snapshot_field_requirements_for_config(
config: &PlatformExprStrategyConfig,
) -> StockSnapshotFieldRequirements {
if Self::has_stock_explicit_actions(config)
|| Self::stock_extra_factor_map_required_for_config(config)
{
return StockSnapshotFieldRequirements {
amount: true,
touched_upper_limit: true,
touched_lower_limit: true,
};
}
let mut identifiers = BTreeSet::new();
for expr in [
config.prelude.as_str(),
config.stock_filter_expr.as_str(),
config.buy_scale_expr.as_str(),
config.stop_loss_expr.as_str(),
config.take_profit_expr.as_str(),
config.rank_expr.as_str(),
config.market_cap_field.as_str(),
config.rank_by.as_str(),
] {
identifiers.extend(Self::extract_identifier_candidates(&Self::normalize_expr(
expr,
)));
}
StockSnapshotFieldRequirements {
amount: identifiers.contains("amount"),
touched_upper_limit: identifiers.contains("touched_upper_limit")
|| identifiers.contains("hit_upper_limit"),
touched_lower_limit: identifiers.contains("touched_lower_limit")
|| identifiers.contains("hit_lower_limit"),
}
}
fn stock_extra_factors_required_for_config( fn stock_extra_factors_required_for_config(
config: &PlatformExprStrategyConfig, config: &PlatformExprStrategyConfig,
prelude_declared_identifiers: &BTreeSet<String>, prelude_declared_identifiers: &BTreeSet<String>,
@@ -12622,7 +12676,8 @@ mod tests {
PlatformPortfolioDrawdownController, PlatformRebalanceSchedule, PlatformScheduleFrequency, PlatformPortfolioDrawdownController, PlatformRebalanceSchedule, PlatformScheduleFrequency,
PlatformStopTakeReferencePriceMode, PlatformTradeAction, PlatformUniverseActionKind, PlatformStopTakeReferencePriceMode, PlatformTradeAction, PlatformUniverseActionKind,
RuntimeHelperResolution, SelectionRiskDeferral, StockFilterQuoteUsage, RuntimeHelperResolution, SelectionRiskDeferral, StockFilterQuoteUsage,
framework_stock_rolling_factor_requirement, scheduled_position_exposure, StockSnapshotFieldRequirements, framework_stock_rolling_factor_requirement,
scheduled_position_exposure,
}; };
use crate::{ use crate::{
AlgoOrderStyle, BenchmarkSnapshot, CandidateEligibility, CorporateAction, AlgoOrderStyle, BenchmarkSnapshot, CandidateEligibility, CorporateAction,
@@ -14460,6 +14515,38 @@ mod tests {
); );
} }
#[test]
fn stock_snapshot_fields_are_loaded_only_when_the_strategy_uses_them() {
let base = PlatformExprStrategy::new(PlatformExprStrategyConfig::microcap_rotation());
assert_eq!(
base.stock_snapshot_field_requirements,
StockSnapshotFieldRequirements::default()
);
let mut direct = PlatformExprStrategyConfig::microcap_rotation();
direct.stock_filter_expr =
"amount > 0.0 && hit_upper_limit && !touched_lower_limit".to_string();
assert_eq!(
PlatformExprStrategy::new(direct).stock_snapshot_field_requirements,
StockSnapshotFieldRequirements {
amount: true,
touched_upper_limit: true,
touched_lower_limit: true,
}
);
let mut dynamic_map = PlatformExprStrategyConfig::microcap_rotation();
dynamic_map.stock_filter_expr = "factors[\"custom_alpha\"] > 0.0".to_string();
assert_eq!(
PlatformExprStrategy::new(dynamic_map).stock_snapshot_field_requirements,
StockSnapshotFieldRequirements {
amount: true,
touched_upper_limit: true,
touched_lower_limit: true,
}
);
}
#[test] #[test]
fn platform_expr_missing_requested_factor_does_not_default_to_zero() { fn platform_expr_missing_requested_factor_does_not_default_to_zero() {
let date = d(2025, 5, 19); let date = d(2025, 5, 19);
@@ -17918,7 +18005,9 @@ mod tests {
order_events: &[], order_events: &[],
fills: &[], fills: &[],
}; };
let strategy = PlatformExprStrategy::new(PlatformExprStrategyConfig::microcap_rotation()); let mut cfg = PlatformExprStrategyConfig::microcap_rotation();
cfg.stock_filter_expr = "touched_upper_limit || touched_lower_limit".to_string();
let strategy = PlatformExprStrategy::new(cfg);
let stock = strategy let stock = strategy
.stock_state_with_factor_date(&ctx, date, date, symbol) .stock_state_with_factor_date(&ctx, date, date, symbol)
@@ -18054,7 +18143,9 @@ mod tests {
order_events: &[], order_events: &[],
fills: &[], fills: &[],
}; };
let strategy = PlatformExprStrategy::new(PlatformExprStrategyConfig::microcap_rotation()); let mut cfg = PlatformExprStrategyConfig::microcap_rotation();
cfg.stock_filter_expr = "touched_upper_limit".to_string();
let strategy = PlatformExprStrategy::new(cfg);
let stock = strategy let stock = strategy
.stock_state_with_factor_date(&ctx, date, factor_date, symbol) .stock_state_with_factor_date(&ctx, date, factor_date, symbol)
@@ -18159,7 +18250,7 @@ mod tests {
}; };
let mut cfg = PlatformExprStrategyConfig::microcap_rotation(); let mut cfg = PlatformExprStrategyConfig::microcap_rotation();
cfg.signal_symbol = symbol.to_string(); cfg.signal_symbol = symbol.to_string();
cfg.stock_filter_expr = "true".to_string(); cfg.stock_filter_expr = "touched_upper_limit".to_string();
cfg.benchmark_short_ma_days = 1; cfg.benchmark_short_ma_days = 1;
cfg.benchmark_long_ma_days = 1; cfg.benchmark_long_ma_days = 1;
let strategy = PlatformExprStrategy::new(cfg); let strategy = PlatformExprStrategy::new(cfg);
@@ -21829,7 +21920,7 @@ mod tests {
cfg.market_cap_upper_expr = "100".to_string(); cfg.market_cap_upper_expr = "100".to_string();
cfg.selection_limit_expr = "1".to_string(); cfg.selection_limit_expr = "1".to_string();
cfg.stock_filter_expr = cfg.stock_filter_expr =
"last_price <= 0 || (!at_upper_limit && !at_lower_limit)".to_string(); "amount >= 0 && (last_price <= 0 || (!at_upper_limit && !at_lower_limit))".to_string();
cfg.exposure_expr = "1.0".to_string(); cfg.exposure_expr = "1.0".to_string();
let mut strategy = PlatformExprStrategy::new(cfg); let mut strategy = PlatformExprStrategy::new(cfg);