复用策略表达式解析计划

This commit is contained in:
boris
2026-08-01 21:44:57 +08:00
parent 6f2c39aaf2
commit d51d324977
+41 -14
View File
@@ -1,5 +1,6 @@
use std::cell::RefCell; use std::cell::RefCell;
use std::collections::{BTreeMap, BTreeSet, HashMap}; use std::collections::{BTreeMap, BTreeSet, HashMap};
use std::sync::Arc;
use chrono::{Datelike, Duration, NaiveDate, NaiveDateTime, NaiveTime}; use chrono::{Datelike, Duration, NaiveDate, NaiveDateTime, NaiveTime};
use rhai::{AST, Dynamic, Engine, Map, Scope}; use rhai::{AST, Dynamic, Engine, Map, Scope};
@@ -835,6 +836,11 @@ impl<'a> SelectiveExpressionScope<'a> {
} }
} }
struct ExpressionEvalPlan {
normalized: String,
identifiers: BTreeSet<String>,
}
pub struct PlatformExprStrategy { pub struct PlatformExprStrategy {
config: PlatformExprStrategyConfig, config: PlatformExprStrategyConfig,
engine: Engine, engine: Engine,
@@ -857,6 +863,7 @@ pub struct PlatformExprStrategy {
/// 命中计数与未命中计数,便于在 unit test 中验证缓存生效;非生产指标。 /// 命中计数与未命中计数,便于在 unit test 中验证缓存生效;非生产指标。
cache_hits: RefCell<u64>, cache_hits: RefCell<u64>,
cache_misses: RefCell<u64>, cache_misses: RefCell<u64>,
expression_plan_cache: RefCell<HashMap<String, Arc<ExpressionEvalPlan>>>,
normalized_prelude: String, normalized_prelude: String,
prelude_identifier_candidates: BTreeSet<String>, prelude_identifier_candidates: BTreeSet<String>,
prelude_declared_identifiers: BTreeSet<String>, prelude_declared_identifiers: BTreeSet<String>,
@@ -1177,6 +1184,7 @@ 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),
expression_plan_cache: RefCell::new(HashMap::new()),
normalized_prelude, normalized_prelude,
prelude_identifier_candidates, prelude_identifier_candidates,
prelude_declared_identifiers, prelude_declared_identifiers,
@@ -4453,8 +4461,9 @@ impl PlatformExprStrategy {
stock: Option<&StockExpressionState>, stock: Option<&StockExpressionState>,
position: Option<&PositionExpressionState>, position: Option<&PositionExpressionState>,
) -> Result<Dynamic, BacktestError> { ) -> Result<Dynamic, BacktestError> {
let normalized_expr = Self::normalize_expr(expr); let expression_plan = self.expression_eval_plan(expr);
let normalized_identifiers = Self::extract_identifier_candidates(&normalized_expr); let normalized_expr = expression_plan.normalized.as_str();
let normalized_identifiers = &expression_plan.identifiers;
let prelude_identifiers = &self.prelude_identifier_candidates; 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")
@@ -4481,27 +4490,30 @@ 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)?;
if let Some(item) = stock { if let Some(item) = stock {
let mut expanded_identifiers = Self::extract_identifier_candidates(&expanded_prelude); let factor_identifiers = normalized_identifiers.iter().chain(
expanded_identifiers.extend(Self::extract_identifier_candidates(&expanded_expr)); prelude_identifiers
for identifier in expanded_identifiers { .iter()
.filter(|identifier| !normalized_identifiers.contains(*identifier)),
);
for identifier in factor_identifiers {
if Self::is_reserved_scope_name(identifier.as_str()) if Self::is_reserved_scope_name(identifier.as_str())
|| self.prelude_declared_identifiers.contains(&identifier) || self.prelude_declared_identifiers.contains(identifier)
|| (!self.stock_extra_factor_identifiers.contains(&identifier) || (!self.stock_extra_factor_identifiers.contains(identifier)
&& !item.extra_factors.contains_key(&identifier) && !item.extra_factors.contains_key(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))
{ {
continue; continue;
} }
if let Some(value) = item.extra_text_factors.get(&identifier) { if let Some(value) = item.extra_text_factors.get(identifier) {
scope.push_dynamic(identifier, Dynamic::from(value.clone())); scope.push_dynamic(identifier.clone(), Dynamic::from(value.clone()));
} else { } else {
let value = item let value = item
.extra_factors .extra_factors
.get(&identifier) .get(identifier)
.copied() .copied()
.unwrap_or(f64::NAN); .unwrap_or(f64::NAN);
scope.push_dynamic(identifier, Dynamic::from(value)); scope.push_dynamic(identifier.clone(), Dynamic::from(value));
} }
} }
} }
@@ -4514,6 +4526,21 @@ impl PlatformExprStrategy {
self.eval_with_cache(&mut scope, &script) self.eval_with_cache(&mut scope, &script)
} }
fn expression_eval_plan(&self, expr: &str) -> Arc<ExpressionEvalPlan> {
if let Some(plan) = self.expression_plan_cache.borrow().get(expr).cloned() {
return plan;
}
let normalized = Self::normalize_expr(expr);
let plan = Arc::new(ExpressionEvalPlan {
identifiers: Self::extract_identifier_candidates(&normalized),
normalized,
});
self.expression_plan_cache
.borrow_mut()
.insert(expr.to_string(), Arc::clone(&plan));
plan
}
fn normalize_expr(expr: &str) -> String { fn normalize_expr(expr: &str) -> String {
let expr = Self::normalize_runtime_field_aliases(expr.trim()); let expr = Self::normalize_runtime_field_aliases(expr.trim());
let expr = Self::normalize_python_numeric_division(&expr); let expr = Self::normalize_python_numeric_division(&expr);