按需保留股票额外因子状态
This commit is contained in:
@@ -554,6 +554,7 @@ pub struct PlatformExprStrategy {
|
|||||||
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_rolling_requirements: StockRollingRequirements,
|
||||||
|
stock_extra_factors_required: bool,
|
||||||
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>>,
|
||||||
@@ -646,6 +647,11 @@ impl PlatformExprStrategy {
|
|||||||
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 =
|
let stock_rolling_requirements =
|
||||||
Self::stock_rolling_requirements_for_config(&config, &normalized_stock_filter_expr);
|
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 {
|
Self {
|
||||||
config,
|
config,
|
||||||
engine,
|
engine,
|
||||||
@@ -663,6 +669,7 @@ impl PlatformExprStrategy {
|
|||||||
compact_stock_filter_expr,
|
compact_stock_filter_expr,
|
||||||
stock_filter_quote_usage,
|
stock_filter_quote_usage,
|
||||||
stock_rolling_requirements,
|
stock_rolling_requirements,
|
||||||
|
stock_extra_factors_required,
|
||||||
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()),
|
||||||
}
|
}
|
||||||
@@ -2461,7 +2468,11 @@ impl PlatformExprStrategy {
|
|||||||
stock_volume_ma20,
|
stock_volume_ma20,
|
||||||
stock_volume_ma60,
|
stock_volume_ma60,
|
||||||
stock_volume_ma100,
|
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
|
extra_text_factors: ctx
|
||||||
.data
|
.data
|
||||||
.factor_text_snapshots_on(date)
|
.factor_text_snapshots_on(date)
|
||||||
@@ -6291,6 +6302,152 @@ impl PlatformExprStrategy {
|
|||||||
requirements
|
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(
|
fn require_stock_rollings_for_identifiers(
|
||||||
requirements: &mut StockRollingRequirements,
|
requirements: &mut StockRollingRequirements,
|
||||||
config: &PlatformExprStrategyConfig,
|
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 {
|
fn stock_filter_uses_intraday_quote_fields(&self) -> bool {
|
||||||
self.stock_filter_quote_usage() != StockFilterQuoteUsage::DailyOnly
|
self.stock_filter_quote_usage() != StockFilterQuoteUsage::DailyOnly
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user