修复次日开盘观测被日线价格分支跳过行情加载

This commit is contained in:
boris
2026-09-19 22:47:03 +08:00
parent 0ac07ac0f1
commit 98732bbc46
2 changed files with 44 additions and 0 deletions
+42
View File
@@ -1298,6 +1298,7 @@ where
submission_time,
);
if self.broker.execution_price_field() != PriceField::Last
&& !self.broker.matching_type_uses_intraday_quotes()
&& !decision_has_algo_execution(decision)
&& post_close_window.is_none()
{
@@ -8941,6 +8942,47 @@ mod tests {
)
}
#[test]
fn next_open_observation_loads_quotes_even_when_execution_price_is_open() {
use crate::execution_capacity::VolumeCapacityMode;
let date = d(2025, 1, 3);
let signal = d(2025, 1, 2);
let open = NaiveTime::from_hms_opt(9, 30, 0).unwrap();
for mode in [VolumeCapacityMode::ExecutionObservation, VolumeCapacityMode::SessionCapacityAudit] {
let mut broker = BrokerSimulator::new_with_execution_price(
ChinaAShareCostModel::default(), ChinaEquityRuleHooks, PriceField::Open,
).with_matching_type(MatchingType::NextBarOpen)
.with_volume_limit(true).with_volume_capacity_mode(mode).with_liquidity_limit(false);
if mode == VolumeCapacityMode::ExecutionObservation {
broker = broker.with_intraday_execution_start_time(open);
}
let calls = Arc::new(Mutex::new(Vec::new()));
let captured = calls.clone();
let mut engine = BacktestEngine::new(dataset(), BuyWhenDecisionDateStrategy { decision_date: signal }, broker,
BacktestConfig { initial_cash: 100_000., benchmark_code: "000852.SH".into(),
start_date: Some(signal), end_date: Some(date), decision_lag_trading_days: 1,
execution_price_field: PriceField::Open })
.with_execution_quote_loader(move |request| {
captured.lock().unwrap().push(request.clone());
Ok(clock_probe_data(request.date, &[(9,30,10.)]).snapshot_components().execution_quotes)
});
let decision = StrategyDecision { order_intents: vec![OrderIntent::Shares {
symbol: SYMBOL.into(), quantity: 100, reason: "next-open-loader-regression".into(),
}], ..Default::default() };
engine.ensure_execution_quotes_for_decision(date, signal, &PortfolioState::new(100_000.), &[], &decision, None, None).unwrap();
let calls = calls.lock().unwrap();
if mode == VolumeCapacityMode::ExecutionObservation {
assert_eq!(calls.len(), 1);
assert_eq!(calls[0].date, date);
assert_eq!(calls[0].start_time, Some(open));
assert_eq!(calls[0].symbols, BTreeSet::from([SYMBOL.to_string()]));
assert_eq!(engine.data.execution_quotes_on(date, SYMBOL).len(), 1);
} else {
assert!(calls.is_empty(), "daily audit must not silently become an opening-liquidity model");
}
}
}
#[test]
fn full_minute_coverage_rejects_missing_active_bars_but_allows_paused_or_zero_volume() {
let first = d(2025, 1, 2);