fix(backtest): keep next-open limit remainders active on the execution day
This commit is contained in:
@@ -2850,11 +2850,11 @@ where
|
||||
)?;
|
||||
|
||||
if should_run_minute_events(&intraday_schedule_rules, &self.subscriptions)
|
||||
|| (self.broker.has_open_orders() && self.broker.matching_type_uses_intraday_quotes())
|
||||
|| (self.broker.has_open_orders() && self.broker.drives_resting_quote_clock())
|
||||
{
|
||||
let unfiltered_minute_stream = self.subscriptions.is_empty();
|
||||
let mut full_minute_symbols = self.subscriptions.clone();
|
||||
if self.broker.matching_type_uses_intraday_quotes() {
|
||||
if self.broker.drives_resting_quote_clock() {
|
||||
full_minute_symbols.extend(self.broker.open_order_views().into_iter().map(|order| order.symbol));
|
||||
}
|
||||
if self.execution_quote_loader.is_some() && !full_minute_symbols.is_empty() {
|
||||
@@ -3125,7 +3125,7 @@ where
|
||||
.map(|order| order.symbol)
|
||||
.filter(|symbol| !full_minute_symbols.contains(symbol))
|
||||
.collect::<BTreeSet<_>>();
|
||||
if !newly_pending.is_empty() && self.broker.matching_type_uses_intraday_quotes() {
|
||||
if !newly_pending.is_empty() && self.broker.drives_resting_quote_clock() {
|
||||
full_minute_symbols.extend(newly_pending.iter().cloned());
|
||||
if self.execution_quote_loader.is_some() {
|
||||
self.load_missing_execution_quotes(execution_date, None, None, &mut newly_pending)?;
|
||||
@@ -5904,10 +5904,11 @@ mod tests {
|
||||
}], ..StrategyDecision::default() })
|
||||
}
|
||||
}
|
||||
for scenario in 0..4 {
|
||||
for scenario in 0..5 {
|
||||
let partial = scenario == 1;
|
||||
let closing_only = scenario >= 2;
|
||||
let date = if closing_only { d(2026, 7, 6) } else { d(2026, 6, 1) };
|
||||
let closing_only = matches!(scenario,2|3);
|
||||
let delayed = scenario == 4;
|
||||
let date = if closing_only { d(2026, 7, 6) } else if delayed { d(2026, 6, 2) } else { d(2026, 6, 1) };
|
||||
let quote = |hour, minute, price| IntradayExecutionQuote {
|
||||
date, symbol: SYMBOL.into(), timestamp: date.and_hms_opt(hour, minute, 0).unwrap(),
|
||||
last_price: price, bid1: price, ask1: price, bid1_volume: 10_000, ask1_volume: 10_000,
|
||||
@@ -5920,17 +5921,20 @@ mod tests {
|
||||
let last = if closing_only { quote(15, 0, if scenario == 2 { 9.8 } else { 10.2 }) } else { quote(10, 1, 9.8) };
|
||||
let mut post_close = quote(15, 5, 9.7);
|
||||
post_close.trading_phase = Some("post_close_fixed_price".into());
|
||||
let mut data = dataset_from_market_and_candidates(vec![market(date, 10.2, 9.8)], vec![candidate(date)]);
|
||||
let prior = date.pred_opt().unwrap();
|
||||
let markets = if delayed {vec![market(prior,10.2,10.2),market(date,10.2,9.8)]} else {vec![market(date,10.2,9.8)]};
|
||||
let candidates = if delayed {vec![candidate(prior),candidate(date)]} else {vec![candidate(date)]};
|
||||
let mut data = dataset_from_market_and_candidates(markets,candidates);
|
||||
data.add_execution_quotes(vec![first.clone()]);
|
||||
let broker = BrokerSimulator::new(ChinaAShareCostModel::default(), ChinaEquityRuleHooks)
|
||||
.with_matching_type(MatchingType::CurrentBarClose)
|
||||
.with_intraday_execution_start_time(NaiveTime::from_hms_opt(9, 30, 0).unwrap())
|
||||
.with_matching_type(if delayed {MatchingType::NextBarOpen} else {MatchingType::CurrentBarClose})
|
||||
.with_volume_limit(partial).with_volume_percent(0.01).with_liquidity_limit(false).with_inactive_limit(false);
|
||||
let broker = if delayed {broker} else {broker.with_intraday_execution_start_time(NaiveTime::from_hms_opt(9,30,0).unwrap())};
|
||||
let requests = Arc::new(Mutex::new(Vec::new()));
|
||||
let captured = Arc::clone(&requests);
|
||||
let mut engine = BacktestEngine::new(data, RestingLimit { quantity: if partial { 300 } else { 100 } }, broker, BacktestConfig {
|
||||
initial_cash: 100_000.0, benchmark_code: "000852.SH".into(), start_date: Some(date), end_date: Some(date),
|
||||
decision_lag_trading_days: 0, execution_price_field: PriceField::Close,
|
||||
initial_cash: 100_000.0, benchmark_code: "000852.SH".into(), start_date: Some(if delayed {prior} else {date}), end_date: Some(date),
|
||||
decision_lag_trading_days: usize::from(delayed), execution_price_field: if delayed {PriceField::Open} else {PriceField::Close},
|
||||
}).with_execution_quote_loader(move |request| {
|
||||
captured.lock().unwrap().push((request.start_time, request.end_time));
|
||||
Ok(vec![earlier.clone(), first.clone(), unchanged.clone(), later.clone(), last.clone(), post_close.clone()])
|
||||
|
||||
Reference in New Issue
Block a user