perf: compact daily stock-state cache keys

This commit is contained in:
boris
2026-09-05 01:11:34 +08:00
parent 98a74f7bb4
commit 6f81e1940a
+119 -10
View File
@@ -1052,9 +1052,8 @@ pub struct PlatformExprStrategy {
stock_text_factors_required: bool, stock_text_factors_required: bool,
stock_state_cache_date: RefCell<Option<NaiveDate>>, stock_state_cache_date: RefCell<Option<NaiveDate>>,
stock_state_cache_calendar_index: RefCell<Option<usize>>, stock_state_cache_calendar_index: RefCell<Option<usize>>,
stock_state_cache: RefCell< stock_state_cache:
AHashMap<(NaiveDate, NaiveDate, u32, Option<NaiveTime>, bool), Arc<StockExpressionState>>, RefCell<AHashMap<(NaiveDate, u32, Option<NaiveTime>, bool), Arc<StockExpressionState>>>,
>,
} }
#[derive(Debug, Clone, PartialEq)] #[derive(Debug, Clone, PartialEq)]
@@ -4022,13 +4021,7 @@ impl PlatformExprStrategy {
} }
*self.stock_state_cache_calendar_index.borrow() *self.stock_state_cache_calendar_index.borrow()
}; };
let cache_key = ( let cache_key = (factor_date, symbol_id, execution_time, use_intraday_quote);
date,
factor_date,
symbol_id,
execution_time,
use_intraday_quote,
);
if let Some(state) = self.stock_state_cache.borrow().get(&cache_key) { if let Some(state) = self.stock_state_cache.borrow().get(&cache_key) {
return Ok(Arc::clone(state)); return Ok(Arc::clone(state));
} }
@@ -12611,6 +12604,7 @@ fn code_number_value(value: &str) -> i64 {
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use std::collections::{BTreeMap, BTreeSet}; use std::collections::{BTreeMap, BTreeSet};
use std::sync::Arc;
use chrono::{NaiveDate, NaiveTime}; use chrono::{NaiveDate, NaiveTime};
@@ -12637,6 +12631,121 @@ mod tests {
NaiveDate::from_ymd_opt(year, month, day).expect("valid date") NaiveDate::from_ymd_opt(year, month, day).expect("valid date")
} }
#[test]
fn stock_state_cache_resets_before_reusing_compact_keys_on_another_date() {
let dates = [d(2025, 1, 2), d(2025, 1, 3)];
let symbol = "300001.SZ";
let market = |date: NaiveDate, close: f64| DailyMarketSnapshot {
date,
symbol: symbol.to_string(),
timestamp: None,
day_open: close,
open: close,
high: close,
low: close,
close,
last_price: close,
bid1: close,
ask1: close,
prev_close: close,
volume: 1_000_000,
minute_volume: 10_000,
bid1_volume: 10_000,
ask1_volume: 10_000,
trading_phase: Some("continuous".to_string()),
paused: false,
upper_limit: close * 1.1,
lower_limit: close * 0.9,
price_tick: 0.01,
};
let factor = |date: NaiveDate| DailyFactorSnapshot {
date,
symbol: symbol.to_string(),
market_cap_bn: 10.0,
free_float_cap_bn: 9.0,
pe_ttm: 12.0,
turnover_ratio: Some(1.0),
effective_turnover_ratio: Some(1.0),
extra_factors: BTreeMap::from([("adjustment_factor_backward1".into(), 1.0)]),
};
let candidate = |date: NaiveDate| CandidateEligibility {
date,
symbol: symbol.to_string(),
is_st: false,
is_star_st: false,
is_new_listing: false,
is_paused: false,
allow_buy: true,
allow_sell: true,
is_kcb: false,
is_one_yuan: false,
risk_level_code: None,
};
let data = DataSet::from_components(
vec![Instrument {
symbol: symbol.to_string(),
name: symbol.to_string(),
board: "SZ".to_string(),
round_lot: 100,
listed_at: Some(d(2020, 1, 1)),
delisted_at: None,
status: "active".to_string(),
}],
vec![market(dates[0], 10.0), market(dates[1], 11.0)],
dates.into_iter().map(factor).collect(),
dates.into_iter().map(candidate).collect(),
dates
.into_iter()
.map(|date| BenchmarkSnapshot {
date,
benchmark: "000852.SH".to_string(),
open: 1_000.0,
close: 1_000.0,
prev_close: 1_000.0,
volume: 1_000_000,
})
.collect(),
)
.expect("cache test dataset");
let portfolio = PortfolioState::new(100_000.0);
let subscriptions = BTreeSet::new();
let context = |date: NaiveDate, decision_index: usize| StrategyContext {
execution_date: date,
decision_date: date,
decision_index,
data: &data,
portfolio: &portfolio,
futures_account: None,
open_orders: &[],
dynamic_universe: None,
subscriptions: &subscriptions,
process_events: &[],
active_process_event: None,
active_datetime: None,
order_events: &[],
fills: &[],
};
let strategy = PlatformExprStrategy::new(PlatformExprStrategyConfig::microcap_rotation());
let first_context = context(dates[0], 0);
let first = strategy
.stock_state(&first_context, dates[0], symbol)
.expect("first state");
let first_again = strategy
.stock_state(&first_context, dates[0], symbol)
.expect("cached first state");
assert!(Arc::ptr_eq(&first, &first_again));
assert_eq!(first.close, 10.0);
let second_context = context(dates[1], 1);
let second = strategy
.stock_state(&second_context, dates[1], symbol)
.expect("second state");
assert!(!Arc::ptr_eq(&first, &second));
assert_eq!(second.close, 11.0);
assert_eq!(*strategy.stock_state_cache_date.borrow(), Some(dates[1]));
assert_eq!(strategy.stock_state_cache.borrow().len(), 1);
}
#[test] #[test]
fn dated_position_exposure_uses_the_latest_effective_point() { fn dated_position_exposure_uses_the_latest_effective_point() {
let schedule = BTreeMap::from([(d(2026, 8, 14), 0.6451), (d(2026, 8, 20), 0.3225)]); let schedule = BTreeMap::from([(d(2026, 8, 14), 0.6451), (d(2026, 8, 20), 0.3225)]);