保留手工逐日进度并在交付校验失败时终止
This commit is contained in:
@@ -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);
|
||||
|
||||
@@ -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`。
|
||||
|
||||
Reference in New Issue
Block a user