perf: project stock snapshot fields by strategy
This commit is contained in:
@@ -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);
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user