diff --git a/crates/fidc-core/src/broker.rs b/crates/fidc-core/src/broker.rs index fb1463b..4880f96 100644 --- a/crates/fidc-core/src/broker.rs +++ b/crates/fidc-core/src/broker.rs @@ -248,7 +248,6 @@ pub enum MatchingType { OpenAuction, CurrentBarClose, NextBarOpen, - PostCloseFixedPrice, MinuteLast, MinuteBestOwn, MinuteBestCounterparty, @@ -546,12 +545,6 @@ impl BrokerSimulator { } fn effective_remainder_policy(&self, allow_pending_limit: bool) -> RemainderPolicy { - if self.matching_type == MatchingType::PostCloseFixedPrice { - return match self.runtime_time_in_force.get() { - Some(OrderTimeInForce::Fok) => RemainderPolicy::FillOrKill, - _ => RemainderPolicy::Cancel, - }; - } match self.runtime_time_in_force.get() { Some(OrderTimeInForce::Fok) => RemainderPolicy::FillOrKill, Some(OrderTimeInForce::Gtc) => RemainderPolicy::KeepUntilCanceled, @@ -990,13 +983,6 @@ where return self.clamp_execution_price(snapshot, side, raw_price); } - // A fixed-price post-close declaration is matched at the official - // close; applying market slippage here would turn it into a different - // order contract. Fees and risk checks still run normally. - if self.matching_type == MatchingType::PostCloseFixedPrice { - return self.clamp_execution_price(snapshot, side, raw_price); - } - let order_value = quantity.and_then(|qty| (qty > 0).then_some(raw_price * qty as f64)); let mut adjusted = match self.slippage_model { SlippageModel::None => raw_price, @@ -7442,7 +7428,7 @@ where MatchingType::OpenAuction | MatchingType::CurrentBarClose | MatchingType::NextBarOpen - | MatchingType::PostCloseFixedPrice => false, + => false, MatchingType::MinuteLast => self.liquidity_limit, MatchingType::MinuteBestOwn | MatchingType::MinuteBestCounterparty @@ -7488,7 +7474,6 @@ fn execution_price_field_from_matching_type(matching_type: MatchingType) -> Pric MatchingType::OpenAuction => PriceField::DayOpen, MatchingType::CurrentBarClose => PriceField::Close, MatchingType::NextBarOpen => PriceField::Open, - MatchingType::PostCloseFixedPrice => PriceField::Close, MatchingType::MinuteLast | MatchingType::MinuteBestOwn | MatchingType::MinuteBestCounterparty @@ -7969,57 +7954,6 @@ mod tests { assert!(broker.quote_quantity_limited(MatchingType::MinuteBestCounterparty)); } - #[test] - fn post_close_fixed_price_uses_daily_close_without_market_slippage() { - let date = chrono::NaiveDate::from_ymd_opt(2026, 7, 6).expect("valid date"); - let mut snapshot = limit_test_snapshot(); - snapshot.date = date; - snapshot.timestamp = Some(format!("{date} 15:00:00")); - snapshot.close = 10.0; - snapshot.last_price = 10.0; - snapshot.bid1 = 10.0; - snapshot.ask1 = 10.0; - let mut candidate = limit_test_candidate(true, true); - candidate.date = date; - let mut benchmark = limit_test_benchmark(); - benchmark.date = date; - let data = DataSet::from_components_with_actions_and_quotes( - vec![limit_test_instrument()], - vec![snapshot], - Vec::new(), - vec![candidate], - vec![benchmark], - Vec::new(), - Vec::new(), - ) - .expect("valid post-close dataset"); - let broker = BrokerSimulator::new( - ChinaAShareCostModel::default(), - ChinaEquityRuleHooks, - ) - .with_matching_type(MatchingType::PostCloseFixedPrice) - .with_slippage_model(SlippageModel::PriceRatio(0.25)) - .with_volume_limit(false) - .with_liquidity_limit(false) - .with_inactive_limit(false); - let decision = StrategyDecision { - order_intents: vec![OrderIntent::TargetValue { - symbol: "000001.SZ".to_string(), - target_value: 2_000.0, - reason: "post_close_buy".to_string(), - }], - ..StrategyDecision::default() - }; - let mut portfolio = PortfolioState::new(20_000.0); - let report = broker - .execute(date, &mut portfolio, &data, &decision) - .expect("post-close execution"); - assert_eq!(report.fill_events.len(), 1, "report={report:?}"); - assert_eq!(report.fill_events[0].price, 10.0); - assert_eq!(report.fill_events[0].quantity, 100); - assert!(!broker.has_open_orders()); - } - #[test] fn next_open_buy_risk_uses_execution_date_not_signal_date() { let signal_date = chrono::NaiveDate::from_ymd_opt(2025, 1, 2).expect("valid date"); diff --git a/crates/fidc-core/src/engine.rs b/crates/fidc-core/src/engine.rs index 6042ad6..3ee1506 100644 --- a/crates/fidc-core/src/engine.rs +++ b/crates/fidc-core/src/engine.rs @@ -26,9 +26,6 @@ use crate::strategy::{ TargetPortfolioOrderPricing, }; -pub const POST_CLOSE_FIXED_PRICE_EFFECTIVE_DATE: NaiveDate = - NaiveDate::from_ymd_opt(2026, 7, 6).expect("valid post-close effective date"); - #[derive(Debug, Error)] pub enum BacktestError { #[error(transparent)] @@ -1973,16 +1970,6 @@ where }) .filter(|date| self.config.end_date.map(|end| *date <= end).unwrap_or(true)) .collect::>(); - if self.broker.matching_type() == MatchingType::PostCloseFixedPrice - && calendar_dates - .first() - .is_some_and(|date| *date < POST_CLOSE_FIXED_PRICE_EFFECTIVE_DATE) - { - return Err(BacktestError::Execution(format!( - "post_close_fixed_price is unavailable before {}", - POST_CLOSE_FIXED_PRICE_EFFECTIVE_DATE - ))); - } let has_decision_inputs = |date: NaiveDate| { !self.data.factor_snapshot_rows_on(date).is_empty() && !self.data.candidate_snapshot_rows_on(date).is_empty() diff --git a/crates/fidc-core/src/platform_expr_strategy.rs b/crates/fidc-core/src/platform_expr_strategy.rs index 9dbb20e..0e4272d 100644 --- a/crates/fidc-core/src/platform_expr_strategy.rs +++ b/crates/fidc-core/src/platform_expr_strategy.rs @@ -2194,7 +2194,6 @@ impl PlatformExprStrategy { MatchingType::OpenAuction => PriceField::DayOpen, MatchingType::CurrentBarClose => PriceField::Close, MatchingType::NextBarOpen => PriceField::Open, - MatchingType::PostCloseFixedPrice => PriceField::Close, MatchingType::MinuteLast | MatchingType::MinuteBestOwn | MatchingType::MinuteBestCounterparty @@ -2257,7 +2256,6 @@ impl PlatformExprStrategy { let price_field = match self.config.matching_type { MatchingType::NextBarOpen => PriceField::Open, MatchingType::CurrentBarClose => PriceField::Close, - MatchingType::PostCloseFixedPrice => PriceField::Close, MatchingType::OpenAuction | MatchingType::MinuteLast | MatchingType::MinuteBestOwn diff --git a/crates/fidc-core/src/platform_strategy_spec.rs b/crates/fidc-core/src/platform_strategy_spec.rs index f846846..f008412 100644 --- a/crates/fidc-core/src/platform_strategy_spec.rs +++ b/crates/fidc-core/src/platform_strategy_spec.rs @@ -1345,10 +1345,9 @@ fn parse_matching_type(value: Option<&str>) -> Result, Stri match normalize_model_name(raw).as_str() { "current_bar_close" => Ok(Some(MatchingType::CurrentBarClose)), "next_bar_open" => Ok(Some(MatchingType::NextBarOpen)), - "post_close_fixed_price" => Ok(Some(MatchingType::PostCloseFixedPrice)), "minute_last" => Ok(Some(MatchingType::MinuteLast)), _ => Err(format!( - "matchingType only supports current_bar_close, next_bar_open, post_close_fixed_price, minute_last: {raw}" + "matchingType only supports current_bar_close, next_bar_open, minute_last: {raw}" )), } } @@ -3661,7 +3660,6 @@ mod tests { for (raw, expected) in [ ("current_bar_close", MatchingType::CurrentBarClose), ("next_bar_open", MatchingType::NextBarOpen), - ("post_close_fixed_price", MatchingType::PostCloseFixedPrice), ("minute_last", MatchingType::MinuteLast), ] { let spec = serde_json::json!({ @@ -3698,7 +3696,7 @@ mod tests { assert!( err.to_string().contains( - "matchingType only supports current_bar_close, next_bar_open, post_close_fixed_price, minute_last" + "matchingType only supports current_bar_close, next_bar_open, minute_last" ), "{err}" );