From 7d293f092e17425c2180d80c9f7bbbf9af170fcf Mon Sep 17 00:00:00 2001 From: boris Date: Tue, 8 Sep 2026 00:43:54 +0800 Subject: [PATCH] test: cover next-open side flips and trim redundant default fields --- crates/fidc-core/src/broker.rs | 47 +++++++++++++++---- crates/fidc-core/src/engine.rs | 12 ----- .../fidc-core/tests/decision_quote_preload.rs | 2 - crates/fidc-core/tests/engine_hooks.rs | 5 -- crates/fidc-core/tests/explicit_order_flow.rs | 20 -------- 5 files changed, 38 insertions(+), 48 deletions(-) diff --git a/crates/fidc-core/src/broker.rs b/crates/fidc-core/src/broker.rs index 0350a8e..652ee38 100644 --- a/crates/fidc-core/src/broker.rs +++ b/crates/fidc-core/src/broker.rs @@ -8218,7 +8218,6 @@ mod tests { fn next_open_buy_decision() -> StrategyDecision { StrategyDecision { - buy_denials: Default::default(), order_intents: vec![OrderIntent::Shares { symbol: "000001.SZ".to_string(), quantity: 100, @@ -8277,9 +8276,46 @@ mod tests { assert!(broker.runtime_buy_denials.borrow().is_empty()); } + #[test] + fn decision_buy_denial_uses_actual_next_open_target_delta() { + let first = chrono::NaiveDate::from_ymd_opt(2025, 1, 2).unwrap(); + let second = chrono::NaiveDate::from_ymd_opt(2025, 1, 3).unwrap(); + let mut next = dated_limit_test_snapshot(second); + next.day_open = 9.5; + next.open = 9.5; + next.close = 9.5; + next.last_price = 9.5; + next.bid1 = 9.5; + next.ask1 = 9.5; + let data = DataSet::from_components(vec![limit_test_instrument()], + vec![dated_limit_test_snapshot(first), next], Vec::new(), + vec![dated_limit_test_candidate(first, false, false, true, true), + dated_limit_test_candidate(second, false, false, true, true)], + vec![dated_limit_test_benchmark(first), dated_limit_test_benchmark(second)]).unwrap(); + let broker = BrokerSimulator::new(ChinaAShareCostModel::default(), ChinaEquityRuleHooks) + .with_matching_type(MatchingType::NextBarOpen); + let mut portfolio = PortfolioState::new(1_000_000.0); + let mut initial = StrategyDecision::default(); + initial.order_intents.push(OrderIntent::Shares { + symbol: "000001.SZ".to_string(), quantity: 10_000, reason: "initial".to_string(), + }); + broker.execute(first, &mut portfolio, &data, &initial).unwrap(); + let mut decision = StrategyDecision::default(); + decision.buy_denials.insert("000001.SZ".to_string(), "strategy_buy_condition_false".to_string()); + // Below the signal-day holding value, but above next-open value. + decision.order_intents.push(OrderIntent::TargetValue { + symbol: "000001.SZ".to_string(), target_value: 97_500.0, reason: "target".to_string(), + }); + let report = broker.execute_with_event_dates_and_decision_equity( + second, first, first, None, &mut portfolio, &data, &decision).unwrap(); + assert!(report.fill_events.is_empty()); + assert_eq!(portfolio.position("000001.SZ").unwrap().quantity, 10_000); + assert!(report.order_events.iter().any(|event| event.side == OrderSide::Buy)); + assert!(broker.runtime_buy_denials.borrow().is_empty()); + } + fn next_open_sell_decision() -> StrategyDecision { StrategyDecision { - buy_denials: Default::default(), order_intents: vec![OrderIntent::Shares { symbol: "000001.SZ".to_string(), quantity: -100, @@ -10257,7 +10293,6 @@ mod tests { ) .expect("valid dataset"); let decision = StrategyDecision { - buy_denials: Default::default(), order_intents: vec![ OrderIntent::TargetValue { symbol: "000002.SZ".to_string(), @@ -10370,7 +10405,6 @@ mod tests { .position_mut("000002.SZ") .buy(prev_date, 1_000, 10.0); let decision = StrategyDecision { - buy_denials: Default::default(), order_intents: vec![ OrderIntent::TargetValue { symbol: "000001.SZ".to_string(), @@ -10438,7 +10472,6 @@ mod tests { .position_mut("000002.SZ") .buy(prev_date, 1_000, 10.0); let decision = StrategyDecision { - buy_denials: Default::default(), order_intents: vec![ OrderIntent::TargetValue { symbol: "000003.SZ".to_string(), @@ -10499,7 +10532,6 @@ mod tests { .position_mut("000001.SZ") .buy(prev_date, 1_000, 10.0); let decision = StrategyDecision { - buy_denials: Default::default(), order_intents: vec![OrderIntent::TargetValue { symbol: "000003.SZ".to_string(), target_value: 9_000.0, @@ -10664,7 +10696,6 @@ mod tests { .with_inactive_limit(false); let mut portfolio = PortfolioState::new(20_000.0); let decision = StrategyDecision { - buy_denials: Default::default(), order_intents: vec![OrderIntent::TargetValue { symbol: "000001.SZ".to_string(), target_value: 10_000.0, @@ -10706,7 +10737,6 @@ mod tests { .with_inactive_limit(false); let mut portfolio = PortfolioState::new(20_000.0); let decision = StrategyDecision { - buy_denials: Default::default(), order_intents: vec![OrderIntent::TargetValue { symbol: "000001.SZ".to_string(), target_value: 10_000.0, @@ -10744,7 +10774,6 @@ mod tests { .with_inactive_limit(false); let mut portfolio = PortfolioState::new(20_000.0); let decision = StrategyDecision { - buy_denials: Default::default(), order_intents: vec![OrderIntent::TargetPortfolioSmart { target_weights: BTreeMap::from([("000001.SZ".to_string(), 0.5)]), order_prices: None, diff --git a/crates/fidc-core/src/engine.rs b/crates/fidc-core/src/engine.rs index e0b2053..5cd3cd1 100644 --- a/crates/fidc-core/src/engine.rs +++ b/crates/fidc-core/src/engine.rs @@ -4856,7 +4856,6 @@ mod tests { ) -> Result { if ctx.decision_date == self.decision_date && ctx.portfolio.position(SYMBOL).is_none() { return Ok(StrategyDecision { - buy_denials: Default::default(), order_intents: vec![OrderIntent::Shares { symbol: SYMBOL.to_string(), quantity: 100, @@ -4897,7 +4896,6 @@ mod tests { ); if ctx.portfolio.position(SYMBOL).is_none() { return Ok(StrategyDecision { - buy_denials: Default::default(), order_intents: vec![OrderIntent::Shares { symbol: SYMBOL.to_string(), quantity: 100, @@ -4957,7 +4955,6 @@ mod tests { rule: &ScheduleRule, ) -> Result { Ok(StrategyDecision { - buy_denials: Default::default(), order_intents: vec![OrderIntent::Shares { symbol: SYMBOL.to_string(), quantity: 100, @@ -4995,7 +4992,6 @@ mod tests { return Ok(StrategyDecision::default()); } Ok(StrategyDecision { - buy_denials: Default::default(), order_intents: vec![OrderIntent::Shares { symbol: SYMBOL.to_string(), quantity: 100, @@ -5032,7 +5028,6 @@ mod tests { return Ok(StrategyDecision::default()); } Ok(StrategyDecision { - buy_denials: Default::default(), order_intents: vec![OrderIntent::TargetPortfolioSmart { target_weights: self.target_weights.clone(), order_prices: None, @@ -5075,7 +5070,6 @@ mod tests { Vec::new() }; Ok(StrategyDecision { - buy_denials: Default::default(), order_intents, ..StrategyDecision::default() }) @@ -5112,7 +5106,6 @@ mod tests { return Ok(StrategyDecision::default()); }; Ok(StrategyDecision { - buy_denials: Default::default(), order_intents: vec![OrderIntent::Shares { symbol, quantity: 100, @@ -5173,7 +5166,6 @@ mod tests { .unwrap_or_default() )); Ok(StrategyDecision { - buy_denials: Default::default(), order_intents: vec![OrderIntent::Shares { symbol: SYMBOL.to_string(), quantity: 100, @@ -5214,7 +5206,6 @@ mod tests { && ctx.portfolio.position(SYMBOL).is_none() { return Ok(StrategyDecision { - buy_denials: Default::default(), order_intents: vec![OrderIntent::Shares { symbol: SYMBOL.to_string(), quantity: 100, @@ -5226,7 +5217,6 @@ mod tests { if ctx.decision_date == self.sell_decision_date { if let Some(position) = ctx.portfolio.position(SYMBOL) { return Ok(StrategyDecision { - buy_denials: Default::default(), order_intents: vec![OrderIntent::Shares { symbol: SYMBOL.to_string(), quantity: -(position.quantity as i32), @@ -5266,7 +5256,6 @@ mod tests { && ctx.portfolio.position(SYMBOL).is_none() { return Ok(StrategyDecision { - buy_denials: Default::default(), order_intents: vec![OrderIntent::Shares { symbol: SYMBOL.to_string(), quantity: 100, @@ -5278,7 +5267,6 @@ mod tests { if ctx.decision_date == self.rebuy_decision_date { if let Some(position) = ctx.portfolio.position(SYMBOL) { return Ok(StrategyDecision { - buy_denials: Default::default(), order_intents: vec![ OrderIntent::Shares { symbol: SYMBOL.to_string(), diff --git a/crates/fidc-core/tests/decision_quote_preload.rs b/crates/fidc-core/tests/decision_quote_preload.rs index 81f899c..fc06c99 100644 --- a/crates/fidc-core/tests/decision_quote_preload.rs +++ b/crates/fidc-core/tests/decision_quote_preload.rs @@ -37,7 +37,6 @@ impl Strategy for DecisionQuoteReader { self.day_count += 1; if self.day_count == 1 { return Ok(StrategyDecision { - buy_denials: Default::default(), order_intents: vec![OrderIntent::Value { symbol: "000001.SZ".to_string(), value: 5_000.0, @@ -627,7 +626,6 @@ impl Strategy for MultiTimeDecisionQuoteReader { self.day_count += 1; if self.day_count == 1 { return Ok(StrategyDecision { - buy_denials: Default::default(), order_intents: vec![OrderIntent::Value { symbol: "000001.SZ".to_string(), value: 5_000.0, diff --git a/crates/fidc-core/tests/engine_hooks.rs b/crates/fidc-core/tests/engine_hooks.rs index 05cbebc..2faf10a 100644 --- a/crates/fidc-core/tests/engine_hooks.rs +++ b/crates/fidc-core/tests/engine_hooks.rs @@ -416,7 +416,6 @@ impl Strategy for FuturesLimitOrderStrategy { return Ok(StrategyDecision::default()); } Ok(StrategyDecision { - buy_denials: Default::default(), order_intents: vec![OrderIntent::Futures { intent: FuturesOrderIntent::limit_open( "IF2501", @@ -448,7 +447,6 @@ impl Strategy for FuturesInvalidTickLimitStrategy { return Ok(StrategyDecision::default()); } Ok(StrategyDecision { - buy_denials: Default::default(), order_intents: vec![OrderIntent::Futures { intent: FuturesOrderIntent::limit_open( "IF2501", @@ -480,7 +478,6 @@ impl Strategy for FuturesClosedPhaseOrderStrategy { return Ok(StrategyDecision::default()); } Ok(StrategyDecision { - buy_denials: Default::default(), order_intents: vec![OrderIntent::Futures { intent: FuturesOrderIntent::open( "IF2501", @@ -512,7 +509,6 @@ impl Strategy for FuturesAboveUpperLimitStrategy { return Ok(StrategyDecision::default()); } Ok(StrategyDecision { - buy_denials: Default::default(), order_intents: vec![OrderIntent::Futures { intent: FuturesOrderIntent::limit_open( "IF2501", @@ -544,7 +540,6 @@ impl Strategy for FuturesDepthLimitOrderStrategy { return Ok(StrategyDecision::default()); } Ok(StrategyDecision { - buy_denials: Default::default(), order_intents: vec![OrderIntent::Futures { intent: FuturesOrderIntent::limit_open( "IF2501", diff --git a/crates/fidc-core/tests/explicit_order_flow.rs b/crates/fidc-core/tests/explicit_order_flow.rs index 26d3b8a..f2fb863 100644 --- a/crates/fidc-core/tests/explicit_order_flow.rs +++ b/crates/fidc-core/tests/explicit_order_flow.rs @@ -5014,7 +5014,6 @@ fn broker_ioc_limit_order_fills_available_quantity_and_cancels_remainder() { &mut portfolio, &data, &StrategyDecision { - buy_denials: Default::default(), order_intents: vec![ OrderIntent::LimitShares { symbol: "000002.SZ".to_string(), @@ -5053,7 +5052,6 @@ fn broker_persists_daily_volume_consumption_across_execute_calls() { .with_liquidity_limit(false); let mut portfolio = PortfolioState::new(1_000_000.0); let decision = || StrategyDecision { - buy_denials: Default::default(), order_intents: vec![OrderIntent::Shares { symbol: "000002.SZ".to_string(), quantity: 100, @@ -5101,7 +5099,6 @@ fn broker_persists_quote_depth_until_fresh_level_data_arrives() { .with_liquidity_limit(true); let mut portfolio = PortfolioState::new(1_000_000.0); let decision = |quantity| StrategyDecision { - buy_denials: Default::default(), order_intents: vec![OrderIntent::Shares { symbol: symbol.to_string(), quantity, @@ -5119,7 +5116,6 @@ fn broker_persists_quote_depth_until_fresh_level_data_arrives() { &mut portfolio, &data, &StrategyDecision { - buy_denials: Default::default(), order_intents: vec![ OrderIntent::Shares { symbol: symbol.to_string(), @@ -5224,7 +5220,6 @@ fn broker_persists_quote_volume_participation_until_next_quote() { .with_liquidity_limit(false); let mut portfolio = PortfolioState::new(1_000_000.0); let decision = |quantity| StrategyDecision { - buy_denials: Default::default(), order_intents: vec![OrderIntent::Shares { symbol: symbol.to_string(), quantity, @@ -5307,7 +5302,6 @@ fn broker_day_market_order_cancels_remainder_without_creating_invalid_open_order &mut portfolio, &data, &StrategyDecision { - buy_denials: Default::default(), order_intents: vec![ OrderIntent::Shares { symbol: "000002.SZ".to_string(), @@ -5349,7 +5343,6 @@ fn broker_fok_order_is_atomic_when_liquidity_is_insufficient() { &mut portfolio, &data, &StrategyDecision { - buy_denials: Default::default(), order_intents: vec![ OrderIntent::Shares { symbol: "000002.SZ".to_string(), @@ -5396,7 +5389,6 @@ fn broker_fok_order_fills_when_full_quantity_is_available() { &mut portfolio, &data, &StrategyDecision { - buy_denials: Default::default(), order_intents: vec![ OrderIntent::Shares { symbol: "000002.SZ".to_string(), @@ -5434,7 +5426,6 @@ fn broker_gtc_limit_order_survives_close_and_fills_next_day() { &mut portfolio, &data, &StrategyDecision { - buy_denials: Default::default(), order_intents: vec![ OrderIntent::LimitShares { symbol: "000002.SZ".to_string(), @@ -5486,7 +5477,6 @@ fn broker_gtc_partial_fills_preserve_cumulative_order_and_commission_state() { &mut portfolio, &data, &StrategyDecision { - buy_denials: Default::default(), order_intents: vec![ OrderIntent::LimitShares { symbol: "000002.SZ".to_string(), @@ -5551,7 +5541,6 @@ fn broker_modifies_gtc_limit_order_without_changing_order_identity() { &mut portfolio, &data, &StrategyDecision { - buy_denials: Default::default(), order_intents: vec![ OrderIntent::LimitShares { symbol: "000002.SZ".to_string(), @@ -5573,7 +5562,6 @@ fn broker_modifies_gtc_limit_order_without_changing_order_identity() { &mut portfolio, &data, &StrategyDecision { - buy_denials: Default::default(), order_intents: vec![OrderIntent::ModifyOrder { order_id, new_total_quantity: Some(400), @@ -5641,7 +5629,6 @@ fn broker_modifies_partially_filled_gtc_total_and_preserves_commission_state() { &mut portfolio, &data, &StrategyDecision { - buy_denials: Default::default(), order_intents: vec![ OrderIntent::LimitShares { symbol: "000002.SZ".to_string(), @@ -5665,7 +5652,6 @@ fn broker_modifies_partially_filled_gtc_total_and_preserves_commission_state() { &mut portfolio, &data, &StrategyDecision { - buy_denials: Default::default(), order_intents: vec![OrderIntent::ModifyOrder { order_id, new_total_quantity: Some(200), @@ -5720,7 +5706,6 @@ fn broker_rejected_modify_has_zero_side_effects() { &mut portfolio, &data, &StrategyDecision { - buy_denials: Default::default(), order_intents: vec![ OrderIntent::LimitShares { symbol: "000002.SZ".to_string(), @@ -5744,7 +5729,6 @@ fn broker_rejected_modify_has_zero_side_effects() { &mut portfolio, &data, &StrategyDecision { - buy_denials: Default::default(), order_intents: vec![OrderIntent::ModifyOrder { order_id, new_total_quantity: Some(100), @@ -5782,7 +5766,6 @@ fn broker_accepted_modify_resets_queue_priority_but_reduction_preserves_it() { ); let mut portfolio = PortfolioState::new(1_000_000.0); let create = |reason: &str| StrategyDecision { - buy_denials: Default::default(), order_intents: vec![ OrderIntent::LimitShares { symbol: "000002.SZ".to_string(), @@ -5813,7 +5796,6 @@ fn broker_accepted_modify_resets_queue_priority_but_reduction_preserves_it() { &mut portfolio, &data, &StrategyDecision { - buy_denials: Default::default(), order_intents: vec![OrderIntent::ModifyOrder { order_id: initial_ids[0], new_total_quantity: Some(200), @@ -5843,7 +5825,6 @@ fn broker_accepted_modify_resets_queue_priority_but_reduction_preserves_it() { &mut portfolio, &data, &StrategyDecision { - buy_denials: Default::default(), order_intents: vec![OrderIntent::ModifyOrder { order_id: initial_ids[0], new_total_quantity: None, @@ -5885,7 +5866,6 @@ fn broker_rejects_gtc_for_market_order() { &mut portfolio, &data, &StrategyDecision { - buy_denials: Default::default(), order_intents: vec![ OrderIntent::Shares { symbol: "000002.SZ".to_string(),