保留手工逐日进度并在交付校验失败时终止
This commit is contained in:
@@ -438,8 +438,10 @@ pub struct BacktestDayProgress {
|
|||||||
pub total_return: f64,
|
pub total_return: f64,
|
||||||
pub benchmark_close: f64,
|
pub benchmark_close: f64,
|
||||||
pub daily_fill_count: usize,
|
pub daily_fill_count: usize,
|
||||||
|
pub daily_manual_fill_count: usize,
|
||||||
pub daily_order_count: usize,
|
pub daily_order_count: usize,
|
||||||
pub cumulative_trade_count: usize,
|
pub cumulative_trade_count: usize,
|
||||||
|
pub cumulative_manual_fill_count: usize,
|
||||||
pub holding_count: usize,
|
pub holding_count: usize,
|
||||||
pub notes: String,
|
pub notes: String,
|
||||||
pub diagnostics: String,
|
pub diagnostics: String,
|
||||||
@@ -628,9 +630,19 @@ impl<S, C, R> BacktestEngine<S, C, R> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn with_observed_manual_executions(
|
pub fn with_observed_manual_executions(
|
||||||
mut self,
|
self,
|
||||||
replay: crate::manual_execution::ManualExecutionReplay,
|
replay: crate::manual_execution::ManualExecutionReplay,
|
||||||
) -> Result<Self, BacktestError>
|
) -> 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
|
where
|
||||||
S: Strategy,
|
S: Strategy,
|
||||||
{
|
{
|
||||||
@@ -639,7 +651,7 @@ impl<S, C, R> BacktestEngine<S, C, R> {
|
|||||||
&replay.position_exposure_events,
|
&replay.position_exposure_events,
|
||||||
&replay.legacy_position_exposure_bps,
|
&replay.legacy_position_exposure_bps,
|
||||||
)?;
|
)?;
|
||||||
self.manual_execution_source = Some(std::sync::Arc::new(replay));
|
self.manual_execution_source = Some(replay);
|
||||||
Ok(self)
|
Ok(self)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -2686,6 +2698,21 @@ where
|
|||||||
) -> Result<BacktestResult, BacktestError>
|
) -> Result<BacktestResult, BacktestError>
|
||||||
where
|
where
|
||||||
F: FnMut(&BacktestDayProgress),
|
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 portfolio = PortfolioState::new(self.config.initial_cash);
|
||||||
let mut manual_cursor = self.manual_execution_source.as_ref().map(|source|
|
let mut manual_cursor = self.manual_execution_source.as_ref().map(|source|
|
||||||
@@ -2902,8 +2929,10 @@ where
|
|||||||
total_return: latest.unit_nav - 1.0,
|
total_return: latest.unit_nav - 1.0,
|
||||||
benchmark_close: latest.benchmark_close,
|
benchmark_close: latest.benchmark_close,
|
||||||
daily_fill_count,
|
daily_fill_count,
|
||||||
|
daily_manual_fill_count: result.manual_executions.len() - day_manual_start,
|
||||||
daily_order_count,
|
daily_order_count,
|
||||||
cumulative_trade_count: result.fills.len() + result.manual_executions.len(),
|
cumulative_trade_count: result.fills.len() + result.manual_executions.len(),
|
||||||
|
cumulative_manual_fill_count: result.manual_executions.len(),
|
||||||
holding_count,
|
holding_count,
|
||||||
notes: include_progress_diagnostics
|
notes: include_progress_diagnostics
|
||||||
.then(|| latest.notes.clone())
|
.then(|| latest.notes.clone())
|
||||||
@@ -2917,14 +2946,14 @@ where
|
|||||||
fills: include_progress_details
|
fills: include_progress_details
|
||||||
.then(|| result.fills[day_fill_start..].to_vec())
|
.then(|| result.fills[day_fill_start..].to_vec())
|
||||||
.unwrap_or_default(),
|
.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
|
holdings: include_progress_details
|
||||||
.then(|| result.daily_holdings[holding_start..].to_vec())
|
.then(|| result.daily_holdings[holding_start..].to_vec())
|
||||||
.unwrap_or_default(),
|
.unwrap_or_default(),
|
||||||
process_events: include_progress_details
|
process_events: include_progress_details
|
||||||
.then(|| result.process_events[progress_process_start..].to_vec())
|
.then(|| result.process_events[progress_process_start..].to_vec())
|
||||||
.unwrap_or_default(),
|
.unwrap_or_default(),
|
||||||
});
|
})?;
|
||||||
stock_equity_by_date.insert(execution_date, portfolio.total_equity());
|
stock_equity_by_date.insert(execution_date, portfolio.total_equity());
|
||||||
continue;
|
continue;
|
||||||
};
|
};
|
||||||
@@ -3943,8 +3972,10 @@ where
|
|||||||
total_return: latest.unit_nav - 1.0,
|
total_return: latest.unit_nav - 1.0,
|
||||||
benchmark_close: latest.benchmark_close,
|
benchmark_close: latest.benchmark_close,
|
||||||
daily_fill_count,
|
daily_fill_count,
|
||||||
|
daily_manual_fill_count: result.manual_executions.len() - day_manual_start,
|
||||||
daily_order_count,
|
daily_order_count,
|
||||||
cumulative_trade_count: result.fills.len() + result.manual_executions.len(),
|
cumulative_trade_count: result.fills.len() + result.manual_executions.len(),
|
||||||
|
cumulative_manual_fill_count: result.manual_executions.len(),
|
||||||
holding_count,
|
holding_count,
|
||||||
notes: include_progress_diagnostics
|
notes: include_progress_diagnostics
|
||||||
.then(|| latest.notes.clone())
|
.then(|| latest.notes.clone())
|
||||||
@@ -3958,14 +3989,14 @@ where
|
|||||||
fills: include_progress_details
|
fills: include_progress_details
|
||||||
.then(|| result.fills[day_fill_start..].to_vec())
|
.then(|| result.fills[day_fill_start..].to_vec())
|
||||||
.unwrap_or_default(),
|
.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
|
holdings: include_progress_details
|
||||||
.then(|| result.daily_holdings[holding_start..].to_vec())
|
.then(|| result.daily_holdings[holding_start..].to_vec())
|
||||||
.unwrap_or_default(),
|
.unwrap_or_default(),
|
||||||
process_events: include_progress_details
|
process_events: include_progress_details
|
||||||
.then(|| result.process_events[progress_process_start..].to_vec())
|
.then(|| result.process_events[progress_process_start..].to_vec())
|
||||||
.unwrap_or_default(),
|
.unwrap_or_default(),
|
||||||
});
|
})?;
|
||||||
stock_equity_by_date.insert(execution_date, portfolio.total_equity());
|
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]
|
#[test]
|
||||||
fn compact_progress_keeps_counts_without_event_payload_clones() {
|
fn compact_progress_keeps_counts_without_event_payload_clones() {
|
||||||
let mut engine = engine_with_matching(MatchingType::CurrentBarClose, PriceField::Close, 0);
|
let mut engine = engine_with_matching(MatchingType::CurrentBarClose, PriceField::Close, 0);
|
||||||
|
|||||||
@@ -46,3 +46,6 @@ Core872通过(9项原ignore不计通过),交易工作区619普通测试通
|
|||||||
## 2026-09-14 运行级仓位配置补充
|
## 2026-09-14 运行级仓位配置补充
|
||||||
|
|
||||||
v3 手工输入独立携带审计仓位/权重时间线与旧日级前缀,不覆盖原策略或股票池。仅已成交证券产生独立行情需求;补充范围不会成为选股候选。恢复跟随回到原规则,未来事件不能被伪称为截止时刻前已观察事实。Core 878 项本机通过,尚未部署;PG、期间隔离、权限与剩余联合验收见 `../../fidc-trading-platform/docs/shadow-manual-input-20260914.md`。本节不替代前述时钟证据,也不宣称全部矩阵完成。
|
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