按需保留股票额外因子状态
This commit is contained in:
@@ -554,6 +554,7 @@ pub struct PlatformExprStrategy {
|
||||
compact_stock_filter_expr: String,
|
||||
stock_filter_quote_usage: StockFilterQuoteUsage,
|
||||
stock_rolling_requirements: StockRollingRequirements,
|
||||
stock_extra_factors_required: bool,
|
||||
stock_state_cache_date: RefCell<Option<NaiveDate>>,
|
||||
stock_state_cache:
|
||||
RefCell<HashMap<(NaiveDate, NaiveDate, String, Option<NaiveTime>), StockExpressionState>>,
|
||||
@@ -646,6 +647,11 @@ impl PlatformExprStrategy {
|
||||
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);
|
||||
let stock_extra_factors_required = Self::stock_extra_factors_required_for_config(
|
||||
&config,
|
||||
&normalized_stock_filter_expr,
|
||||
&prelude_declared_identifiers,
|
||||
);
|
||||
Self {
|
||||
config,
|
||||
engine,
|
||||
@@ -663,6 +669,7 @@ impl PlatformExprStrategy {
|
||||
compact_stock_filter_expr,
|
||||
stock_filter_quote_usage,
|
||||
stock_rolling_requirements,
|
||||
stock_extra_factors_required,
|
||||
stock_state_cache_date: RefCell::new(None),
|
||||
stock_state_cache: RefCell::new(HashMap::new()),
|
||||
}
|
||||
@@ -2461,7 +2468,11 @@ impl PlatformExprStrategy {
|
||||
stock_volume_ma20,
|
||||
stock_volume_ma60,
|
||||
stock_volume_ma100,
|
||||
extra_factors: factor.extra_factors.clone(),
|
||||
extra_factors: if self.stock_extra_factors_required {
|
||||
factor.extra_factors.clone()
|
||||
} else {
|
||||
BTreeMap::new()
|
||||
},
|
||||
extra_text_factors: ctx
|
||||
.data
|
||||
.factor_text_snapshots_on(date)
|
||||
@@ -6291,6 +6302,152 @@ impl PlatformExprStrategy {
|
||||
requirements
|
||||
}
|
||||
|
||||
fn stock_extra_factors_required_for_config(
|
||||
config: &PlatformExprStrategyConfig,
|
||||
normalized_stock_filter_expr: &str,
|
||||
prelude_declared_identifiers: &BTreeSet<String>,
|
||||
) -> bool {
|
||||
if !config.explicit_actions.is_empty() {
|
||||
return true;
|
||||
}
|
||||
if Self::stock_field_may_use_extra_factors(&config.market_cap_field)
|
||||
|| Self::stock_field_may_use_extra_factors(&config.rank_by)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
let stock_filter_has_fast_path =
|
||||
Self::stock_filter_fast_path_supported(normalized_stock_filter_expr);
|
||||
if !stock_filter_has_fast_path
|
||||
&& Self::expr_requires_stock_extra_factors(
|
||||
&config.stock_filter_expr,
|
||||
prelude_declared_identifiers,
|
||||
true,
|
||||
)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
[
|
||||
config.buy_scale_expr.as_str(),
|
||||
config.stop_loss_expr.as_str(),
|
||||
config.take_profit_expr.as_str(),
|
||||
config.rank_expr.as_str(),
|
||||
]
|
||||
.into_iter()
|
||||
.any(|expr| {
|
||||
Self::expr_requires_stock_extra_factors(expr, prelude_declared_identifiers, true)
|
||||
})
|
||||
}
|
||||
|
||||
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" | "sma" | "ma" | "vma"))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
identifiers.into_iter().any(|name| {
|
||||
!Self::is_expression_keyword(&name)
|
||||
&& !Self::is_runtime_helper(&name)
|
||||
&& !Self::is_reserved_scope_name(&name)
|
||||
&& !prelude_declared_identifiers.contains(&name)
|
||||
})
|
||||
}
|
||||
|
||||
fn is_expression_keyword(name: &str) -> bool {
|
||||
matches!(
|
||||
name,
|
||||
"true" | "false" | "let" | "if" | "else" | "return" | "and" | "or" | "not"
|
||||
)
|
||||
}
|
||||
|
||||
fn stock_field_may_use_extra_factors(field: &str) -> bool {
|
||||
let field = field.trim();
|
||||
if field.is_empty() {
|
||||
return false;
|
||||
}
|
||||
!Self::stock_field_is_builtin(field)
|
||||
}
|
||||
|
||||
fn stock_field_is_builtin(field: &str) -> bool {
|
||||
matches!(
|
||||
field.trim(),
|
||||
"market_cap"
|
||||
| "market_cap_bn"
|
||||
| "free_float_cap"
|
||||
| "free_float_market_cap"
|
||||
| "free_float_cap_bn"
|
||||
| "pe_ttm"
|
||||
| "volume"
|
||||
| "tick_volume"
|
||||
| "bid1_volume"
|
||||
| "ask1_volume"
|
||||
| "turnover"
|
||||
| "turnover_ratio"
|
||||
| "effective_turnover_ratio"
|
||||
| "open"
|
||||
| "high"
|
||||
| "low"
|
||||
| "close"
|
||||
| "last"
|
||||
| "last_price"
|
||||
| "prev_close"
|
||||
| "amount"
|
||||
| "upper_limit"
|
||||
| "lower_limit"
|
||||
| "price_tick"
|
||||
| "round_lot"
|
||||
| "minimum_order_quantity"
|
||||
| "order_step_size"
|
||||
| "listed_days"
|
||||
| "stock_ma_short"
|
||||
| "stock_ma_mid"
|
||||
| "stock_ma_long"
|
||||
| "stock_ma5"
|
||||
| "stock_ma10"
|
||||
| "stock_ma20"
|
||||
| "stock_ma30"
|
||||
| "ma5"
|
||||
| "ma10"
|
||||
| "ma20"
|
||||
| "ma30"
|
||||
| "stock_volume_ma5"
|
||||
| "stock_volume_ma10"
|
||||
| "stock_volume_ma20"
|
||||
| "stock_volume_ma60"
|
||||
| "stock_volume_ma100"
|
||||
| "volume_ma5"
|
||||
| "volume_ma10"
|
||||
| "volume_ma20"
|
||||
| "volume_ma60"
|
||||
| "volume_ma100"
|
||||
| "allow_buy"
|
||||
| "allow_sell"
|
||||
| "touched_upper_limit"
|
||||
| "hit_upper_limit"
|
||||
| "touched_lower_limit"
|
||||
| "hit_lower_limit"
|
||||
| "paused"
|
||||
| "is_st"
|
||||
| "is_kcb"
|
||||
| "is_one_yuan"
|
||||
| "is_new_listing"
|
||||
| "candidate_market_cap"
|
||||
| "candidate_market_cap_bn"
|
||||
| "candidate_free_float_cap"
|
||||
| "candidate_free_float_cap_bn"
|
||||
)
|
||||
}
|
||||
|
||||
fn require_stock_rollings_for_identifiers(
|
||||
requirements: &mut StockRollingRequirements,
|
||||
config: &PlatformExprStrategyConfig,
|
||||
@@ -6368,6 +6525,28 @@ impl PlatformExprStrategy {
|
||||
}
|
||||
}
|
||||
|
||||
fn stock_filter_fast_path_supported(normalized_stock_filter_expr: &str) -> bool {
|
||||
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" {
|
||||
return true;
|
||||
}
|
||||
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";
|
||||
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"
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
fn stock_filter_uses_intraday_quote_fields(&self) -> bool {
|
||||
self.stock_filter_quote_usage() != StockFilterQuoteUsage::DailyOnly
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user