支持动态因子缺值安全表达式
This commit is contained in:
@@ -556,6 +556,7 @@ pub struct PlatformExprStrategy {
|
|||||||
selection_quote_usage: StockFilterQuoteUsage,
|
selection_quote_usage: StockFilterQuoteUsage,
|
||||||
stock_rolling_requirements: StockRollingRequirements,
|
stock_rolling_requirements: StockRollingRequirements,
|
||||||
stock_extra_factors_required: bool,
|
stock_extra_factors_required: bool,
|
||||||
|
stock_extra_factor_identifiers: BTreeSet<String>,
|
||||||
stock_text_factors_required: bool,
|
stock_text_factors_required: bool,
|
||||||
stock_state_cache_date: RefCell<Option<NaiveDate>>,
|
stock_state_cache_date: RefCell<Option<NaiveDate>>,
|
||||||
stock_state_cache: RefCell<
|
stock_state_cache: RefCell<
|
||||||
@@ -657,6 +658,11 @@ impl PlatformExprStrategy {
|
|||||||
&normalized_stock_filter_expr,
|
&normalized_stock_filter_expr,
|
||||||
&prelude_declared_identifiers,
|
&prelude_declared_identifiers,
|
||||||
);
|
);
|
||||||
|
let stock_extra_factor_identifiers = Self::stock_extra_factor_identifiers_for_config(
|
||||||
|
&config,
|
||||||
|
&normalized_stock_filter_expr,
|
||||||
|
&prelude_declared_identifiers,
|
||||||
|
);
|
||||||
let stock_text_factors_required = Self::stock_text_factors_required_for_config(
|
let stock_text_factors_required = Self::stock_text_factors_required_for_config(
|
||||||
&config,
|
&config,
|
||||||
&normalized_stock_filter_expr,
|
&normalized_stock_filter_expr,
|
||||||
@@ -681,6 +687,7 @@ impl PlatformExprStrategy {
|
|||||||
selection_quote_usage,
|
selection_quote_usage,
|
||||||
stock_rolling_requirements,
|
stock_rolling_requirements,
|
||||||
stock_extra_factors_required,
|
stock_extra_factors_required,
|
||||||
|
stock_extra_factor_identifiers,
|
||||||
stock_text_factors_required,
|
stock_text_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()),
|
||||||
@@ -3196,7 +3203,9 @@ impl PlatformExprStrategy {
|
|||||||
for identifier in Self::extract_identifier_candidates(&expanded_expr) {
|
for identifier in Self::extract_identifier_candidates(&expanded_expr) {
|
||||||
if Self::is_reserved_scope_name(identifier.as_str())
|
if Self::is_reserved_scope_name(identifier.as_str())
|
||||||
|| self.prelude_declared_identifiers.contains(&identifier)
|
|| self.prelude_declared_identifiers.contains(&identifier)
|
||||||
|| (!day.available_factor_names.contains(&identifier)
|
|| (!self.stock_extra_factor_identifiers.contains(&identifier)
|
||||||
|
&& !item.extra_factors.contains_key(&identifier)
|
||||||
|
&& !day.available_factor_names.contains(&identifier)
|
||||||
&& !day.available_text_factor_names.contains(&identifier))
|
&& !day.available_text_factor_names.contains(&identifier))
|
||||||
{
|
{
|
||||||
continue;
|
continue;
|
||||||
@@ -3204,7 +3213,7 @@ impl PlatformExprStrategy {
|
|||||||
if let Some(value) = item.extra_text_factors.get(&identifier) {
|
if let Some(value) = item.extra_text_factors.get(&identifier) {
|
||||||
scope.push_dynamic(identifier, Dynamic::from(value.clone()));
|
scope.push_dynamic(identifier, Dynamic::from(value.clone()));
|
||||||
} else {
|
} else {
|
||||||
let value = item.extra_factors.get(&identifier).copied().unwrap_or(0.0);
|
let value = item.extra_factors.get(&identifier).copied().unwrap_or(f64::NAN);
|
||||||
scope.push_dynamic(identifier, Dynamic::from(value));
|
scope.push_dynamic(identifier, Dynamic::from(value));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -6423,6 +6432,69 @@ impl PlatformExprStrategy {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn stock_extra_factor_identifiers_for_config(
|
||||||
|
config: &PlatformExprStrategyConfig,
|
||||||
|
normalized_stock_filter_expr: &str,
|
||||||
|
prelude_declared_identifiers: &BTreeSet<String>,
|
||||||
|
) -> BTreeSet<String> {
|
||||||
|
let mut identifiers = BTreeSet::new();
|
||||||
|
if Self::stock_field_may_use_extra_factors(&config.market_cap_field) {
|
||||||
|
identifiers.insert(config.market_cap_field.trim().to_string());
|
||||||
|
}
|
||||||
|
if Self::stock_field_may_use_extra_factors(&config.rank_by) {
|
||||||
|
identifiers.insert(config.rank_by.trim().to_string());
|
||||||
|
}
|
||||||
|
let stock_filter_has_fast_path =
|
||||||
|
Self::stock_filter_fast_path_supported(normalized_stock_filter_expr);
|
||||||
|
if !stock_filter_has_fast_path {
|
||||||
|
Self::collect_stock_extra_factor_identifiers(
|
||||||
|
&mut identifiers,
|
||||||
|
&config.stock_filter_expr,
|
||||||
|
prelude_declared_identifiers,
|
||||||
|
true,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
for expr in [
|
||||||
|
config.buy_scale_expr.as_str(),
|
||||||
|
config.stop_loss_expr.as_str(),
|
||||||
|
config.take_profit_expr.as_str(),
|
||||||
|
config.rank_expr.as_str(),
|
||||||
|
] {
|
||||||
|
Self::collect_stock_extra_factor_identifiers(
|
||||||
|
&mut identifiers,
|
||||||
|
expr,
|
||||||
|
prelude_declared_identifiers,
|
||||||
|
true,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
identifiers
|
||||||
|
}
|
||||||
|
|
||||||
|
fn collect_stock_extra_factor_identifiers(
|
||||||
|
out: &mut BTreeSet<String>,
|
||||||
|
expr: &str,
|
||||||
|
prelude_declared_identifiers: &BTreeSet<String>,
|
||||||
|
stock_rolling_helpers_require_extra: bool,
|
||||||
|
) {
|
||||||
|
let normalized = Self::normalize_expr(expr);
|
||||||
|
for name in Self::extract_identifier_candidates(&normalized) {
|
||||||
|
if Self::is_expression_keyword(&name)
|
||||||
|
|| Self::is_runtime_helper(&name)
|
||||||
|
|| Self::is_reserved_scope_name(&name)
|
||||||
|
|| Self::stock_field_is_builtin(&name)
|
||||||
|
|| prelude_declared_identifiers.contains(&name)
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if stock_rolling_helpers_require_extra
|
||||||
|
&& matches!(name.as_str(), "rolling_mean" | "sma" | "ma" | "vma")
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
out.insert(name);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fn stock_text_factors_required_for_config(
|
fn stock_text_factors_required_for_config(
|
||||||
config: &PlatformExprStrategyConfig,
|
config: &PlatformExprStrategyConfig,
|
||||||
normalized_stock_filter_expr: &str,
|
normalized_stock_filter_expr: &str,
|
||||||
@@ -8213,6 +8285,19 @@ mod tests {
|
|||||||
assert!((strategy.buy_commission(1_000.0) - 5.0).abs() < 1e-9);
|
assert!((strategy.buy_commission(1_000.0) - 5.0).abs() < 1e-9);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn platform_expr_tracks_requested_dynamic_factor_identifiers() {
|
||||||
|
let mut cfg = PlatformExprStrategyConfig::microcap_rotation();
|
||||||
|
cfg.rank_expr = "model_score + close / prev_close".to_string();
|
||||||
|
|
||||||
|
let strategy = PlatformExprStrategy::new(cfg);
|
||||||
|
|
||||||
|
assert!(strategy.stock_extra_factors_required);
|
||||||
|
assert!(strategy.stock_extra_factor_identifiers.contains("model_score"));
|
||||||
|
assert!(!strategy.stock_extra_factor_identifiers.contains("close"));
|
||||||
|
assert!(!strategy.stock_extra_factor_identifiers.contains("prev_close"));
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn platform_expr_rewrites_prelude_assignment_ternary() {
|
fn platform_expr_rewrites_prelude_assignment_ternary() {
|
||||||
let prelude = "let cap_low = sig_close <= 0 ? 7 : max(5, min(cap_low_raw, 70));";
|
let prelude = "let cap_low = sig_close <= 0 ? 7 : max(5, min(cap_low_raw, 70));";
|
||||||
|
|||||||
Reference in New Issue
Block a user