减少滚动窗口重复索引开销
This commit is contained in:
@@ -1,3 +1,4 @@
|
||||
use std::borrow::Cow;
|
||||
use std::collections::{BTreeMap, HashMap, HashSet};
|
||||
use std::sync::{Arc, OnceLock};
|
||||
|
||||
@@ -959,7 +960,7 @@ impl SymbolPriceSeries {
|
||||
}
|
||||
|
||||
fn numeric_value_at(&self, index: usize, field: &str) -> Option<f64> {
|
||||
match normalize_field(field).as_str() {
|
||||
match normalized_field(field).as_ref() {
|
||||
"day_open" | "dayopen" => Some(self.day_opens[index]),
|
||||
"open" => Some(self.opens[index]),
|
||||
"high" => Some(self.highs[index]),
|
||||
@@ -1405,12 +1406,14 @@ impl DataSet {
|
||||
.and_then(|rows| find_arc_by_symbol(rows, symbol, |row| row.symbol.as_str()))
|
||||
}
|
||||
|
||||
fn market_series(&self, symbol: &str) -> Option<Arc<SymbolPriceSeries>> {
|
||||
self.market_series_by_symbol.get(symbol).cloned()
|
||||
fn market_series(&self, symbol: &str) -> Option<&SymbolPriceSeries> {
|
||||
self.market_series_by_symbol.get(symbol).map(Arc::as_ref)
|
||||
}
|
||||
|
||||
fn adjusted_close_series(&self, symbol: &str) -> Option<Arc<AdjustedCloseSeries>> {
|
||||
self.adjusted_close_series_by_symbol.get(symbol).cloned()
|
||||
fn adjusted_close_series(&self, symbol: &str) -> Option<&AdjustedCloseSeries> {
|
||||
self.adjusted_close_series_by_symbol
|
||||
.get(symbol)
|
||||
.map(Arc::as_ref)
|
||||
}
|
||||
|
||||
pub fn factor(&self, date: NaiveDate, symbol: &str) -> Option<&DailyFactorSnapshot> {
|
||||
@@ -2452,19 +2455,14 @@ impl DataSet {
|
||||
field: &str,
|
||||
lookback: usize,
|
||||
) -> Option<f64> {
|
||||
let field = normalize_field(field);
|
||||
match field.as_str() {
|
||||
let field = normalized_field(field);
|
||||
match field.as_ref() {
|
||||
"close" | "prev_close" | "stock_close" | "price" => self
|
||||
.adjusted_close_series(symbol)
|
||||
.and_then(|series| series.decision_moving_average(date, lookback)),
|
||||
"volume" | "stock_volume" => {
|
||||
if !self.source_daily_volume_window_available(date, symbol, lookback, false) {
|
||||
None
|
||||
} else {
|
||||
self.market_series(symbol)
|
||||
.and_then(|series| series.decision_volume_moving_average(date, lookback))
|
||||
}
|
||||
}
|
||||
"volume" | "stock_volume" => self
|
||||
.market_series(symbol)
|
||||
.and_then(|series| series.decision_volume_moving_average(date, lookback)),
|
||||
"day_open" | "dayopen" => {
|
||||
self.market_moving_average(date, symbol, lookback, PriceField::DayOpen)
|
||||
}
|
||||
@@ -2483,22 +2481,14 @@ impl DataSet {
|
||||
field: &str,
|
||||
lookback: usize,
|
||||
) -> Option<f64> {
|
||||
let field = normalize_field(field);
|
||||
match field.as_str() {
|
||||
let field = normalized_field(field);
|
||||
match field.as_ref() {
|
||||
"close" | "prev_close" | "stock_close" | "price" => self
|
||||
.adjusted_close_series(symbol)
|
||||
.and_then(|series| series.current_moving_average(date, lookback)),
|
||||
"volume" | "stock_volume" => {
|
||||
if !self.source_daily_volume_window_available(date, symbol, lookback, true) {
|
||||
None
|
||||
} else {
|
||||
self.market_series(symbol)
|
||||
.and_then(|series| series.current_volume_moving_average(date, lookback))
|
||||
.or_else(|| {
|
||||
self.factor_moving_average(date, symbol, "daily_volume", lookback)
|
||||
})
|
||||
}
|
||||
}
|
||||
"volume" | "stock_volume" => self
|
||||
.market_series(symbol)
|
||||
.and_then(|series| series.current_volume_moving_average(date, lookback)),
|
||||
"day_open" | "dayopen" => {
|
||||
self.market_moving_average(date, symbol, lookback, PriceField::DayOpen)
|
||||
}
|
||||
@@ -2525,21 +2515,16 @@ impl DataSet {
|
||||
if lookback == 0 {
|
||||
return Vec::new();
|
||||
}
|
||||
let field = normalize_field(field);
|
||||
match field.as_str() {
|
||||
let field = normalized_field(field);
|
||||
match field.as_ref() {
|
||||
"close" | "prev_close" | "stock_close" | "price" => self
|
||||
.adjusted_close_series(symbol)
|
||||
.map(|series| series.values(date, lookback, false))
|
||||
.unwrap_or_default(),
|
||||
"volume" | "stock_volume" => {
|
||||
if !self.source_daily_volume_window_available(date, symbol, lookback, false) {
|
||||
Vec::new()
|
||||
} else {
|
||||
self.market_series(symbol)
|
||||
.and_then(|series| series.decision_volume_values(date, lookback))
|
||||
.unwrap_or_default()
|
||||
}
|
||||
}
|
||||
"volume" | "stock_volume" => self
|
||||
.market_series(symbol)
|
||||
.and_then(|series| series.decision_volume_values(date, lookback))
|
||||
.unwrap_or_default(),
|
||||
"day_open" | "dayopen" => self
|
||||
.market_series(symbol)
|
||||
.map(|series| series.trailing_values(date, lookback, PriceField::DayOpen))
|
||||
@@ -2563,14 +2548,9 @@ impl DataSet {
|
||||
field: &str,
|
||||
lookback: usize,
|
||||
) -> Vec<f64> {
|
||||
let field = normalize_field(field);
|
||||
if matches!(field.as_str(), "volume" | "stock_volume")
|
||||
&& !self.source_daily_volume_window_available(date, symbol, lookback, true)
|
||||
{
|
||||
return Vec::new();
|
||||
}
|
||||
let field = normalized_field(field);
|
||||
if matches!(
|
||||
field.as_str(),
|
||||
field.as_ref(),
|
||||
"close" | "prev_close" | "stock_close" | "price"
|
||||
) {
|
||||
return self
|
||||
@@ -2578,40 +2558,17 @@ impl DataSet {
|
||||
.map(|series| series.values(date, lookback, true))
|
||||
.unwrap_or_default();
|
||||
}
|
||||
if matches!(field.as_str(), "volume" | "stock_volume") {
|
||||
if matches!(field.as_ref(), "volume" | "stock_volume") {
|
||||
return self
|
||||
.market_series(symbol)
|
||||
.and_then(|series| series.current_volume_values(date, lookback))
|
||||
.unwrap_or_default();
|
||||
}
|
||||
self.market_series(symbol)
|
||||
.map(|series| series.trailing_numeric_values(date, lookback, &field, true))
|
||||
.map(|series| series.trailing_numeric_values(date, lookback, field.as_ref(), true))
|
||||
.unwrap_or_default()
|
||||
}
|
||||
|
||||
fn source_daily_volume_window_available(
|
||||
&self,
|
||||
date: NaiveDate,
|
||||
symbol: &str,
|
||||
lookback: usize,
|
||||
include_now: bool,
|
||||
) -> bool {
|
||||
if lookback == 0 {
|
||||
return false;
|
||||
}
|
||||
let Some(series) = self.market_series(symbol) else {
|
||||
return false;
|
||||
};
|
||||
let end_index = if include_now {
|
||||
series.end_index(date)
|
||||
} else {
|
||||
series.previous_completed_end_index(date)
|
||||
};
|
||||
end_index
|
||||
.and_then(|end| series.valid_volume_window(end, lookback))
|
||||
.is_some()
|
||||
}
|
||||
|
||||
pub fn factor_numeric_values(
|
||||
&self,
|
||||
date: NaiveDate,
|
||||
@@ -3034,11 +2991,16 @@ fn intraday_quote_price_bar(snapshot: &IntradayExecutionQuote) -> PriceBar {
|
||||
}
|
||||
|
||||
fn normalize_field(field: &str) -> String {
|
||||
field
|
||||
.trim()
|
||||
.trim_matches('"')
|
||||
.trim_matches('\'')
|
||||
.to_ascii_lowercase()
|
||||
normalized_field(field).into_owned()
|
||||
}
|
||||
|
||||
fn normalized_field(field: &str) -> Cow<'_, str> {
|
||||
let trimmed = field.trim().trim_matches('"').trim_matches('\'');
|
||||
if trimmed.bytes().all(|byte| !byte.is_ascii_uppercase()) {
|
||||
Cow::Borrowed(trimmed)
|
||||
} else {
|
||||
Cow::Owned(trimmed.to_ascii_lowercase())
|
||||
}
|
||||
}
|
||||
|
||||
fn normalize_factor_snapshots(factors: Vec<DailyFactorSnapshot>) -> Vec<DailyFactorSnapshot> {
|
||||
|
||||
@@ -8432,7 +8432,6 @@ impl PlatformExprStrategy {
|
||||
&& Self::expr_requires_stock_extra_factors(
|
||||
&config.stock_filter_expr,
|
||||
prelude_declared_identifiers,
|
||||
true,
|
||||
)
|
||||
{
|
||||
return true;
|
||||
@@ -8444,9 +8443,7 @@ impl PlatformExprStrategy {
|
||||
config.rank_expr.as_str(),
|
||||
]
|
||||
.into_iter()
|
||||
.any(|expr| {
|
||||
Self::expr_requires_stock_extra_factors(expr, prelude_declared_identifiers, true)
|
||||
})
|
||||
.any(|expr| Self::expr_requires_stock_extra_factors(expr, prelude_declared_identifiers))
|
||||
}
|
||||
|
||||
fn stock_extra_factor_identifiers_for_config(
|
||||
@@ -8468,7 +8465,6 @@ impl PlatformExprStrategy {
|
||||
&mut identifiers,
|
||||
&config.stock_filter_expr,
|
||||
prelude_declared_identifiers,
|
||||
true,
|
||||
);
|
||||
}
|
||||
for expr in [
|
||||
@@ -8481,7 +8477,6 @@ impl PlatformExprStrategy {
|
||||
&mut identifiers,
|
||||
expr,
|
||||
prelude_declared_identifiers,
|
||||
true,
|
||||
);
|
||||
}
|
||||
identifiers
|
||||
@@ -8491,7 +8486,6 @@ impl PlatformExprStrategy {
|
||||
out: &mut BTreeSet<String>,
|
||||
expr: &str,
|
||||
prelude_declared_identifiers: &BTreeSet<String>,
|
||||
stock_rolling_helpers_require_extra: bool,
|
||||
) {
|
||||
let normalized = Self::normalize_expr(expr);
|
||||
for name in Self::extract_identifier_candidates(&normalized) {
|
||||
@@ -8503,11 +8497,6 @@ impl PlatformExprStrategy {
|
||||
{
|
||||
continue;
|
||||
}
|
||||
if stock_rolling_helpers_require_extra
|
||||
&& matches!(name.as_str(), "rolling_mean" | "sma" | "ma" | "vma")
|
||||
{
|
||||
continue;
|
||||
}
|
||||
out.insert(name);
|
||||
}
|
||||
}
|
||||
@@ -8558,23 +8547,12 @@ impl PlatformExprStrategy {
|
||||
fn expr_requires_stock_extra_factors(
|
||||
expr: &str,
|
||||
prelude_declared_identifiers: &BTreeSet<String>,
|
||||
stock_rolling_helpers_require_extra: bool,
|
||||
) -> bool {
|
||||
let normalized = Self::normalize_expr(expr);
|
||||
let identifiers = Self::extract_identifier_candidates(&normalized);
|
||||
if identifiers.contains("factors") || identifiers.contains("factor") {
|
||||
return true;
|
||||
}
|
||||
if stock_rolling_helpers_require_extra
|
||||
&& identifiers.iter().any(|name| {
|
||||
matches!(
|
||||
name.as_str(),
|
||||
"rolling_mean" | "rolling_mean_current" | "sma" | "ma" | "vma"
|
||||
)
|
||||
})
|
||||
{
|
||||
return true;
|
||||
}
|
||||
identifiers.into_iter().any(|name| {
|
||||
!Self::is_expression_keyword(&name)
|
||||
&& !Self::is_runtime_helper(&name)
|
||||
@@ -8717,11 +8695,6 @@ impl PlatformExprStrategy {
|
||||
) {
|
||||
let compact = Self::compact_expr(expr);
|
||||
Self::require_stock_rollings_for_named_helper(requirements, &compact, "rolling_mean");
|
||||
Self::require_stock_rollings_for_named_helper(
|
||||
requirements,
|
||||
&compact,
|
||||
"rolling_mean_current",
|
||||
);
|
||||
Self::require_stock_rollings_for_named_helper(requirements, &compact, "sma");
|
||||
Self::require_stock_rollings_for_named_helper(requirements, &compact, "ma");
|
||||
Self::require_stock_rollings_for_vma_helper(requirements, &compact);
|
||||
@@ -12614,6 +12587,24 @@ mod tests {
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn current_rolling_helpers_do_not_load_factor_maps_or_decision_rollings() {
|
||||
let mut cfg = PlatformExprStrategyConfig::microcap_rotation();
|
||||
cfg.stock_filter_expr = concat!(
|
||||
"rolling_mean_current(\"close\", 5) > rolling_mean_current(\"close\", 10)",
|
||||
" && rolling_mean_current(\"volume\", 5) < rolling_mean_current(\"volume\", 100)"
|
||||
)
|
||||
.to_string();
|
||||
|
||||
let strategy = PlatformExprStrategy::new(cfg);
|
||||
|
||||
assert!(!strategy.stock_extra_factors_required);
|
||||
assert!(!strategy.stock_rolling_requirements.requires("close", 5));
|
||||
assert!(!strategy.stock_rolling_requirements.requires("close", 10));
|
||||
assert!(!strategy.stock_rolling_requirements.requires("volume", 5));
|
||||
assert!(!strategy.stock_rolling_requirements.requires("volume", 100));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn platform_expr_missing_requested_factor_does_not_default_to_zero() {
|
||||
let date = d(2025, 5, 19);
|
||||
|
||||
Reference in New Issue
Block a user