From 8ba4b4d2c1a602c4a7caf2fe671fec92265296f1 Mon Sep 17 00:00:00 2001 From: boris Date: Wed, 1 Jul 2026 09:06:44 +0800 Subject: [PATCH] =?UTF-8?q?=E8=A1=A5=E5=85=85=E8=A1=A8=E8=BE=BE=E5=BC=8F?= =?UTF-8?q?=E5=86=B3=E7=AD=96=E6=97=A5=E5=8F=98=E9=87=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../fidc-core/src/platform_expr_strategy.rs | 134 ++++++++++++++++++ 1 file changed, 134 insertions(+) diff --git a/crates/fidc-core/src/platform_expr_strategy.rs b/crates/fidc-core/src/platform_expr_strategy.rs index 35df9d7..748c98c 100644 --- a/crates/fidc-core/src/platform_expr_strategy.rs +++ b/crates/fidc-core/src/platform_expr_strategy.rs @@ -2556,9 +2556,13 @@ impl PlatformExprStrategy { ) -> Scope<'static> { let mut scope = Scope::new(); let trade_date = day.date.format("%Y-%m-%d").to_string(); + let decision_date = ctx.decision_date.format("%Y-%m-%d").to_string(); + let execution_date = ctx.execution_date.format("%Y-%m-%d").to_string(); scope.push("trade_date", trade_date.clone()); scope.push("current_date", trade_date.clone()); scope.push("date", trade_date); + scope.push("decision_date", decision_date); + scope.push("execution_date", execution_date); scope.push("signal_open", day.signal_open); scope.push("signal_close", day.signal_close); scope.push("benchmark_open", day.benchmark_open); @@ -20332,6 +20336,136 @@ mod tests { } } + #[test] + fn target_portfolio_smart_exposes_decision_and_execution_dates() { + let decision_date = d(2023, 1, 3); + let execution_date = d(2023, 1, 4); + let signal = "000001.SH"; + let data = DataSet::from_components( + vec![], + vec![ + DailyMarketSnapshot { + date: decision_date, + symbol: signal.to_string(), + timestamp: Some("2023-01-03 10:18:00".to_string()), + day_open: 1000.0, + open: 1000.0, + high: 1002.0, + low: 998.0, + close: 1001.0, + last_price: 1001.0, + bid1: 1000.5, + ask1: 1001.5, + prev_close: 999.0, + volume: 100_000, + minute_volume: 5_000, + bid1_volume: 2_500, + ask1_volume: 2_500, + trading_phase: Some("continuous".to_string()), + paused: false, + upper_limit: 1098.9, + lower_limit: 899.1, + price_tick: 0.01, + }, + DailyMarketSnapshot { + date: execution_date, + symbol: signal.to_string(), + timestamp: Some("2023-01-04 10:18:00".to_string()), + day_open: 1002.0, + open: 1002.0, + high: 1004.0, + low: 1000.0, + close: 1003.0, + last_price: 1003.0, + bid1: 1002.5, + ask1: 1003.5, + prev_close: 1001.0, + volume: 100_000, + minute_volume: 5_000, + bid1_volume: 2_500, + ask1_volume: 2_500, + trading_phase: Some("continuous".to_string()), + paused: false, + upper_limit: 1101.1, + lower_limit: 900.9, + price_tick: 0.01, + }, + ], + vec![], + vec![], + vec![ + BenchmarkSnapshot { + date: decision_date, + benchmark: "000852.SH".to_string(), + open: 1000.0, + close: 1001.0, + prev_close: 999.0, + volume: 100_000, + }, + BenchmarkSnapshot { + date: execution_date, + benchmark: "000852.SH".to_string(), + open: 1002.0, + close: 1003.0, + prev_close: 1001.0, + volume: 100_000, + }, + ], + ) + .expect("dataset"); + let portfolio = PortfolioState::new(1_000_000.0); + let subscriptions = BTreeSet::new(); + let ctx = StrategyContext { + execution_date, + decision_date, + decision_index: 1, + 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 = signal.to_string(); + cfg.rotation_enabled = false; + cfg.benchmark_short_ma_days = 1; + cfg.benchmark_long_ma_days = 1; + cfg.explicit_actions = vec![PlatformTradeAction::TargetPortfolioSmart { + target_weights_expr: concat!( + "{", + "\"000521.SZ\": if decision_date == \"2023-01-03\" && execution_date == \"2023-01-04\" { 0.025 } else { 0.0 },", + "\"000333.SZ\": if current_date == \"2023-01-03\" { 0.05 } else { 0.0 }", + "}" + ) + .to_string(), + order_prices_expr: None, + valuation_prices_expr: None, + when_expr: Some( + "decision_date == \"2023-01-03\" && execution_date == \"2023-01-04\"" + .to_string(), + ), + reason: "decision_date_conditioned_target_weights".to_string(), + }]; + let mut strategy = PlatformExprStrategy::new(cfg); + + let decision = strategy.on_day(&ctx).expect("platform decision"); + + assert_eq!(decision.order_intents.len(), 1); + match &decision.order_intents[0] { + crate::strategy::OrderIntent::TargetPortfolioSmart { target_weights, .. } => { + assert_eq!(target_weights.get("000521.SZ").copied(), Some(0.025)); + assert_eq!(target_weights.get("000333.SZ").copied(), Some(0.05)); + } + other => panic!("unexpected explicit target portfolio intent: {other:?}"), + } + } + #[test] fn platform_strategy_emits_target_portfolio_smart_algo_order_style() { let date = d(2025, 2, 3);