复用当前时点标准rolling值

This commit is contained in:
boris
2026-08-28 16:47:07 +08:00
parent e261d93ce5
commit 2d516cf1eb
+179 -4
View File
@@ -706,10 +706,59 @@ struct StockExpressionState {
stock_volume_ma20: f64,
stock_volume_ma60: f64,
stock_volume_ma100: f64,
stock_current_ma5: f64,
stock_current_ma10: f64,
stock_current_ma20: f64,
stock_current_ma30: f64,
stock_current_volume_ma5: f64,
stock_current_volume_ma10: f64,
stock_current_volume_ma20: f64,
stock_current_volume_ma60: f64,
stock_current_volume_ma100: f64,
extra_factors: BTreeMap<String, f64>,
extra_text_factors: BTreeMap<String, String>,
}
impl StockExpressionState {
fn standard_rolling_mean(
&self,
field: &str,
lookback: usize,
include_now: bool,
) -> Option<f64> {
let value = match (field, lookback, include_now) {
("close" | "prev_close" | "stock_close" | "price", 5, false) => self.stock_ma5,
("close" | "prev_close" | "stock_close" | "price", 10, false) => self.stock_ma10,
("close" | "prev_close" | "stock_close" | "price", 20, false) => self.stock_ma20,
("close" | "prev_close" | "stock_close" | "price", 30, false) => self.stock_ma30,
("volume" | "stock_volume", 5, false) => self.stock_volume_ma5,
("volume" | "stock_volume", 10, false) => self.stock_volume_ma10,
("volume" | "stock_volume", 20, false) => self.stock_volume_ma20,
("volume" | "stock_volume", 60, false) => self.stock_volume_ma60,
("volume" | "stock_volume", 100, false) => self.stock_volume_ma100,
("close" | "prev_close" | "stock_close" | "price", 5, true) => {
self.stock_current_ma5
}
("close" | "prev_close" | "stock_close" | "price", 10, true) => {
self.stock_current_ma10
}
("close" | "prev_close" | "stock_close" | "price", 20, true) => {
self.stock_current_ma20
}
("close" | "prev_close" | "stock_close" | "price", 30, true) => {
self.stock_current_ma30
}
("volume" | "stock_volume", 5, true) => self.stock_current_volume_ma5,
("volume" | "stock_volume", 10, true) => self.stock_current_volume_ma10,
("volume" | "stock_volume", 20, true) => self.stock_current_volume_ma20,
("volume" | "stock_volume", 60, true) => self.stock_current_volume_ma60,
("volume" | "stock_volume", 100, true) => self.stock_current_volume_ma100,
_ => return None,
};
value.is_finite().then_some(value)
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
enum StockFilterQuoteUsage {
DailyOnly,
@@ -727,6 +776,7 @@ enum StockRollingField {
struct StockRollingRequirements {
all: bool,
fields: BTreeSet<(StockRollingField, usize)>,
current_fields: BTreeSet<(StockRollingField, usize)>,
}
impl StockRollingRequirements {
@@ -736,6 +786,12 @@ impl StockRollingRequirements {
}
}
fn require_current(&mut self, field: StockRollingField, lookback: usize) {
if lookback > 0 {
self.current_fields.insert((field, lookback));
}
}
fn require_all(&mut self) {
self.all = true;
}
@@ -751,6 +807,19 @@ impl StockRollingRequirements {
};
self.fields.contains(&(field, lookback))
}
fn requires_current(&self, field: &'static str, lookback: usize) -> bool {
let field = match field {
"close" => StockRollingField::Close,
"volume" => StockRollingField::Volume,
_ => return false,
};
self.current_fields.contains(&(field, lookback))
}
fn has_current(&self) -> bool {
!self.current_fields.is_empty()
}
}
#[derive(Debug, Clone)]
@@ -3943,6 +4012,12 @@ impl PlatformExprStrategy {
.then_some(lookback)
.unwrap_or(0)
};
let required_current_rolling = |field: &'static str, lookback: usize| {
self.stock_rolling_requirements
.requires_current(field, lookback)
.then_some(lookback)
.unwrap_or(0)
};
let close_lookbacks = [
required_rolling("close", self.config.stock_short_ma_days),
required_rolling("close", self.config.stock_mid_ma_days),
@@ -3959,6 +4034,22 @@ impl PlatformExprStrategy {
required_rolling("volume", 60),
required_rolling("volume", 100),
];
let current_close_lookbacks = [
0,
0,
0,
required_current_rolling("close", 5),
required_current_rolling("close", 10),
required_current_rolling("close", 20),
required_current_rolling("close", 30),
];
let current_volume_lookbacks = [
required_current_rolling("volume", 5),
required_current_rolling("volume", 10),
required_current_rolling("volume", 20),
required_current_rolling("volume", 60),
required_current_rolling("volume", 100),
];
let rolling_means = ctx.data.market_standard_rolling_means_by_symbol_id(
date,
symbol_id,
@@ -3966,8 +4057,26 @@ impl PlatformExprStrategy {
&volume_lookbacks,
false,
);
let current_rolling_means = if self.stock_rolling_requirements.has_current() {
ctx.data.market_standard_rolling_means_by_symbol_id(
date,
symbol_id,
&current_close_lookbacks,
&current_volume_lookbacks,
true,
)
} else {
crate::data::StandardRollingMeans {
close: [None; 7],
volume: [None; 5],
}
};
let close_rolling = |index: usize| rolling_means.close[index].unwrap_or(f64::NAN);
let volume_rolling = |index: usize| rolling_means.volume[index].unwrap_or(f64::NAN);
let current_close_rolling =
|index: usize| current_rolling_means.close[index].unwrap_or(f64::NAN);
let current_volume_rolling =
|index: usize| current_rolling_means.volume[index].unwrap_or(f64::NAN);
let stock_ma_short = close_rolling(0);
let stock_ma_mid = close_rolling(1);
let stock_ma_long = close_rolling(2);
@@ -3980,6 +4089,15 @@ impl PlatformExprStrategy {
let stock_volume_ma20 = volume_rolling(2);
let stock_volume_ma60 = volume_rolling(3);
let stock_volume_ma100 = volume_rolling(4);
let stock_current_ma5 = current_close_rolling(3);
let stock_current_ma10 = current_close_rolling(4);
let stock_current_ma20 = current_close_rolling(5);
let stock_current_ma30 = current_close_rolling(6);
let stock_current_volume_ma5 = current_volume_rolling(0);
let stock_current_volume_ma10 = current_volume_rolling(1);
let stock_current_volume_ma20 = current_volume_rolling(2);
let stock_current_volume_ma60 = current_volume_rolling(3);
let stock_current_volume_ma100 = current_volume_rolling(4);
let touched_upper_limit = if intraday_same_day_factor {
!market.paused
&& (market.is_at_upper_limit_price(market.close)
@@ -4126,6 +4244,15 @@ impl PlatformExprStrategy {
stock_volume_ma20,
stock_volume_ma60,
stock_volume_ma100,
stock_current_ma5,
stock_current_ma10,
stock_current_ma20,
stock_current_ma30,
stock_current_volume_ma5,
stock_current_volume_ma10,
stock_current_volume_ma20,
stock_current_volume_ma60,
stock_current_volume_ma100,
extra_factors,
extra_text_factors: if self.stock_text_factors_required {
ctx.data
@@ -6700,6 +6827,7 @@ impl PlatformExprStrategy {
"rolling_mean(\"{other}\", {lookback}) requires stock context"
))
})?;
stock.standard_rolling_mean(other, lookback, false).or_else(|| {
self.stock_decision_rolling_mean(
ctx,
day.date,
@@ -6708,6 +6836,7 @@ impl PlatformExprStrategy {
other,
lookback,
)
})
}
};
value.ok_or_else(|| {
@@ -6749,6 +6878,7 @@ impl PlatformExprStrategy {
"rolling_mean_current(\"{other}\", {lookback}) requires stock context"
))
})?;
stock.standard_rolling_mean(other, lookback, true).or_else(|| {
self.stock_current_rolling_mean(
ctx,
day.date,
@@ -6757,6 +6887,7 @@ impl PlatformExprStrategy {
other,
lookback,
)
})
}
};
value.ok_or_else(|| {
@@ -9852,9 +9983,32 @@ impl PlatformExprStrategy {
expr: &str,
) {
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, "sma");
Self::require_stock_rollings_for_named_helper(requirements, &compact, "ma");
Self::require_stock_rollings_for_named_helper(
requirements,
&compact,
"rolling_mean",
false,
);
Self::require_stock_rollings_for_named_helper(requirements, &compact, "sma", false);
Self::require_stock_rollings_for_named_helper(requirements, &compact, "ma", false);
Self::require_stock_rollings_for_named_helper(
requirements,
&compact,
"rolling_mean_current",
true,
);
Self::require_stock_rollings_for_named_helper(
requirements,
&compact,
"rolling_max_current",
true,
);
Self::require_stock_rollings_for_named_helper(
requirements,
&compact,
"rolling_return_stddev_current",
true,
);
Self::require_stock_rollings_for_vma_helper(requirements, &compact);
}
@@ -9862,6 +10016,7 @@ impl PlatformExprStrategy {
requirements: &mut StockRollingRequirements,
compact: &str,
helper: &str,
current: bool,
) {
let needle = format!("{helper}(");
let mut offset = 0usize;
@@ -9883,11 +10038,19 @@ impl PlatformExprStrategy {
let normalized_field = field.trim().to_ascii_lowercase();
match normalized_field.as_str() {
"close" | "prev_close" | "stock_close" | "price" => {
if current {
requirements.require_current(StockRollingField::Close, lookback)
} else {
requirements.require(StockRollingField::Close, lookback)
}
}
"volume" | "stock_volume" => {
if current {
requirements.require_current(StockRollingField::Volume, lookback)
} else {
requirements.require(StockRollingField::Volume, lookback)
}
}
_ => {}
}
offset = args_start;
@@ -13750,7 +13913,7 @@ mod tests {
}
#[test]
fn current_rolling_helpers_do_not_load_factor_maps_or_decision_rollings() {
fn current_rolling_helpers_load_only_current_standard_rollings() {
let mut cfg = PlatformExprStrategyConfig::microcap_rotation();
cfg.stock_filter_expr = concat!(
"rolling_mean_current(\"close\", 5) > rolling_mean_current(\"close\", 10)",
@@ -13765,6 +13928,18 @@ mod tests {
assert!(!strategy.stock_rolling_requirements.requires("close", 10));
assert!(!strategy.stock_rolling_requirements.requires("volume", 5));
assert!(!strategy.stock_rolling_requirements.requires("volume", 100));
assert!(strategy
.stock_rolling_requirements
.requires_current("close", 5));
assert!(strategy
.stock_rolling_requirements
.requires_current("close", 10));
assert!(strategy
.stock_rolling_requirements
.requires_current("volume", 5));
assert!(strategy
.stock_rolling_requirements
.requires_current("volume", 100));
}
#[test]