修正回测推进并增强策略样例

This commit is contained in:
zsb
2026-04-08 19:10:28 -07:00
parent a26049ff15
commit 581021651c
8 changed files with 465 additions and 66 deletions

View File

@@ -8,7 +8,7 @@ use crate::data::{BenchmarkSnapshot, DataSet, DataSetError, PriceField};
use crate::events::{AccountEvent, FillEvent, OrderEvent, PositionEvent};
use crate::portfolio::{HoldingSummary, PortfolioState};
use crate::rules::EquityRuleHooks;
use crate::strategy::{Strategy, StrategyContext, StrategyDecision};
use crate::strategy::{Strategy, StrategyContext};
#[derive(Debug, Error)]
pub enum BacktestError {
@@ -30,6 +30,8 @@ pub enum BacktestError {
pub struct BacktestConfig {
pub initial_cash: f64,
pub benchmark_code: String,
pub start_date: Option<NaiveDate>,
pub end_date: Option<NaiveDate>,
}
#[derive(Debug, Clone, Serialize)]
@@ -87,9 +89,25 @@ where
{
pub fn run(&mut self) -> Result<BacktestResult, BacktestError> {
let mut portfolio = PortfolioState::new(self.config.initial_cash);
let execution_dates = self
.data
.calendar()
.iter()
.filter(|date| self.config.start_date.map(|start| *date >= start).unwrap_or(true))
.filter(|date| self.config.end_date.map(|end| *date <= end).unwrap_or(true))
.filter(|date| {
!self.data.factor_snapshots_on(*date).is_empty() && !self.data.candidate_snapshots_on(*date).is_empty()
})
.collect::<Vec<_>>();
let mut result = BacktestResult {
strategy_name: self.strategy.name().to_string(),
benchmark_series: self.data.benchmark_series(),
benchmark_series: self
.data
.benchmark_series()
.into_iter()
.filter(|row| self.config.start_date.map(|start| row.date >= start).unwrap_or(true))
.filter(|row| self.config.end_date.map(|end| row.date <= end).unwrap_or(true))
.collect(),
order_events: Vec::new(),
fills: Vec::new(),
position_events: Vec::new(),
@@ -98,20 +116,21 @@ where
holdings_summary: Vec::new(),
};
for execution_date in self.data.calendar().iter() {
let decision = match self.data.calendar().previous_day(execution_date) {
Some(decision_date) => {
let decision_index = self.data.calendar().index_of(decision_date).unwrap_or(0);
for (execution_idx, execution_date) in execution_dates.iter().copied().enumerate() {
let decision = execution_idx
.checked_sub(1)
.map(|decision_idx| {
let decision_date = execution_dates[decision_idx];
self.strategy.on_day(&StrategyContext {
execution_date,
decision_date,
decision_index,
decision_index: decision_idx,
data: &self.data,
portfolio: &portfolio,
})?
}
None => StrategyDecision::default(),
};
})
})
.transpose()?
.unwrap_or_default();
let report = self
.broker
@@ -140,7 +159,7 @@ where
});
}
if let Some(last_date) = self.data.calendar().days().last().copied() {
if let Some(last_date) = execution_dates.last().copied() {
result.holdings_summary = portfolio.holdings_summary(last_date);
}