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

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

@@ -14,6 +14,8 @@ pub struct UniverseCandidate {
pub symbol: String,
pub market_cap_bn: f64,
pub free_float_cap_bn: f64,
pub band_low: f64,
pub band_high: f64,
}
pub struct SelectionContext<'a> {
@@ -29,51 +31,54 @@ pub trait UniverseSelector {
#[derive(Debug, Clone)]
pub struct DynamicMarketCapBandSelector {
pub base_index_level: f64,
pub bullish_threshold: f64,
pub neutral_threshold: f64,
pub bullish_band: (f64, f64),
pub neutral_band: (f64, f64),
pub defensive_band: (f64, f64),
pub base_cap_floor: f64,
pub cap_span: f64,
pub xs: f64,
pub top_n: usize,
}
impl DynamicMarketCapBandSelector {
pub fn demo(top_n: usize) -> Self {
pub fn new(
base_index_level: f64,
base_cap_floor: f64,
cap_span: f64,
xs: f64,
top_n: usize,
) -> Self {
Self {
base_index_level: 3000.0,
bullish_threshold: 1.02,
neutral_threshold: 1.0,
bullish_band: (30.0, 60.0),
neutral_band: (40.0, 90.0),
defensive_band: (60.0, 120.0),
base_index_level,
base_cap_floor,
cap_span,
xs,
top_n,
}
}
pub fn demo(top_n: usize) -> Self {
Self::new(2000.0, 7.0, 10.0, 4.0 / 500.0, top_n)
}
pub fn regime(&self, benchmark_level: f64) -> BandRegime {
let ratio = benchmark_level / self.base_index_level;
if ratio >= self.bullish_threshold {
if benchmark_level >= self.base_index_level + 400.0 {
BandRegime::Bullish
} else if ratio >= self.neutral_threshold {
} else if benchmark_level >= self.base_index_level {
BandRegime::Neutral
} else {
BandRegime::Defensive
}
}
fn band(&self, regime: BandRegime) -> (f64, f64) {
match regime {
BandRegime::Bullish => self.bullish_band,
BandRegime::Neutral => self.neutral_band,
BandRegime::Defensive => self.defensive_band,
}
pub fn band_for_level(&self, benchmark_level: f64) -> (f64, f64) {
let start = ((benchmark_level - self.base_index_level) * self.xs) + self.base_cap_floor;
let low = start.round();
(low, low + self.cap_span)
}
}
impl UniverseSelector for DynamicMarketCapBandSelector {
fn select(&self, ctx: &SelectionContext<'_>) -> Vec<UniverseCandidate> {
let regime = self.regime(ctx.benchmark.close);
let (min_cap, max_cap) = self.band(regime);
let _regime = self.regime(ctx.benchmark.close);
let (min_cap, max_cap) = self.band_for_level(ctx.benchmark.close);
let mut selected = ctx
.data
@@ -94,6 +99,8 @@ impl UniverseSelector for DynamicMarketCapBandSelector {
symbol: factor.symbol.clone(),
market_cap_bn: factor.market_cap_bn,
free_float_cap_bn: factor.free_float_cap_bn,
band_low: min_cap,
band_high: max_cap,
})
})
.collect::<Vec<_>>();