Compare commits

...

1 Commits

Author SHA1 Message Date
boris 98732bbc46 修复次日开盘观测被日线价格分支跳过行情加载 2026-09-19 22:47:03 +08:00
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);
@@ -9,3 +9,5 @@
执行状态新增 `last_target_weight_ratios`,首次记录精确比例升级schema2。旧schema1可读但不得携带新比例字段;旧消费者应拒绝新状态,回滚不能删除或降精度重写状态。回测、Paper、Live及Strategy Runtime都必须共同消费该比例,ETF顺延目标也携带相同比例。
新增临界100股补仓、调序不改变等权金额、停牌/候补、显式部分预算、状态序列化回读回归。当前为候选:本机Rust语法检查通过,类型/运行测试受Xcode许可阻断,转177验证;未通过Linux测试前不得发布。
后续真实Source回放补充:原始报价门禁打开后,Engine仍因PriceField::Open提前跳过行情加载;不能把此错误标成原始数据缺失。补充按实际撮合是否需要盘中观测判断加载路径,测试验证NextBarOpen加载执行日09:30报价、日终审计保持不加载。实际发布、回放及缺数清单以工作区`docs/fidc/stock-pool-precision-correction-20260919.md`为准。