按表达式裁剪股票rolling状态
This commit is contained in:
@@ -442,6 +442,42 @@ enum StockFilterQuoteUsage {
|
|||||||
IntradayQuote,
|
IntradayQuote,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
|
||||||
|
enum StockRollingField {
|
||||||
|
Close,
|
||||||
|
Volume,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Default)]
|
||||||
|
struct StockRollingRequirements {
|
||||||
|
all: bool,
|
||||||
|
fields: BTreeSet<(StockRollingField, usize)>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl StockRollingRequirements {
|
||||||
|
fn require(&mut self, field: StockRollingField, lookback: usize) {
|
||||||
|
if lookback > 0 {
|
||||||
|
self.fields.insert((field, lookback));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn require_all(&mut self) {
|
||||||
|
self.all = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
fn requires(&self, field: &'static str, lookback: usize) -> bool {
|
||||||
|
if self.all {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
let field = match field {
|
||||||
|
"close" => StockRollingField::Close,
|
||||||
|
"volume" => StockRollingField::Volume,
|
||||||
|
_ => return false,
|
||||||
|
};
|
||||||
|
self.fields.contains(&(field, lookback))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
struct PositionExpressionState {
|
struct PositionExpressionState {
|
||||||
order_book_id: String,
|
order_book_id: String,
|
||||||
@@ -517,6 +553,7 @@ pub struct PlatformExprStrategy {
|
|||||||
prelude_numeric_constants: HashMap<String, f64>,
|
prelude_numeric_constants: HashMap<String, f64>,
|
||||||
compact_stock_filter_expr: String,
|
compact_stock_filter_expr: String,
|
||||||
stock_filter_quote_usage: StockFilterQuoteUsage,
|
stock_filter_quote_usage: StockFilterQuoteUsage,
|
||||||
|
stock_rolling_requirements: StockRollingRequirements,
|
||||||
stock_state_cache_date: RefCell<Option<NaiveDate>>,
|
stock_state_cache_date: RefCell<Option<NaiveDate>>,
|
||||||
stock_state_cache:
|
stock_state_cache:
|
||||||
RefCell<HashMap<(NaiveDate, NaiveDate, String, Option<NaiveTime>), StockExpressionState>>,
|
RefCell<HashMap<(NaiveDate, NaiveDate, String, Option<NaiveTime>), StockExpressionState>>,
|
||||||
@@ -607,6 +644,8 @@ impl PlatformExprStrategy {
|
|||||||
let compact_stock_filter_expr = Self::compact_expr(&normalized_stock_filter_expr);
|
let compact_stock_filter_expr = Self::compact_expr(&normalized_stock_filter_expr);
|
||||||
let stock_filter_quote_usage =
|
let stock_filter_quote_usage =
|
||||||
Self::stock_filter_quote_usage_for_expr(&normalized_stock_filter_expr);
|
Self::stock_filter_quote_usage_for_expr(&normalized_stock_filter_expr);
|
||||||
|
let stock_rolling_requirements =
|
||||||
|
Self::stock_rolling_requirements_for_config(&config, &normalized_stock_filter_expr);
|
||||||
Self {
|
Self {
|
||||||
config,
|
config,
|
||||||
engine,
|
engine,
|
||||||
@@ -623,6 +662,7 @@ impl PlatformExprStrategy {
|
|||||||
prelude_numeric_constants,
|
prelude_numeric_constants,
|
||||||
compact_stock_filter_expr,
|
compact_stock_filter_expr,
|
||||||
stock_filter_quote_usage,
|
stock_filter_quote_usage,
|
||||||
|
stock_rolling_requirements,
|
||||||
stock_state_cache_date: RefCell::new(None),
|
stock_state_cache_date: RefCell::new(None),
|
||||||
stock_state_cache: RefCell::new(HashMap::new()),
|
stock_state_cache: RefCell::new(HashMap::new()),
|
||||||
}
|
}
|
||||||
@@ -2266,6 +2306,9 @@ impl PlatformExprStrategy {
|
|||||||
let instrument = ctx.data.instrument(symbol);
|
let instrument = ctx.data.instrument(symbol);
|
||||||
let mut rolling_values = HashMap::<(&'static str, usize), f64>::new();
|
let mut rolling_values = HashMap::<(&'static str, usize), f64>::new();
|
||||||
let mut rolling = |field: &'static str, lookback: usize| -> f64 {
|
let mut rolling = |field: &'static str, lookback: usize| -> f64 {
|
||||||
|
if !self.stock_rolling_requirements.requires(field, lookback) {
|
||||||
|
return f64::NAN;
|
||||||
|
}
|
||||||
*rolling_values.entry((field, lookback)).or_insert_with(|| {
|
*rolling_values.entry((field, lookback)).or_insert_with(|| {
|
||||||
self.stock_decision_rolling_mean(
|
self.stock_decision_rolling_mean(
|
||||||
ctx,
|
ctx,
|
||||||
@@ -6189,6 +6232,113 @@ impl PlatformExprStrategy {
|
|||||||
usage
|
usage
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn stock_rolling_requirements_for_config(
|
||||||
|
config: &PlatformExprStrategyConfig,
|
||||||
|
normalized_stock_filter_expr: &str,
|
||||||
|
) -> StockRollingRequirements {
|
||||||
|
let mut requirements = StockRollingRequirements::default();
|
||||||
|
let expressions = [
|
||||||
|
config.prelude.as_str(),
|
||||||
|
config.stock_filter_expr.as_str(),
|
||||||
|
config.buy_scale_expr.as_str(),
|
||||||
|
config.stop_loss_expr.as_str(),
|
||||||
|
config.take_profit_expr.as_str(),
|
||||||
|
config.rank_expr.as_str(),
|
||||||
|
config.market_cap_field.as_str(),
|
||||||
|
];
|
||||||
|
for expr in expressions {
|
||||||
|
let normalized = Self::normalize_expr(expr);
|
||||||
|
if Self::extract_identifier_candidates(&normalized).contains("factors") {
|
||||||
|
requirements.require_all();
|
||||||
|
return requirements;
|
||||||
|
}
|
||||||
|
Self::require_stock_rollings_for_identifiers(&mut requirements, config, &normalized);
|
||||||
|
}
|
||||||
|
Self::require_stock_rollings_for_fast_filter(
|
||||||
|
&mut requirements,
|
||||||
|
config,
|
||||||
|
normalized_stock_filter_expr,
|
||||||
|
);
|
||||||
|
requirements
|
||||||
|
}
|
||||||
|
|
||||||
|
fn require_stock_rollings_for_identifiers(
|
||||||
|
requirements: &mut StockRollingRequirements,
|
||||||
|
config: &PlatformExprStrategyConfig,
|
||||||
|
expr: &str,
|
||||||
|
) {
|
||||||
|
for name in Self::extract_identifier_candidates(expr) {
|
||||||
|
match name.as_str() {
|
||||||
|
"stock_ma_short" => {
|
||||||
|
requirements.require(StockRollingField::Close, config.stock_short_ma_days)
|
||||||
|
}
|
||||||
|
"stock_ma_mid" => {
|
||||||
|
requirements.require(StockRollingField::Close, config.stock_mid_ma_days)
|
||||||
|
}
|
||||||
|
"stock_ma_long" => {
|
||||||
|
requirements.require(StockRollingField::Close, config.stock_long_ma_days)
|
||||||
|
}
|
||||||
|
"stock_ma5" | "ma5" => requirements.require(StockRollingField::Close, 5),
|
||||||
|
"stock_ma10" | "ma10" => requirements.require(StockRollingField::Close, 10),
|
||||||
|
"stock_ma20" | "ma20" => requirements.require(StockRollingField::Close, 20),
|
||||||
|
"stock_ma30" | "ma30" => requirements.require(StockRollingField::Close, 30),
|
||||||
|
"stock_volume_ma5" | "volume_ma5" | "vma5" => {
|
||||||
|
requirements.require(StockRollingField::Volume, 5)
|
||||||
|
}
|
||||||
|
"stock_volume_ma10" | "volume_ma10" | "vma10" => {
|
||||||
|
requirements.require(StockRollingField::Volume, 10)
|
||||||
|
}
|
||||||
|
"stock_volume_ma20" | "volume_ma20" | "vma20" => {
|
||||||
|
requirements.require(StockRollingField::Volume, 20)
|
||||||
|
}
|
||||||
|
"stock_volume_ma60" | "volume_ma60" | "vma60" => {
|
||||||
|
requirements.require(StockRollingField::Volume, 60)
|
||||||
|
}
|
||||||
|
"stock_volume_ma100" | "volume_ma100" | "vma100" => {
|
||||||
|
requirements.require(StockRollingField::Volume, 100)
|
||||||
|
}
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn require_stock_rollings_for_fast_filter(
|
||||||
|
requirements: &mut StockRollingRequirements,
|
||||||
|
config: &PlatformExprStrategyConfig,
|
||||||
|
normalized_stock_filter_expr: &str,
|
||||||
|
) {
|
||||||
|
let compact = Self::compact_expr(normalized_stock_filter_expr);
|
||||||
|
if compact == "stock_ma_short>stock_ma_mid*ma_ratio&&stock_ma_mid>stock_ma_long" {
|
||||||
|
requirements.require(StockRollingField::Close, config.stock_short_ma_days);
|
||||||
|
requirements.require(StockRollingField::Close, config.stock_mid_ma_days);
|
||||||
|
requirements.require(StockRollingField::Close, config.stock_long_ma_days);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut filter_body = compact.as_str();
|
||||||
|
if let Some(rest) = filter_body.strip_prefix("listed_days>=min_listed_days&&") {
|
||||||
|
filter_body = rest;
|
||||||
|
}
|
||||||
|
let base_microcap_filter = "rolling_mean(\"close\",5)>rolling_mean(\"close\",10)*ma_ratio&&rolling_mean(\"close\",10)>rolling_mean(\"close\",30)*ma_ratio&&rolling_mean(\"volume\",5)<rolling_mean(\"volume\",100)*max_volume_ratio";
|
||||||
|
let matches_microcap_fast_filter = filter_body == base_microcap_filter
|
||||||
|
|| filter_body
|
||||||
|
.strip_prefix(base_microcap_filter)
|
||||||
|
.is_some_and(|tail| {
|
||||||
|
matches!(
|
||||||
|
tail,
|
||||||
|
"&&rolling_mean(\"volume\",5)>0&&rolling_mean(\"volume\",100)>0"
|
||||||
|
| "&&rolling_mean(\"volume\",5)>0.0&&rolling_mean(\"volume\",100)>0.0"
|
||||||
|
)
|
||||||
|
});
|
||||||
|
if matches_microcap_fast_filter {
|
||||||
|
requirements.require(StockRollingField::Close, 5);
|
||||||
|
requirements.require(StockRollingField::Close, 10);
|
||||||
|
requirements.require(StockRollingField::Close, 30);
|
||||||
|
requirements.require(StockRollingField::Volume, 5);
|
||||||
|
requirements.require(StockRollingField::Volume, 100);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fn stock_filter_uses_intraday_quote_fields(&self) -> bool {
|
fn stock_filter_uses_intraday_quote_fields(&self) -> bool {
|
||||||
self.stock_filter_quote_usage() != StockFilterQuoteUsage::DailyOnly
|
self.stock_filter_quote_usage() != StockFilterQuoteUsage::DailyOnly
|
||||||
}
|
}
|
||||||
@@ -18735,6 +18885,7 @@ mod tests {
|
|||||||
};
|
};
|
||||||
let mut cfg = PlatformExprStrategyConfig::microcap_rotation();
|
let mut cfg = PlatformExprStrategyConfig::microcap_rotation();
|
||||||
cfg.prefer_precomputed_rolling_factors = true;
|
cfg.prefer_precomputed_rolling_factors = true;
|
||||||
|
cfg.stock_filter_expr = "stock_ma5 > 0 && stock_volume_ma5 > 0".to_string();
|
||||||
let strategy = PlatformExprStrategy::new(cfg);
|
let strategy = PlatformExprStrategy::new(cfg);
|
||||||
let stock = strategy
|
let stock = strategy
|
||||||
.stock_state_with_factor_date(&ctx, date, date, symbol)
|
.stock_state_with_factor_date(&ctx, date, date, symbol)
|
||||||
@@ -18742,7 +18893,9 @@ mod tests {
|
|||||||
assert_eq!(stock.stock_ma5, 99.0);
|
assert_eq!(stock.stock_ma5, 99.0);
|
||||||
assert_eq!(stock.stock_volume_ma5, 88.0);
|
assert_eq!(stock.stock_volume_ma5, 88.0);
|
||||||
|
|
||||||
let strategy = PlatformExprStrategy::new(PlatformExprStrategyConfig::microcap_rotation());
|
let mut cfg = PlatformExprStrategyConfig::microcap_rotation();
|
||||||
|
cfg.stock_filter_expr = "stock_ma5 > 0 && stock_volume_ma5 > 0".to_string();
|
||||||
|
let strategy = PlatformExprStrategy::new(cfg);
|
||||||
let stock = strategy
|
let stock = strategy
|
||||||
.stock_state_with_factor_date(&ctx, date, date, symbol)
|
.stock_state_with_factor_date(&ctx, date, date, symbol)
|
||||||
.expect("stock state");
|
.expect("stock state");
|
||||||
|
|||||||
Reference in New Issue
Block a user