按实际委托时间选择盘后撮合阶段

This commit is contained in:
boris
2026-08-28 00:12:19 +08:00
parent a9511f9a4a
commit 6c47c33cab
3 changed files with 342 additions and 27 deletions
+92 -4
View File
@@ -638,15 +638,23 @@ where
if self.execution_quote_loader.is_none() {
return Ok(());
}
let post_close_window = self
.broker
.post_close_execution_quote_window(execution_date);
if self.broker.execution_price_field() != PriceField::Last
&& !decision_has_algo_execution(decision)
&& post_close_window.is_none()
{
return Ok(());
}
let caller_start_time = start_time;
let caller_end_time = end_time;
let start_time = caller_start_time.or_else(|| self.broker.intraday_execution_start_time());
let start_time = post_close_window
.map(|window| window.0)
.or(caller_start_time)
.or_else(|| self.broker.intraday_execution_start_time());
let end_time = post_close_window.map(|window| window.1).or(caller_end_time);
let mut symbols = execution_quote_symbols_for_decision(decision, portfolio, open_orders);
self.load_missing_execution_quotes(execution_date, start_time, end_time, &mut symbols)?;
@@ -4614,15 +4622,16 @@ mod tests {
use std::cell::RefCell;
use std::collections::{BTreeMap, BTreeSet};
use std::rc::Rc;
use std::sync::{Arc, Mutex};
use chrono::NaiveDate;
use chrono::{NaiveDate, NaiveTime};
use super::{BacktestConfig, BacktestEngine};
use crate::broker::{BrokerSimulator, MatchingType};
use crate::broker::{BrokerSimulator, MatchingType, SlippageModel};
use crate::cost::ChinaAShareCostModel;
use crate::data::{
BenchmarkSnapshot, CandidateEligibility, DailyFactorSnapshot, DailyMarketSnapshot, DataSet,
PriceField,
IntradayExecutionQuote, PriceField,
};
use crate::events::{OrderSide, OrderStatus};
use crate::instrument::Instrument;
@@ -5302,6 +5311,85 @@ mod tests {
.expect("backtest run")
}
#[test]
fn current_close_order_at_1500_loads_and_uses_post_close_matching_window() {
let date = d(2026, 7, 6);
let data = dataset_from_market_and_candidates(
vec![market(date, 9.5, 10.0)],
vec![candidate(date)],
);
let broker = BrokerSimulator::new_with_execution_price(
ChinaAShareCostModel::default(),
ChinaEquityRuleHooks,
PriceField::Close,
)
.with_matching_type(MatchingType::CurrentBarClose)
.with_intraday_execution_start_time(
NaiveTime::from_hms_opt(15, 0, 0).expect("valid submission time"),
)
.with_slippage_model(SlippageModel::PriceRatio(0.25))
.with_volume_limit(false)
.with_liquidity_limit(false)
.with_inactive_limit(false);
let config = BacktestConfig {
initial_cash: 100_000.0,
benchmark_code: "000852.SH".to_string(),
start_date: Some(date),
end_date: Some(date),
decision_lag_trading_days: 0,
execution_price_field: PriceField::Close,
};
let requests = Arc::new(Mutex::new(Vec::new()));
let captured = Arc::clone(&requests);
let mut engine = BacktestEngine::new(
data,
BuyWhenDecisionDateStrategy {
decision_date: date,
},
broker,
config,
)
.with_execution_quote_loader(move |request| {
captured
.lock()
.expect("request capture lock")
.push((request.start_time, request.end_time));
Ok(request
.symbols
.into_iter()
.map(|symbol| IntradayExecutionQuote {
date: request.date,
symbol,
timestamp: request.date.and_hms_opt(15, 5, 0).expect("valid timestamp"),
last_price: 12.0,
bid1: 11.99,
ask1: 12.01,
bid1_volume: 10_000,
ask1_volume: 10_000,
volume_delta: 10_000,
amount_delta: 120_000.0,
trading_phase: Some("post_close_fixed_price".to_string()),
})
.collect())
});
let result = engine.run().expect("post-close backtest run");
assert_eq!(
requests.lock().expect("request capture lock").as_slice(),
&[(
NaiveTime::from_hms_opt(15, 5, 0),
NaiveTime::from_hms_opt(15, 30, 0),
)]
);
assert_eq!(result.fills.len(), 1, "{result:?}");
assert_eq!(result.fills[0].price, 10.0);
assert_eq!(
result.fills[0].execution_timestamp,
date.and_hms_opt(15, 5, 0)
);
}
#[test]
fn compact_progress_keeps_counts_without_event_payload_clones() {
let mut engine = engine_with_matching(MatchingType::CurrentBarClose, PriceField::Close, 0);