复用当前时点标准rolling值
This commit is contained in:
@@ -706,10 +706,59 @@ struct StockExpressionState {
|
|||||||
stock_volume_ma20: f64,
|
stock_volume_ma20: f64,
|
||||||
stock_volume_ma60: f64,
|
stock_volume_ma60: f64,
|
||||||
stock_volume_ma100: 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_factors: BTreeMap<String, f64>,
|
||||||
extra_text_factors: BTreeMap<String, String>,
|
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)]
|
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||||
enum StockFilterQuoteUsage {
|
enum StockFilterQuoteUsage {
|
||||||
DailyOnly,
|
DailyOnly,
|
||||||
@@ -727,6 +776,7 @@ enum StockRollingField {
|
|||||||
struct StockRollingRequirements {
|
struct StockRollingRequirements {
|
||||||
all: bool,
|
all: bool,
|
||||||
fields: BTreeSet<(StockRollingField, usize)>,
|
fields: BTreeSet<(StockRollingField, usize)>,
|
||||||
|
current_fields: BTreeSet<(StockRollingField, usize)>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl StockRollingRequirements {
|
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) {
|
fn require_all(&mut self) {
|
||||||
self.all = true;
|
self.all = true;
|
||||||
}
|
}
|
||||||
@@ -751,6 +807,19 @@ impl StockRollingRequirements {
|
|||||||
};
|
};
|
||||||
self.fields.contains(&(field, lookback))
|
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)]
|
#[derive(Debug, Clone)]
|
||||||
@@ -3943,6 +4012,12 @@ impl PlatformExprStrategy {
|
|||||||
.then_some(lookback)
|
.then_some(lookback)
|
||||||
.unwrap_or(0)
|
.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 = [
|
let close_lookbacks = [
|
||||||
required_rolling("close", self.config.stock_short_ma_days),
|
required_rolling("close", self.config.stock_short_ma_days),
|
||||||
required_rolling("close", self.config.stock_mid_ma_days),
|
required_rolling("close", self.config.stock_mid_ma_days),
|
||||||
@@ -3959,6 +4034,22 @@ impl PlatformExprStrategy {
|
|||||||
required_rolling("volume", 60),
|
required_rolling("volume", 60),
|
||||||
required_rolling("volume", 100),
|
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(
|
let rolling_means = ctx.data.market_standard_rolling_means_by_symbol_id(
|
||||||
date,
|
date,
|
||||||
symbol_id,
|
symbol_id,
|
||||||
@@ -3966,8 +4057,26 @@ impl PlatformExprStrategy {
|
|||||||
&volume_lookbacks,
|
&volume_lookbacks,
|
||||||
false,
|
false,
|
||||||
);
|
);
|
||||||
|
let current_rolling_means = if self.stock_rolling_requirements.has_current() {
|
||||||
|
ctx.data.market_standard_rolling_means_by_symbol_id(
|
||||||
|
date,
|
||||||
|
symbol_id,
|
||||||
|
¤t_close_lookbacks,
|
||||||
|
¤t_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 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 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_short = close_rolling(0);
|
||||||
let stock_ma_mid = close_rolling(1);
|
let stock_ma_mid = close_rolling(1);
|
||||||
let stock_ma_long = close_rolling(2);
|
let stock_ma_long = close_rolling(2);
|
||||||
@@ -3980,6 +4089,15 @@ impl PlatformExprStrategy {
|
|||||||
let stock_volume_ma20 = volume_rolling(2);
|
let stock_volume_ma20 = volume_rolling(2);
|
||||||
let stock_volume_ma60 = volume_rolling(3);
|
let stock_volume_ma60 = volume_rolling(3);
|
||||||
let stock_volume_ma100 = volume_rolling(4);
|
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 {
|
let touched_upper_limit = if intraday_same_day_factor {
|
||||||
!market.paused
|
!market.paused
|
||||||
&& (market.is_at_upper_limit_price(market.close)
|
&& (market.is_at_upper_limit_price(market.close)
|
||||||
@@ -4126,6 +4244,15 @@ impl PlatformExprStrategy {
|
|||||||
stock_volume_ma20,
|
stock_volume_ma20,
|
||||||
stock_volume_ma60,
|
stock_volume_ma60,
|
||||||
stock_volume_ma100,
|
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_factors,
|
||||||
extra_text_factors: if self.stock_text_factors_required {
|
extra_text_factors: if self.stock_text_factors_required {
|
||||||
ctx.data
|
ctx.data
|
||||||
@@ -6700,14 +6827,16 @@ impl PlatformExprStrategy {
|
|||||||
"rolling_mean(\"{other}\", {lookback}) requires stock context"
|
"rolling_mean(\"{other}\", {lookback}) requires stock context"
|
||||||
))
|
))
|
||||||
})?;
|
})?;
|
||||||
self.stock_decision_rolling_mean(
|
stock.standard_rolling_mean(other, lookback, false).or_else(|| {
|
||||||
ctx,
|
self.stock_decision_rolling_mean(
|
||||||
day.date,
|
ctx,
|
||||||
stock.symbol_id,
|
day.date,
|
||||||
&stock.symbol,
|
stock.symbol_id,
|
||||||
other,
|
&stock.symbol,
|
||||||
lookback,
|
other,
|
||||||
)
|
lookback,
|
||||||
|
)
|
||||||
|
})
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
value.ok_or_else(|| {
|
value.ok_or_else(|| {
|
||||||
@@ -6749,14 +6878,16 @@ impl PlatformExprStrategy {
|
|||||||
"rolling_mean_current(\"{other}\", {lookback}) requires stock context"
|
"rolling_mean_current(\"{other}\", {lookback}) requires stock context"
|
||||||
))
|
))
|
||||||
})?;
|
})?;
|
||||||
self.stock_current_rolling_mean(
|
stock.standard_rolling_mean(other, lookback, true).or_else(|| {
|
||||||
ctx,
|
self.stock_current_rolling_mean(
|
||||||
day.date,
|
ctx,
|
||||||
stock.symbol_id,
|
day.date,
|
||||||
&stock.symbol,
|
stock.symbol_id,
|
||||||
other,
|
&stock.symbol,
|
||||||
lookback,
|
other,
|
||||||
)
|
lookback,
|
||||||
|
)
|
||||||
|
})
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
value.ok_or_else(|| {
|
value.ok_or_else(|| {
|
||||||
@@ -9852,9 +9983,32 @@ impl PlatformExprStrategy {
|
|||||||
expr: &str,
|
expr: &str,
|
||||||
) {
|
) {
|
||||||
let compact = Self::compact_expr(expr);
|
let compact = Self::compact_expr(expr);
|
||||||
Self::require_stock_rollings_for_named_helper(requirements, &compact, "rolling_mean");
|
Self::require_stock_rollings_for_named_helper(
|
||||||
Self::require_stock_rollings_for_named_helper(requirements, &compact, "sma");
|
requirements,
|
||||||
Self::require_stock_rollings_for_named_helper(requirements, &compact, "ma");
|
&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);
|
Self::require_stock_rollings_for_vma_helper(requirements, &compact);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -9862,6 +10016,7 @@ impl PlatformExprStrategy {
|
|||||||
requirements: &mut StockRollingRequirements,
|
requirements: &mut StockRollingRequirements,
|
||||||
compact: &str,
|
compact: &str,
|
||||||
helper: &str,
|
helper: &str,
|
||||||
|
current: bool,
|
||||||
) {
|
) {
|
||||||
let needle = format!("{helper}(");
|
let needle = format!("{helper}(");
|
||||||
let mut offset = 0usize;
|
let mut offset = 0usize;
|
||||||
@@ -9883,10 +10038,18 @@ impl PlatformExprStrategy {
|
|||||||
let normalized_field = field.trim().to_ascii_lowercase();
|
let normalized_field = field.trim().to_ascii_lowercase();
|
||||||
match normalized_field.as_str() {
|
match normalized_field.as_str() {
|
||||||
"close" | "prev_close" | "stock_close" | "price" => {
|
"close" | "prev_close" | "stock_close" | "price" => {
|
||||||
requirements.require(StockRollingField::Close, lookback)
|
if current {
|
||||||
|
requirements.require_current(StockRollingField::Close, lookback)
|
||||||
|
} else {
|
||||||
|
requirements.require(StockRollingField::Close, lookback)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
"volume" | "stock_volume" => {
|
"volume" | "stock_volume" => {
|
||||||
requirements.require(StockRollingField::Volume, lookback)
|
if current {
|
||||||
|
requirements.require_current(StockRollingField::Volume, lookback)
|
||||||
|
} else {
|
||||||
|
requirements.require(StockRollingField::Volume, lookback)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
_ => {}
|
_ => {}
|
||||||
}
|
}
|
||||||
@@ -13750,7 +13913,7 @@ mod tests {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[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();
|
let mut cfg = PlatformExprStrategyConfig::microcap_rotation();
|
||||||
cfg.stock_filter_expr = concat!(
|
cfg.stock_filter_expr = concat!(
|
||||||
"rolling_mean_current(\"close\", 5) > rolling_mean_current(\"close\", 10)",
|
"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("close", 10));
|
||||||
assert!(!strategy.stock_rolling_requirements.requires("volume", 5));
|
assert!(!strategy.stock_rolling_requirements.requires("volume", 5));
|
||||||
assert!(!strategy.stock_rolling_requirements.requires("volume", 100));
|
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]
|
#[test]
|
||||||
|
|||||||
Reference in New Issue
Block a user