From c52478708fe9933f96dfda0cd7cc4579ae30fcb6 Mon Sep 17 00:00:00 2001 From: boris Date: Mon, 24 Aug 2026 12:09:09 +0800 Subject: [PATCH] =?UTF-8?q?=E7=94=A8=E5=BF=AB=E9=80=9F=E5=93=88=E5=B8=8C?= =?UTF-8?q?=E4=BC=98=E5=8C=96=E5=9B=9E=E6=B5=8B=E5=86=85=E9=83=A8=E7=B4=A2?= =?UTF-8?q?=E5=BC=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Cargo.lock | 1 + Cargo.toml | 1 + crates/fidc-core/Cargo.toml | 1 + crates/fidc-core/src/data.rs | 22 ++++++---- .../fidc-core/src/platform_expr_strategy.rs | 41 +++++++++++-------- 5 files changed, 40 insertions(+), 26 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index a563763..ef0b58b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -146,6 +146,7 @@ checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" name = "fidc-core" version = "0.1.0" dependencies = [ + "ahash", "chrono", "indexmap", "rayon", diff --git a/Cargo.toml b/Cargo.toml index 8987030..7745668 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -11,6 +11,7 @@ version = "0.1.0" authors = ["OpenAI Codex"] [workspace.dependencies] +ahash = "=0.8.12" chrono = { version = "=0.4.44", features = ["serde"] } indexmap = { version = "=2.11.4", features = ["serde"] } reqwest = { version = "=0.12.24", default-features = false, features = ["json", "rustls-tls"] } diff --git a/crates/fidc-core/Cargo.toml b/crates/fidc-core/Cargo.toml index 149e877..0fcfb41 100644 --- a/crates/fidc-core/Cargo.toml +++ b/crates/fidc-core/Cargo.toml @@ -6,6 +6,7 @@ license.workspace = true authors.workspace = true [dependencies] +ahash.workspace = true chrono.workspace = true indexmap.workspace = true rayon.workspace = true diff --git a/crates/fidc-core/src/data.rs b/crates/fidc-core/src/data.rs index 09ee95e..b52ecf5 100644 --- a/crates/fidc-core/src/data.rs +++ b/crates/fidc-core/src/data.rs @@ -2,6 +2,7 @@ use std::borrow::Cow; use std::collections::{BTreeMap, HashMap, HashSet}; use std::sync::{Arc, OnceLock}; +use ahash::AHashMap; use chrono::{NaiveDate, NaiveDateTime}; use rayon::prelude::*; use serde::{Deserialize, Serialize}; @@ -1136,12 +1137,12 @@ pub struct DataSet { execution_quotes_by_date: HashMap>>, order_book_depth_index: HashMap<(NaiveDate, String), Vec>, benchmark_by_date: BTreeMap, - market_series_by_symbol: Arc>>, - adjusted_close_series_by_symbol: Arc>>, + market_series_by_symbol: Arc>>, + adjusted_close_series_by_symbol: Arc>>, market_series_by_symbol_id: Arc>>>, adjusted_close_series_by_symbol_id: Arc>>>, benchmark_series_cache: BenchmarkPriceSeries, - symbol_id_by_code: Arc>, + symbol_id_by_code: Arc>, eligible_universe_by_date: Arc>>>, benchmark_code: String, futures_params_by_symbol: HashMap>, @@ -1305,27 +1306,32 @@ impl DataSet { let mut factor_by_date = group_arc_by_date(&factors, |item| item.date); sort_arc_groups_by_symbol(&mut factor_by_date, |item| item.symbol.as_str()); - let mut market_rows_by_symbol = HashMap::>::new(); + let mut market_rows_by_symbol = AHashMap::>::new(); for row in &market { market_rows_by_symbol .entry(row.symbol.clone()) .or_default() .push(row.as_ref()); } + let market_rows_by_symbol = market_rows_by_symbol.into_iter().collect::>(); let market_series_by_symbol = market_rows_by_symbol .into_par_iter() .map(|(symbol, rows)| { let series = Arc::new(SymbolPriceSeries::new(symbol.clone(), rows)); (symbol, series) }) - .collect::>(); + .collect::>() + .into_iter() + .collect::>(); let adjusted_close_series_by_symbol = market_series_by_symbol .par_iter() .filter_map(|(symbol, market)| { AdjustedCloseSeries::new(market, &factor_by_date) .map(|series| (symbol.clone(), Arc::new(series))) }) - .collect::>(); + .collect::>() + .into_iter() + .collect::>(); let factor_texts = factor_texts .into_iter() .filter_map(|mut item| { @@ -3298,7 +3304,7 @@ fn build_symbol_id_index( market_by_date: &BTreeMap>>, factor_by_date: &BTreeMap>>, candidate_by_date: &BTreeMap>>, -) -> HashMap { +) -> AHashMap { let mut symbols = instruments.keys().cloned().collect::>(); for rows in market_by_date.values() { for row in rows { @@ -3337,7 +3343,7 @@ fn build_symbol_id_index( fn build_group_symbol_ids( groups: &BTreeMap>>, - symbol_id_by_code: &HashMap, + symbol_id_by_code: &AHashMap, symbol_of: F, ) -> BTreeMap> where diff --git a/crates/fidc-core/src/platform_expr_strategy.rs b/crates/fidc-core/src/platform_expr_strategy.rs index 070a3bd..18ce2c4 100644 --- a/crates/fidc-core/src/platform_expr_strategy.rs +++ b/crates/fidc-core/src/platform_expr_strategy.rs @@ -1,7 +1,8 @@ use std::cell::RefCell; -use std::collections::{BTreeMap, BTreeSet, HashMap}; +use std::collections::{BTreeMap, BTreeSet}; use std::sync::Arc; +use ahash::{AHashMap, AHashSet}; use chrono::{Datelike, Duration, NaiveDate, NaiveDateTime, NaiveTime}; use rhai::{AST, Dynamic, Engine, Map, Scope}; @@ -768,24 +769,19 @@ fn framework_stock_rolling_factor_requirement(key: &str) -> Option<(&'static str struct SelectiveExpressionScope<'a> { inner: Scope<'static>, - expression_identifiers: &'a BTreeSet, - prelude_identifiers: &'a BTreeSet, + required_identifiers: &'a AHashSet, } impl<'a> SelectiveExpressionScope<'a> { - fn new( - expression_identifiers: &'a BTreeSet, - prelude_identifiers: &'a BTreeSet, - ) -> Self { + fn new(required_identifiers: &'a AHashSet) -> Self { Self { inner: Scope::new(), - expression_identifiers, - prelude_identifiers, + required_identifiers, } } fn requires(&self, name: &str) -> bool { - self.expression_identifiers.contains(name) || self.prelude_identifiers.contains(name) + self.required_identifiers.contains(name) } fn push>(&mut self, name: &str, value: T) -> &mut Self { @@ -811,6 +807,7 @@ impl<'a> SelectiveExpressionScope<'a> { struct ExpressionEvalPlan { identifiers: BTreeSet, + scope_identifiers: AHashSet, runtime_template: Result, prelude_source: String, prelude_identifiers: BTreeSet, @@ -910,11 +907,11 @@ pub struct PlatformExprStrategy { /// Value 是 Rhai 编译产物。命中后 eval 走 eval_ast_with_scope,避免重复 /// parsing。一次回测里同一表达式(stock_filter / stop_loss / rank_expr 等) /// 会被反复执行,重复解析的常数级开销在大规模回测里不可忽略。 - compiled_cache: RefCell>, + compiled_cache: RefCell>, /// 命中计数与未命中计数,便于在 unit test 中验证缓存生效;非生产指标。 cache_hits: RefCell, cache_misses: RefCell, - expression_plan_cache: RefCell>>, + expression_plan_cache: RefCell>>, prelude_dependency_plan: PreludeDependencyPlan, prelude_identifier_candidates: BTreeSet, prelude_declared_identifiers: BTreeSet, @@ -926,7 +923,7 @@ pub struct PlatformExprStrategy { stock_text_factors_required: bool, stock_state_cache_date: RefCell>, stock_state_cache: RefCell< - HashMap<(NaiveDate, NaiveDate, u32, Option, bool), StockExpressionState>, + AHashMap<(NaiveDate, NaiveDate, u32, Option, bool), StockExpressionState>, >, } @@ -1222,10 +1219,10 @@ impl PlatformExprStrategy { position_entry_dates: BTreeMap::new(), position_holding_days: BTreeMap::new(), position_holding_days_last_counted: BTreeMap::new(), - compiled_cache: RefCell::new(HashMap::new()), + compiled_cache: RefCell::new(AHashMap::new()), cache_hits: RefCell::new(0), cache_misses: RefCell::new(0), - expression_plan_cache: RefCell::new(HashMap::new()), + expression_plan_cache: RefCell::new(AHashMap::new()), prelude_dependency_plan, prelude_identifier_candidates, prelude_declared_identifiers, @@ -1236,7 +1233,7 @@ impl PlatformExprStrategy { stock_extra_factor_identifiers, stock_text_factors_required, stock_state_cache_date: RefCell::new(None), - stock_state_cache: RefCell::new(HashMap::new()), + stock_state_cache: RefCell::new(AHashMap::new()), } } @@ -3973,13 +3970,14 @@ impl PlatformExprStrategy { day: &DayExpressionState, stock: Option<&StockExpressionState>, position: Option<&PositionExpressionState>, + scope_identifiers: &AHashSet, identifiers: &BTreeSet, prelude_identifiers: &BTreeSet, include_day_factors: bool, include_factors_map: bool, include_process_event_counts: bool, ) -> Scope<'static> { - let mut scope = SelectiveExpressionScope::new(identifiers, prelude_identifiers); + let mut scope = SelectiveExpressionScope::new(scope_identifiers); let trade_date = day.date.format("%Y-%m-%d").to_string(); let decision_date = ctx.decision_date.format("%Y-%m-%d").to_string(); let execution_date = ctx.execution_date.format("%Y-%m-%d").to_string(); @@ -4686,7 +4684,8 @@ impl PlatformExprStrategy { day, stock, position, - &normalized_identifiers, + &expression_plan.scope_identifiers, + normalized_identifiers, prelude_identifiers, include_day_factors, include_factors_map, @@ -4756,10 +4755,16 @@ impl PlatformExprStrategy { .prelude_dependency_plan .source_for_expression(&identifiers); let prelude_identifiers = Self::extract_identifier_candidates(&prelude_source); + let scope_identifiers = identifiers + .iter() + .chain(&prelude_identifiers) + .cloned() + .collect::>(); let prelude_runtime_template = (!prelude_source.trim().is_empty()) .then(|| Self::compile_runtime_helper_template(&prelude_source)); let plan = Arc::new(ExpressionEvalPlan { identifiers, + scope_identifiers, runtime_template: Self::compile_runtime_helper_template(&normalized), prelude_source, prelude_identifiers,