缓存平台策略表达式元数据

This commit is contained in:
boris
2026-06-21 00:20:56 +08:00
parent beebc5fa58
commit 0f982887a3
+41 -17
View File
@@ -511,6 +511,12 @@ pub struct PlatformExprStrategy {
/// 命中计数与未命中计数,便于在 unit test 中验证缓存生效;非生产指标。 /// 命中计数与未命中计数,便于在 unit test 中验证缓存生效;非生产指标。
cache_hits: RefCell<u64>, cache_hits: RefCell<u64>,
cache_misses: RefCell<u64>, cache_misses: RefCell<u64>,
normalized_prelude: String,
prelude_identifier_candidates: BTreeSet<String>,
prelude_declared_identifiers: BTreeSet<String>,
prelude_numeric_constants: HashMap<String, f64>,
compact_stock_filter_expr: String,
stock_filter_quote_usage: StockFilterQuoteUsage,
} }
#[derive(Debug, Clone, PartialEq)] #[derive(Debug, Clone, PartialEq)]
@@ -589,6 +595,15 @@ impl PlatformExprStrategy {
engine.register_fn("trim", |value: &str| value.trim().to_string()); engine.register_fn("trim", |value: &str| value.trim().to_string());
engine.register_fn("strlen", |value: &str| value.chars().count() as i64); engine.register_fn("strlen", |value: &str| value.chars().count() as i64);
engine.register_fn("code_number", code_number_value); engine.register_fn("code_number", code_number_value);
let normalized_prelude = Self::normalize_prelude_for_eval(&config.prelude);
let prelude_identifier_candidates =
Self::extract_identifier_candidates(&normalized_prelude);
let prelude_declared_identifiers = Self::declared_prelude_identifiers(&config.prelude);
let prelude_numeric_constants = Self::parse_prelude_numeric_constants(&normalized_prelude);
let normalized_stock_filter_expr = Self::normalize_expr(&config.stock_filter_expr);
let compact_stock_filter_expr = Self::compact_expr(&normalized_stock_filter_expr);
let stock_filter_quote_usage =
Self::stock_filter_quote_usage_for_expr(&normalized_stock_filter_expr);
Self { Self {
config, config,
engine, engine,
@@ -599,6 +614,12 @@ impl PlatformExprStrategy {
compiled_cache: RefCell::new(HashMap::new()), compiled_cache: RefCell::new(HashMap::new()),
cache_hits: RefCell::new(0), cache_hits: RefCell::new(0),
cache_misses: RefCell::new(0), cache_misses: RefCell::new(0),
normalized_prelude,
prelude_identifier_candidates,
prelude_declared_identifiers,
prelude_numeric_constants,
compact_stock_filter_expr,
stock_filter_quote_usage,
} }
} }
@@ -3024,9 +3045,8 @@ impl PlatformExprStrategy {
position: Option<&PositionExpressionState>, position: Option<&PositionExpressionState>,
) -> Result<Dynamic, BacktestError> { ) -> Result<Dynamic, BacktestError> {
let normalized_expr = Self::normalize_expr(expr); let normalized_expr = Self::normalize_expr(expr);
let prelude = Self::normalize_prelude_for_eval(&self.config.prelude);
let normalized_identifiers = Self::extract_identifier_candidates(&normalized_expr); let normalized_identifiers = Self::extract_identifier_candidates(&normalized_expr);
let prelude_identifiers = Self::extract_identifier_candidates(&prelude); let prelude_identifiers = &self.prelude_identifier_candidates;
let include_day_factors = normalized_identifiers.contains("day_factors") let include_day_factors = normalized_identifiers.contains("day_factors")
|| normalized_identifiers.contains("day_factor") || normalized_identifiers.contains("day_factor")
|| prelude_identifiers.contains("day_factors"); || prelude_identifiers.contains("day_factors");
@@ -3046,11 +3066,10 @@ impl PlatformExprStrategy {
); );
let expanded_expr = let expanded_expr =
self.expand_runtime_helpers(ctx, day, stock, &normalized_expr, &mut scope)?; self.expand_runtime_helpers(ctx, day, stock, &normalized_expr, &mut scope)?;
let prelude_declared_identifiers = Self::declared_prelude_identifiers(&self.config.prelude);
if let Some(item) = stock { if let Some(item) = stock {
for identifier in Self::extract_identifier_candidates(&expanded_expr) { for identifier in Self::extract_identifier_candidates(&expanded_expr) {
if Self::is_reserved_scope_name(identifier.as_str()) if Self::is_reserved_scope_name(identifier.as_str())
|| prelude_declared_identifiers.contains(&identifier) || self.prelude_declared_identifiers.contains(&identifier)
|| (!day.available_factor_names.contains(&identifier) || (!day.available_factor_names.contains(&identifier)
&& !day.available_text_factor_names.contains(&identifier)) && !day.available_text_factor_names.contains(&identifier))
{ {
@@ -3064,11 +3083,11 @@ impl PlatformExprStrategy {
} }
} }
} }
let mut script_parts = Vec::new(); let mut script_parts = Vec::with_capacity(2);
if !prelude.trim().is_empty() { if !self.normalized_prelude.trim().is_empty() {
script_parts.push(prelude); script_parts.push(self.normalized_prelude.as_str());
} }
script_parts.push(expanded_expr); script_parts.push(expanded_expr.as_str());
let script = script_parts.join("\n"); let script = script_parts.join("\n");
self.eval_with_cache(&mut scope, &script) self.eval_with_cache(&mut scope, &script)
} }
@@ -3114,7 +3133,12 @@ impl PlatformExprStrategy {
} }
fn prelude_numeric_constant(&self, name: &str) -> Option<f64> { fn prelude_numeric_constant(&self, name: &str) -> Option<f64> {
for line in Self::normalize_prelude_for_eval(&self.config.prelude).lines() { self.prelude_numeric_constants.get(name).copied()
}
fn parse_prelude_numeric_constants(normalized_prelude: &str) -> HashMap<String, f64> {
let mut constants = HashMap::new();
for line in normalized_prelude.lines() {
let trimmed = line.trim(); let trimmed = line.trim();
let Some(body) = trimmed.strip_prefix("let ") else { let Some(body) = trimmed.strip_prefix("let ") else {
continue; continue;
@@ -3122,15 +3146,12 @@ impl PlatformExprStrategy {
let Some((lhs, rhs)) = body.split_once('=') else { let Some((lhs, rhs)) = body.split_once('=') else {
continue; continue;
}; };
if lhs.trim() != name {
continue;
}
let rhs = rhs.trim().trim_end_matches(';').trim(); let rhs = rhs.trim().trim_end_matches(';').trim();
if let Ok(value) = rhs.parse::<f64>() { if let Ok(value) = rhs.parse::<f64>() {
return Some(value); constants.insert(lhs.trim().to_string(), value);
} }
} }
None constants
} }
fn normalize_prelude_for_eval(prelude: &str) -> String { fn normalize_prelude_for_eval(prelude: &str) -> String {
@@ -5642,7 +5663,7 @@ impl PlatformExprStrategy {
_day: &DayExpressionState, _day: &DayExpressionState,
stock: &StockExpressionState, stock: &StockExpressionState,
) -> Option<bool> { ) -> Option<bool> {
let compact = Self::compact_expr(&Self::normalize_expr(&self.config.stock_filter_expr)); let compact = self.compact_stock_filter_expr.as_str();
let ma_ratio = self.prelude_numeric_constant("ma_ratio").unwrap_or(1.0); let ma_ratio = self.prelude_numeric_constant("ma_ratio").unwrap_or(1.0);
if compact == "stock_ma_short>stock_ma_mid*ma_ratio&&stock_ma_mid>stock_ma_long" { if compact == "stock_ma_short>stock_ma_mid*ma_ratio&&stock_ma_mid>stock_ma_long" {
return Some( return Some(
@@ -5654,7 +5675,7 @@ impl PlatformExprStrategy {
); );
} }
let mut filter_body = compact.as_str(); let mut filter_body = compact;
let requires_min_listed_days = let requires_min_listed_days =
if let Some(rest) = filter_body.strip_prefix("listed_days>=min_listed_days&&") { if let Some(rest) = filter_body.strip_prefix("listed_days>=min_listed_days&&") {
filter_body = rest; filter_body = rest;
@@ -6157,7 +6178,10 @@ impl PlatformExprStrategy {
} }
fn stock_filter_quote_usage(&self) -> StockFilterQuoteUsage { fn stock_filter_quote_usage(&self) -> StockFilterQuoteUsage {
let expr = Self::normalize_runtime_field_aliases(&self.config.stock_filter_expr); self.stock_filter_quote_usage
}
fn stock_filter_quote_usage_for_expr(expr: &str) -> StockFilterQuoteUsage {
let mut usage = StockFilterQuoteUsage::DailyOnly; let mut usage = StockFilterQuoteUsage::DailyOnly;
for name in Self::extract_identifier_candidates(&expr) { for name in Self::extract_identifier_candidates(&expr) {
match name.as_str() { match name.as_str() {