From 41237dccfd05c75a4006fc42ce9e9b2ec5cbdfd9 Mon Sep 17 00:00:00 2001 From: boris Date: Sat, 27 Jun 2026 07:56:29 +0800 Subject: [PATCH] =?UTF-8?q?=E8=A1=A5=E5=85=85=E5=8A=A8=E6=80=81=E5=9B=A0?= =?UTF-8?q?=E5=AD=90=E7=BC=BA=E5=A4=B1=E5=9B=9E=E5=BD=92=E6=B5=8B=E8=AF=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../fidc-core/src/platform_expr_strategy.rs | 135 ++++++++++++++++++ 1 file changed, 135 insertions(+) diff --git a/crates/fidc-core/src/platform_expr_strategy.rs b/crates/fidc-core/src/platform_expr_strategy.rs index fde2659..012d062 100644 --- a/crates/fidc-core/src/platform_expr_strategy.rs +++ b/crates/fidc-core/src/platform_expr_strategy.rs @@ -8298,6 +8298,141 @@ mod tests { assert!(!strategy.stock_extra_factor_identifiers.contains("prev_close")); } + #[test] + fn platform_expr_missing_requested_factor_does_not_default_to_zero() { + let date = d(2025, 5, 19); + let missing_symbol = "000001.SZ"; + let present_symbol = "000002.SZ"; + let symbols = [missing_symbol, present_symbol]; + let data = DataSet::from_components( + symbols + .iter() + .map(|symbol| Instrument { + symbol: (*symbol).to_string(), + name: (*symbol).to_string(), + board: "SZ".to_string(), + round_lot: 100, + listed_at: Some(d(2020, 1, 1)), + delisted_at: None, + status: "active".to_string(), + }) + .collect(), + symbols + .iter() + .map(|symbol| DailyMarketSnapshot { + date, + symbol: (*symbol).to_string(), + timestamp: Some("2025-05-19 10:18:00".to_string()), + day_open: 10.0, + open: 10.0, + high: 10.5, + low: 9.8, + close: 10.0, + last_price: 10.0, + bid1: 9.99, + ask1: 10.01, + prev_close: 9.9, + volume: 1_000_000, + minute_volume: 10_000, + bid1_volume: 2_000, + ask1_volume: 2_000, + trading_phase: Some("continuous".to_string()), + paused: false, + upper_limit: 11.0, + lower_limit: 9.0, + price_tick: 0.01, + }) + .collect(), + vec![ + DailyFactorSnapshot { + date, + symbol: missing_symbol.to_string(), + market_cap_bn: 12.0, + free_float_cap_bn: 10.0, + pe_ttm: 8.0, + turnover_ratio: Some(1.0), + effective_turnover_ratio: Some(1.0), + extra_factors: BTreeMap::new(), + }, + DailyFactorSnapshot { + date, + symbol: present_symbol.to_string(), + market_cap_bn: 12.0, + free_float_cap_bn: 10.0, + pe_ttm: 8.0, + turnover_ratio: Some(1.0), + effective_turnover_ratio: Some(1.0), + extra_factors: BTreeMap::from([("model_score".to_string(), 2.0)]), + }, + ], + symbols + .iter() + .map(|symbol| CandidateEligibility { + date, + symbol: (*symbol).to_string(), + is_st: false, + is_new_listing: false, + is_paused: false, + allow_buy: true, + allow_sell: true, + is_kcb: false, + is_one_yuan: false, + risk_level_code: None, + }) + .collect(), + vec![BenchmarkSnapshot { + date, + benchmark: "000852.SH".to_string(), + open: 1000.0, + close: 1002.0, + prev_close: 998.0, + volume: 1_000_000, + }], + ) + .expect("dataset"); + let portfolio = PortfolioState::new(1_000_000.0); + let subscriptions = BTreeSet::new(); + let ctx = StrategyContext { + execution_date: date, + decision_date: date, + decision_index: 100, + data: &data, + portfolio: &portfolio, + futures_account: None, + open_orders: &[], + dynamic_universe: None, + subscriptions: &subscriptions, + process_events: &[], + active_process_event: None, + active_datetime: None, + order_events: &[], + fills: &[], + }; + let mut cfg = PlatformExprStrategyConfig::microcap_rotation(); + cfg.signal_symbol = missing_symbol.to_string(); + cfg.stock_filter_expr = "model_score < 1.0".to_string(); + let strategy = PlatformExprStrategy::new(cfg); + let day = strategy.day_state(&ctx, date).expect("day state"); + let missing_stock = strategy + .stock_state_with_factor_date(&ctx, date, date, missing_symbol) + .expect("missing stock state"); + let present_stock = strategy + .stock_state_with_factor_date(&ctx, date, date, present_symbol) + .expect("present stock state"); + + assert!(day.available_factor_names.contains("model_score")); + assert!( + !strategy + .stock_passes_expr(&ctx, &day, &missing_stock) + .expect("missing stock filter") + ); + assert!( + !strategy + .stock_passes_expr(&ctx, &day, &present_stock) + .expect("present stock filter") + ); + } + #[test] fn platform_expr_rewrites_prelude_assignment_ternary() { let prelude = "let cap_low = sig_close <= 0 ? 7 : max(5, min(cap_low_raw, 70));";