增强回测引擎第二版策略与快照层

This commit is contained in:
zsb
2026-04-07 00:34:52 -07:00
parent 334864cbc5
commit d039c4e741
10 changed files with 244 additions and 86 deletions

View File

@@ -110,14 +110,31 @@ pub struct CandidateEligibility {
pub is_paused: bool,
pub allow_buy: bool,
pub allow_sell: bool,
pub is_kcb: bool,
pub is_one_yuan: bool,
}
impl CandidateEligibility {
pub fn eligible_for_selection(&self) -> bool {
!self.is_st && !self.is_new_listing && !self.is_paused && self.allow_buy && self.allow_sell
!self.is_st
&& !self.is_new_listing
&& !self.is_paused
&& !self.is_kcb
&& !self.is_one_yuan
&& self.allow_buy
&& self.allow_sell
}
}
#[derive(Debug, Clone)]
pub struct DailySnapshotBundle {
pub date: NaiveDate,
pub benchmark: BenchmarkSnapshot,
pub market: Vec<DailyMarketSnapshot>,
pub factors: Vec<DailyFactorSnapshot>,
pub candidates: Vec<CandidateEligibility>,
}
#[derive(Debug, Clone)]
pub struct DataSet {
instruments: HashMap<String, Instrument>,
@@ -246,6 +263,20 @@ impl DataSet {
.unwrap_or_default()
}
pub fn bundle_on(&self, date: NaiveDate) -> Result<DailySnapshotBundle, DataSetError> {
let benchmark = self
.benchmark(date)
.cloned()
.ok_or(DataSetError::MissingBenchmark { date })?;
Ok(DailySnapshotBundle {
date,
benchmark,
market: self.market_by_date.get(&date).cloned().unwrap_or_default(),
factors: self.factor_by_date.get(&date).cloned().unwrap_or_default(),
candidates: self.candidate_by_date.get(&date).cloned().unwrap_or_default(),
})
}
pub fn benchmark_closes_up_to(&self, date: NaiveDate, lookback: usize) -> Vec<f64> {
self.calendar
.trailing_days(date, lookback)
@@ -342,6 +373,8 @@ fn read_candidates(path: &Path) -> Result<Vec<CandidateEligibility>, DataSetErro
is_paused: row.parse_bool(4)?,
allow_buy: row.parse_bool(5)?,
allow_sell: row.parse_bool(6)?,
is_kcb: row.parse_optional_bool(7).unwrap_or(false),
is_one_yuan: row.parse_optional_bool(8).unwrap_or(false),
});
}
Ok(snapshots)
@@ -415,6 +448,12 @@ impl CsvRow {
message: format!("invalid bool: {err}"),
})
}
fn parse_optional_bool(&self, index: usize) -> Option<bool> {
self.fields
.get(index)
.and_then(|value| value.parse::<bool>().ok())
}
}
fn read_rows(path: &Path) -> Result<Vec<CsvRow>, DataSetError> {