修复日内时钟提前记账并按原订单续执行算法单

This commit is contained in:
boris
2026-09-14 03:52:52 +08:00
parent 3a3091a2cf
commit 237ee15a51
8 changed files with 2001 additions and 346 deletions
+1
View File
@@ -2748,6 +2748,7 @@ fn strategy_context_exposes_engine_native_account_runtime_view() {
avg_price: 0.0,
transaction_cost: 0.0,
limit_price: 12.0,
reserved_cash: None,
reason: "pending_buy".to_string(),
}];
let subscriptions = BTreeSet::new();
@@ -887,6 +887,42 @@ fn historical_etf_late_signal_freezes_money_and_requantifies_at_next_official_op
assert!(result.terminal_audit.is_clean());
}
#[test]
fn deferred_etf_open_does_not_appear_in_a_pre_open_minute_callback() {
use fidc_core::strategy::{Strategy,StrategyContext};
use std::{cell::RefCell,rc::Rc};
struct ObservedPool { inner:EtfPoolSignal, observations:Rc<RefCell<Vec<(chrono::NaiveDateTime,u32,usize)>>> }
impl Strategy for ObservedPool {
fn name(&self)->&str {"ETF actual opening clock"}
fn initial_subscriptions(&self)->BTreeSet<String> {BTreeSet::from([code(1)])}
fn decision_quote_times(&self)->Vec<chrono::NaiveTime> {self.inner.decision_quote_times()}
fn decision_quote_symbols(&mut self,ctx:&StrategyContext<'_>)->Result<BTreeSet<String>,fidc_core::BacktestError> {self.inner.decision_quote_symbols(ctx)}
fn on_day(&mut self,ctx:&StrategyContext<'_>)->Result<StrategyDecision,fidc_core::BacktestError> {self.inner.on_day(ctx)}
fn on_minute(&mut self,ctx:&StrategyContext<'_>,quote:&IntradayExecutionQuote)->Result<StrategyDecision,fidc_core::BacktestError> {
if quote.date==day(5) {self.observations.borrow_mut().push((quote.timestamp,
ctx.portfolio.position(&code(2)).map_or(0,|position|position.quantity),ctx.fills.iter().filter(|fill|fill.symbol==code(2)).count()));}
Ok(StrategyDecision::default())
}
}
let time=chrono::NaiveTime::from_hms_opt(13,0,0).unwrap();
let mut data=etf_fallback_fixture(time);
let quote=data.execution_quotes_on(day(5),&code(1))[0].clone();
data.add_execution_quotes([(9,15),(9,31)].into_iter().map(|(hour,minute)| {
let mut row=quote.clone();row.timestamp=day(5).and_hms_opt(hour,minute,0).unwrap();row
}).collect());
let observations=Rc::new(RefCell::new(Vec::new()));
let broker=broker(false).with_matching_type(MatchingType::MinuteLast)
.with_execution_price_field(PriceField::Last).with_intraday_execution_start_time(time)
.with_historical_etf_open_fallback(true);
let result=BacktestEngine::new(data,ObservedPool {inner:EtfPoolSignal{at:time,condition:String::new()},observations:observations.clone()},broker,BacktestConfig {
initial_cash:30000.,benchmark_code:"000300.SH".into(),start_date:Some(day(2)),end_date:Some(day(5)),decision_lag_trading_days:0,execution_price_field:PriceField::Last,
}).with_execution_quote_loader(|_|Ok(vec![])).run().unwrap();
let observations=observations.borrow();
assert_eq!(observations[0],(day(5).and_hms_opt(9,15,0).unwrap(),0,0));
assert_eq!(observations[1],(day(5).and_hms_opt(9,31,0).unwrap(),3700,1));
assert_eq!(result.fills.iter().filter(|fill|fill.symbol==code(2)).count(),1);
}
#[test]
fn historical_etf_pending_target_at_end_is_not_a_fake_order_or_fill() {
let result=run_etf_fallback(chrono::NaiveTime::from_hms_opt(13,0,0).unwrap(),day(2),true,"",false,false).unwrap();