保留手工逐日进度并在交付校验失败时终止

This commit is contained in:
boris
2026-09-14 17:30:00 +08:00
parent f8955bfb18
commit 232e9ae154
2 changed files with 74 additions and 6 deletions
+71 -6
View File
@@ -438,8 +438,10 @@ pub struct BacktestDayProgress {
pub total_return: f64,
pub benchmark_close: f64,
pub daily_fill_count: usize,
pub daily_manual_fill_count: usize,
pub daily_order_count: usize,
pub cumulative_trade_count: usize,
pub cumulative_manual_fill_count: usize,
pub holding_count: usize,
pub notes: String,
pub diagnostics: String,
@@ -628,9 +630,19 @@ impl<S, C, R> BacktestEngine<S, C, R> {
}
pub fn with_observed_manual_executions(
mut self,
self,
replay: crate::manual_execution::ManualExecutionReplay,
) -> Result<Self, BacktestError>
where
S: Strategy,
{
self.with_observed_manual_execution_source(std::sync::Arc::new(replay))
}
pub fn with_observed_manual_execution_source(
mut self,
replay: std::sync::Arc<crate::manual_execution::ManualExecutionReplay>,
) -> Result<Self, BacktestError>
where
S: Strategy,
{
@@ -639,7 +651,7 @@ impl<S, C, R> BacktestEngine<S, C, R> {
&replay.position_exposure_events,
&replay.legacy_position_exposure_bps,
)?;
self.manual_execution_source = Some(std::sync::Arc::new(replay));
self.manual_execution_source = Some(replay);
Ok(self)
}
@@ -2686,6 +2698,21 @@ where
) -> Result<BacktestResult, BacktestError>
where
F: FnMut(&BacktestDayProgress),
{
self.run_with_fallible_progress_options(include_progress_details, include_progress_diagnostics, |event| {
on_progress(event);
Ok(())
})
}
pub fn run_with_fallible_progress_options<F>(
&mut self,
include_progress_details: bool,
include_progress_diagnostics: bool,
mut on_progress: F,
) -> Result<BacktestResult, BacktestError>
where
F: FnMut(&BacktestDayProgress) -> Result<(), BacktestError>,
{
let mut portfolio = PortfolioState::new(self.config.initial_cash);
let mut manual_cursor = self.manual_execution_source.as_ref().map(|source|
@@ -2902,8 +2929,10 @@ where
total_return: latest.unit_nav - 1.0,
benchmark_close: latest.benchmark_close,
daily_fill_count,
daily_manual_fill_count: result.manual_executions.len() - day_manual_start,
daily_order_count,
cumulative_trade_count: result.fills.len() + result.manual_executions.len(),
cumulative_manual_fill_count: result.manual_executions.len(),
holding_count,
notes: include_progress_diagnostics
.then(|| latest.notes.clone())
@@ -2917,14 +2946,14 @@ where
fills: include_progress_details
.then(|| result.fills[day_fill_start..].to_vec())
.unwrap_or_default(),
manual_executions: include_progress_details.then(|| result.manual_executions[day_manual_start..].to_vec()).unwrap_or_default(),
manual_executions: result.manual_executions[day_manual_start..].to_vec(),
holdings: include_progress_details
.then(|| result.daily_holdings[holding_start..].to_vec())
.unwrap_or_default(),
process_events: include_progress_details
.then(|| result.process_events[progress_process_start..].to_vec())
.unwrap_or_default(),
});
})?;
stock_equity_by_date.insert(execution_date, portfolio.total_equity());
continue;
};
@@ -3943,8 +3972,10 @@ where
total_return: latest.unit_nav - 1.0,
benchmark_close: latest.benchmark_close,
daily_fill_count,
daily_manual_fill_count: result.manual_executions.len() - day_manual_start,
daily_order_count,
cumulative_trade_count: result.fills.len() + result.manual_executions.len(),
cumulative_manual_fill_count: result.manual_executions.len(),
holding_count,
notes: include_progress_diagnostics
.then(|| latest.notes.clone())
@@ -3958,14 +3989,14 @@ where
fills: include_progress_details
.then(|| result.fills[day_fill_start..].to_vec())
.unwrap_or_default(),
manual_executions: include_progress_details.then(|| result.manual_executions[day_manual_start..].to_vec()).unwrap_or_default(),
manual_executions: result.manual_executions[day_manual_start..].to_vec(),
holdings: include_progress_details
.then(|| result.daily_holdings[holding_start..].to_vec())
.unwrap_or_default(),
process_events: include_progress_details
.then(|| result.process_events[progress_process_start..].to_vec())
.unwrap_or_default(),
});
})?;
stock_equity_by_date.insert(execution_date, portfolio.total_equity());
}
@@ -8076,6 +8107,40 @@ mod tests {
);
}
#[test]
fn compact_manual_progress_keeps_exact_applications_and_fallible_delivery_stops_the_run() {
struct NoOrders;
impl Strategy for NoOrders {
fn name(&self) -> &str { "manual-progress" }
fn on_day(&mut self, _: &StrategyContext<'_>) -> Result<StrategyDecision, crate::BacktestError> { Ok(Default::default()) }
fn requires_minute_callbacks(&self) -> bool { false }
}
let date = d(2026, 6, 1);
let data = clock_probe_data(date, &[]);
let broker = BrokerSimulator::new_with_execution_price(ChinaAShareCostModel::default(), ChinaEquityRuleHooks, PriceField::Open).with_matching_type(MatchingType::NextBarOpen).with_volume_limit(false).with_liquidity_limit(false);
let config = BacktestConfig { initial_cash: 10000., benchmark_code: "000852.SH".into(), start_date: Some(date), end_date: Some(date), decision_lag_trading_days: 0, execution_price_field: PriceField::Open };
let source = std::sync::Arc::new(observed_manual_replay(date, &[(10, 0)]));
let mut engine = BacktestEngine::new(data, NoOrders, broker, config).with_observed_manual_execution_source(source.clone()).unwrap();
let mut days = vec![];
let result = engine.run_with_progress_options(false, false, |event| days.push(event.clone())).unwrap();
assert_eq!(days.len(), 1);
assert_eq!(days[0].daily_manual_fill_count, 1);
assert_eq!(days[0].cumulative_manual_fill_count, 1);
assert_eq!(days[0].manual_executions, result.manual_executions);
assert!(days[0].fills.is_empty() && days[0].orders.is_empty());
assert!(std::sync::Arc::ptr_eq(result.manual_execution_source.as_ref().unwrap(), &source));
for lag in [0, 1] {
let mut engine = engine_with_matching(MatchingType::CurrentBarClose, PriceField::Close, lag);
let mut delivered = 0;
let result = engine.run_with_fallible_progress_options(false, false, |_| {
delivered += 1;
Err(crate::BacktestError::Execution("progress source binding failed".into()))
});
assert!(result.unwrap_err().to_string().contains("progress source binding failed"));
assert_eq!(delivered, 1);
}
}
#[test]
fn compact_progress_keeps_counts_without_event_payload_clones() {
let mut engine = engine_with_matching(MatchingType::CurrentBarClose, PriceField::Close, 0);
+3
View File
@@ -46,3 +46,6 @@ Core872通过(9项原ignore不计通过),交易工作区619普通测试通
## 2026-09-14 运行级仓位配置补充
v3 手工输入独立携带审计仓位/权重时间线与旧日级前缀,不覆盖原策略或股票池。仅已成交证券产生独立行情需求;补充范围不会成为选股候选。恢复跟随回到原规则,未来事件不能被伪称为截止时刻前已观察事实。Core 878 项本机通过,尚未部署;PG、期间隔离、权限与剩余联合验收见 `../../fidc-trading-platform/docs/shadow-manual-input-20260914.md`。本节不替代前述时钟证据,也不宣称全部矩阵完成。
## 逐日手工交付补充
手工观察输入可通过Arc与进度投影共享;默认紧凑进度保留当日手工应用及独立累计计数,原生明细开关不改。新增可失败进度回调,投影来源/计数错误会终止本次回测,不忽略错误后返回成功。Core879本机通过,当前完整版本Linux及发布验收未完成;共享最终/逐日投影与真实本机WebSocket证据见Service `docs/manual-stream-projection-20260914.md`