From 214872dfbf00b0cb20f2e47a8879e5085d6d4658 Mon Sep 17 00:00:00 2001 From: boris Date: Sun, 12 Jul 2026 05:47:37 +0800 Subject: [PATCH] =?UTF-8?q?=E4=BF=AE=E6=AD=A3=E6=AC=A1=E6=97=A5=E5=BC=80?= =?UTF-8?q?=E7=9B=98=E7=9B=AE=E6=A0=87=E5=B8=82=E5=80=BC=E7=8E=B0=E9=87=91?= =?UTF-8?q?=E6=8A=95=E5=BD=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../fidc-core/src/platform_expr_strategy.rs | 138 +++++++++++++++++- 1 file changed, 137 insertions(+), 1 deletion(-) diff --git a/crates/fidc-core/src/platform_expr_strategy.rs b/crates/fidc-core/src/platform_expr_strategy.rs index f1dc89c..fbf2ff6 100644 --- a/crates/fidc-core/src/platform_expr_strategy.rs +++ b/crates/fidc-core/src/platform_expr_strategy.rs @@ -2143,7 +2143,7 @@ impl PlatformExprStrategy { } let market = ctx.data.market(date, symbol)?; let current_value = if self.config.aiquant_transaction_cost { - self.projected_position_value_at_execution_price(ctx, projected, date, symbol) + self.projected_target_value_current_position_value(ctx, projected, date, symbol) } else { let valuation_price = if market.close.is_finite() && market.close > 0.0 { market.close @@ -2275,6 +2275,26 @@ impl PlatformExprStrategy { } } + fn projected_target_value_current_position_value( + &self, + ctx: &StrategyContext<'_>, + projected: &PortfolioState, + date: NaiveDate, + symbol: &str, + ) -> f64 { + let Some(position) = projected.position(symbol) else { + return 0.0; + }; + if self.config.matching_type == MatchingType::NextBarOpen { + if let Some(market) = ctx.data.market(date, symbol) { + if market.prev_close.is_finite() && market.prev_close > 0.0 { + return position.quantity as f64 * market.prev_close; + } + } + } + self.projected_position_value_at_execution_price(ctx, projected, date, symbol) + } + fn context_position_value_for_remaining_buy_cash( &self, ctx: &StrategyContext<'_>, @@ -10158,6 +10178,122 @@ mod tests { ); } + #[test] + fn platform_aiquant_next_open_target_delta_uses_previous_close_and_sizes_at_open() { + let date = d(2025, 1, 2); + let symbol = "000001.SZ"; + let data = DataSet::from_components( + vec![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(), + }], + vec![DailyMarketSnapshot { + date, + symbol: symbol.to_string(), + timestamp: Some(format!("{date} 15:00:00")), + day_open: 12.0, + open: 12.0, + high: 20.0, + low: 9.8, + close: 20.0, + last_price: 20.0, + bid1: 12.0, + ask1: 12.0, + prev_close: 10.0, + volume: 1_000_000, + minute_volume: 10_000, + bid1_volume: 10_000, + ask1_volume: 10_000, + trading_phase: Some("continuous".to_string()), + paused: false, + upper_limit: 22.0, + lower_limit: 9.0, + price_tick: 0.01, + }], + vec![DailyFactorSnapshot { + date, + symbol: symbol.to_string(), + market_cap_bn: 10.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(), + }], + vec![CandidateEligibility { + date, + symbol: symbol.to_string(), + is_st: false, + is_star_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, + }], + 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 mut portfolio = PortfolioState::new(10_000.0); + portfolio.position_mut(symbol).buy(date, 100, 10.0); + let subscriptions = BTreeSet::new(); + let ctx = StrategyContext { + execution_date: date, + decision_date: 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.aiquant_transaction_cost = true; + cfg.matching_type = MatchingType::NextBarOpen; + cfg.risk_config.trading_constraints.volume_limit_enabled = false; + cfg.risk_config.trading_constraints.liquidity_limit_enabled = false; + let strategy = PlatformExprStrategy::new(cfg); + let mut projected = portfolio.clone(); + let mut execution_state = super::ProjectedExecutionState::default(); + + assert_eq!( + strategy.projected_target_value_current_position_value(&ctx, &projected, date, symbol,), + 1_000.0 + ); + assert_eq!( + strategy.project_target_value( + &ctx, + &mut projected, + date, + symbol, + 3_410.0, + &mut execution_state, + ), + Some(200) + ); + assert_eq!(projected.position(symbol).unwrap().quantity, 300); + } + #[test] fn platform_next_open_sell_risk_uses_open_not_close_for_lower_limit() { let prev_date = d(2025, 1, 1);