修正回测推进并增强策略样例
This commit is contained in:
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user