From 8e6c912a07670d215ce1571687c3384d582e3474 Mon Sep 17 00:00:00 2001 From: boris Date: Tue, 16 Jun 2026 07:49:10 +0800 Subject: [PATCH] =?UTF-8?q?=E4=BF=AE=E6=AD=A3AiQuant=E7=9B=AE=E6=A0=87?= =?UTF-8?q?=E5=B8=82=E5=80=BC=E4=BC=B0=E5=80=BC=E5=8F=A3=E5=BE=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- crates/fidc-core/src/broker.rs | 28 ++-- .../fidc-core/src/platform_expr_strategy.rs | 158 +++++++++++++++++- 2 files changed, 164 insertions(+), 22 deletions(-) diff --git a/crates/fidc-core/src/broker.rs b/crates/fidc-core/src/broker.rs index a3c5452..a4300df 100644 --- a/crates/fidc-core/src/broker.rs +++ b/crates/fidc-core/src/broker.rs @@ -2848,8 +2848,15 @@ where return Ok(()); } - let valuation_price = self.target_value_valuation_price(date, data, symbol, snapshot); - let current_value = valuation_price * current_qty as f64; + let current_value = if self.aiquant_rqalpha_execution_rules { + portfolio + .position(symbol) + .map(|position| position.market_value()) + .unwrap_or(0.0) + } else { + let valuation_price = self.target_value_valuation_price(date, data, symbol, snapshot); + valuation_price * current_qty as f64 + }; let cash_delta = target_value.max(0.0) - current_value; if cash_delta.abs() > f64::EPSILON { @@ -5278,7 +5285,7 @@ mod tests { } #[test] - fn aiquant_target_value_valuation_uses_scheduled_tick_last_price() { + fn aiquant_target_value_delta_uses_position_market_value() { let date = chrono::NaiveDate::from_ymd_opt(2023, 5, 8).expect("valid date"); let symbol = "603101.SH"; let broker = BrokerSimulator::new_with_execution_price( @@ -5369,12 +5376,6 @@ mod tests { vec![quote], ) .expect("valid dataset"); - let snapshot = data.market(date, symbol).expect("market snapshot"); - assert_eq!( - broker.target_value_valuation_price(date, &data, symbol, snapshot), - 5.83 - ); - let mut portfolio = PortfolioState::new(8_261_416.62); portfolio.position_mut(symbol).buy(date, 21_200, 5.8817); let mut report = BrokerExecutionReport::default(); @@ -5396,11 +5397,12 @@ mod tests { assert_eq!( portfolio.position(symbol).map(|pos| pos.quantity), - Some(21_400) + Some(21_200) ); - let order = report.order_events.last().expect("target value order"); - assert_eq!(order.requested_quantity, 200); - assert_eq!(order.filled_quantity, 200); + if let Some(order) = report.order_events.last() { + assert_eq!(order.requested_quantity, 0); + assert_eq!(order.filled_quantity, 0); + } } #[test] diff --git a/crates/fidc-core/src/platform_expr_strategy.rs b/crates/fidc-core/src/platform_expr_strategy.rs index a76dc00..a208da3 100644 --- a/crates/fidc-core/src/platform_expr_strategy.rs +++ b/crates/fidc-core/src/platform_expr_strategy.rs @@ -1580,7 +1580,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) + projected.position(symbol)?.market_value() } else { let valuation_price = if market.close.is_finite() && market.close > 0.0 { market.close @@ -1600,14 +1600,9 @@ impl PlatformExprStrategy { return None; } if cash_delta > 0.0 { - return Some(self.project_order_value( - ctx, - projected, - date, - symbol, - cash_delta, - execution_state, - )); + let filled = + self.project_order_value(ctx, projected, date, symbol, cash_delta, execution_state); + return (filled > 0).then_some(filled); } if !self.can_sell_position(ctx, date, symbol) { @@ -7432,6 +7427,151 @@ mod tests { assert!((position.average_cost - 5.12022).abs() < 1e-9); } + #[test] + fn platform_aiquant_target_value_uses_position_market_value_for_delta() { + let prev_date = d(2023, 5, 11); + let date = d(2023, 5, 12); + let symbol = "603176.SH"; + let data = DataSet::from_components_with_actions_and_quotes( + vec![Instrument { + symbol: symbol.to_string(), + name: symbol.to_string(), + board: "SH".to_string(), + round_lot: 100, + listed_at: Some(d(2010, 1, 1)), + delisted_at: None, + status: "active".to_string(), + }], + vec![DailyMarketSnapshot { + date, + symbol: symbol.to_string(), + timestamp: Some("2023-05-12 10:40:00".to_string()), + day_open: 6.70, + open: 6.70, + high: 6.72, + low: 6.48, + close: 6.48, + last_price: 6.5369, + bid1: 6.5369, + ask1: 6.5369, + prev_close: 6.72, + volume: 1_000_000, + tick_volume: 10_000, + bid1_volume: 10_000, + ask1_volume: 10_000, + trading_phase: Some("continuous".to_string()), + paused: false, + upper_limit: 7.39, + lower_limit: 6.05, + 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_new_listing: false, + is_paused: false, + allow_buy: true, + allow_sell: true, + is_kcb: false, + is_one_yuan: false, + }], + vec![BenchmarkSnapshot { + date, + benchmark: "000852.SH".to_string(), + open: 1000.0, + close: 1000.0, + prev_close: 998.0, + volume: 1_000_000, + }], + Vec::new(), + vec![IntradayExecutionQuote { + date, + symbol: symbol.to_string(), + timestamp: date.and_hms_opt(10, 40, 0).expect("timestamp"), + last_price: 6.5369, + bid1: 6.5369, + ask1: 6.5369, + bid1_volume: 10_000, + ask1_volume: 10_000, + volume_delta: 10_000, + amount_delta: 65_369.0, + trading_phase: Some("continuous".to_string()), + }], + ) + .expect("dataset"); + + let subscriptions = BTreeSet::new(); + let mut portfolio = PortfolioState::new(500_000.0); + portfolio + .position_mut(symbol) + .buy_with_mark_price(prev_date, 18_600, 7.22, 6.72); + let target_value = 124_995.0; + assert!( + portfolio.position(symbol).unwrap().market_value() < target_value, + "fixture must be slightly below target by position.market_value" + ); + assert!( + target_value - portfolio.position(symbol).unwrap().market_value() < 100.0 * 6.5369, + "fixture must be below one lot when position.market_value is used" + ); + assert!( + 18_600.0 * 6.5369 < target_value, + "fixture must be below target if tick price is used incorrectly" + ); + + let ctx = StrategyContext { + execution_date: date, + decision_date: date, + decision_index: 20, + 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.commission_rate = Some(0.0003); + cfg.minimum_commission = Some(5.0); + cfg.strict_value_budget = true; + cfg.slippage_model = SlippageModel::PriceRatio(0.002); + cfg.matching_type = MatchingType::NextTickLast; + cfg.quote_quantity_limit = false; + cfg.intraday_execution_time = Some(NaiveTime::from_hms_opt(10, 40, 0).expect("time")); + let strategy = PlatformExprStrategy::new(cfg); + + let mut projected = portfolio.clone(); + let mut execution_state = super::ProjectedExecutionState::default(); + let filled = strategy.project_target_value( + &ctx, + &mut projected, + date, + symbol, + target_value, + &mut execution_state, + ); + + assert_eq!(filled, None); + assert_eq!(projected.position(symbol).unwrap().quantity, 18_600); + } + #[test] fn platform_marked_total_value_uses_current_day_close() { let prev_date = d(2025, 6, 20);