diff --git a/crates/fidc-core/src/broker.rs b/crates/fidc-core/src/broker.rs index a6fdc05..46e7830 100644 --- a/crates/fidc-core/src/broker.rs +++ b/crates/fidc-core/src/broker.rs @@ -8735,7 +8735,7 @@ where | MatchingType::MinuteBestCounterparty | MatchingType::Vwap | MatchingType::Twap - ) || (self.matching_type == MatchingType::CurrentBarClose + ) || (matches!(self.matching_type, MatchingType::CurrentBarClose | MatchingType::NextBarOpen) && self.intraday_execution_start_time.is_some()) } @@ -10349,6 +10349,28 @@ mod tests { assert!(!audit_a[0].passed); assert!(audit_b[0].passed); } + #[test] + fn next_open_with_observations_uses_opening_volume_not_daily_totals_or_future_quotes() { + let market=limit_test_snapshot(); + let date=market.date; + let open=NaiveTime::from_hms_opt(9,30,0).unwrap(); + let quote=|time:NaiveTime,volume| IntradayExecutionQuote { observation_kind:Default::default(),date, + symbol:"000001.SZ".into(),timestamp:date.and_time(time),last_price:market.open, + bid1:0.,ask1:0.,bid1_volume:0,ask1_volume:0,volume_delta:volume,amount_delta:market.open*volume as f64,trading_phase:None }; + let quotes=vec![quote(open,400),quote(NaiveTime::from_hms_opt(9,31,0).unwrap(),1_000_000)]; + let data=DataSet::from_components_with_actions_and_quotes(vec![limit_test_instrument()],vec![market],vec![], + vec![limit_test_candidate(true,true)],vec![limit_test_benchmark()],vec![],quotes).unwrap(); + let broker=BrokerSimulator::new(ChinaAShareCostModel::default(),ChinaEquityRuleHooks) + .with_matching_type(MatchingType::NextBarOpen).with_intraday_execution_start_time(open).with_liquidity_limit(false); + broker.runtime_intraday_end_time.set(Some(open)); + broker.runtime_execution_clock.set(Some(open)); + let mut account=PortfolioState::new(100_000.); + let report=broker.execute(date,&mut account,&data,&StrategyDecision { order_intents:vec![OrderIntent::Shares { + symbol:"000001.SZ".into(),quantity:1000,reason:"opening capacity regression".into()}],..Default::default() }).unwrap(); + assert_eq!(report.fill_events.iter().map(|fill|fill.quantity).sum::(),100); + assert!(report.fill_events.iter().all(|fill|fill.execution_timestamp==Some(date.and_time(open)))); + } + #[test] fn daily_capacity_requires_a_timed_observation_instead_of_falling_back_to_total_volume() { let mut snapshot = limit_test_snapshot(); diff --git a/crates/fidc-core/src/broker_order_recovery_tests.rs b/crates/fidc-core/src/broker_order_recovery_tests.rs index 0c50317..53feb0e 100644 --- a/crates/fidc-core/src/broker_order_recovery_tests.rs +++ b/crates/fidc-core/src/broker_order_recovery_tests.rs @@ -320,6 +320,7 @@ fn deferred_etf_batch_failure_keeps_both_targets_and_prior_generation_progress() execute_on: Some(date), target_value: 1000.into(), target_weight_bps: 5000, + target_weight_ratio: None, side: crate::stock_pool_execution::OrderSide::Buy, max_positions: 2, rule: Default::default(), diff --git a/crates/fidc-core/src/stock_pool_frozen.rs b/crates/fidc-core/src/stock_pool_frozen.rs index 029fb83..2cf94e4 100644 --- a/crates/fidc-core/src/stock_pool_frozen.rs +++ b/crates/fidc-core/src/stock_pool_frozen.rs @@ -95,7 +95,7 @@ pub(super) fn valuation( mod ratio_tests { use super::*; fn configuration() -> (Vec, StockPoolDecisionConstraints, BTreeMap) { - let symbols = vec!["000001.SZ".into(),"000002.SZ".into(),"000003.SZ".into()]; + let symbols: Vec = vec!["000001.SZ".into(),"000002.SZ".into(),"000003.SZ".into()]; let paused = FrozenStockPoolPosition { trade_date: NaiveDate::from_ymd_opt(2026,9,3).unwrap(), reason:"paused".into(), valuation_price:Decimal::from(10) }; let constraints = StockPoolDecisionConstraints { frozen_positions:BTreeMap::from([(symbols[0].clone(),paused)]), prior_target_weights:BTreeMap::from([(symbols[0].clone(),3334)]), ..Default::default() };