修正复权序列未来数据污染

This commit is contained in:
boris
2026-07-17 23:22:39 +08:00
parent 59a500b879
commit af4cd25f47
2 changed files with 85 additions and 23 deletions
+84 -19
View File
@@ -491,8 +491,8 @@ struct SymbolPriceSeries {
#[derive(Debug, Clone)]
struct AdjustedCloseSeries {
dates: Vec<NaiveDate>,
backward_factors: Vec<f64>,
back_adjusted_closes: Vec<f64>,
backward_factors: Vec<Option<f64>>,
back_adjusted_closes: Vec<Option<f64>>,
}
impl AdjustedCloseSeries {
@@ -503,22 +503,18 @@ impl AdjustedCloseSeries {
let mut backward_factors = Vec::with_capacity(market.dates.len());
let mut back_adjusted_closes = Vec::with_capacity(market.dates.len());
for (date, close) in market.dates.iter().zip(&market.closes) {
if !close.is_finite() || *close <= 0.0 {
return None;
}
let factor = factor_by_date
.get(date)
.and_then(|rows| {
find_arc_by_symbol(rows, &market.symbol, |row| row.symbol.as_str())
})
.and_then(|snapshot| {
factor_numeric_value(snapshot, "adjustment_factor_backward1")
})?;
if !factor.is_finite() || factor <= 0.0 {
return None;
}
.and_then(|snapshot| factor_numeric_value(snapshot, "adjustment_factor_backward1"))
.filter(|factor| factor.is_finite() && *factor > 0.0);
let back_adjusted_close = factor
.filter(|_| close.is_finite() && *close > 0.0)
.map(|factor| close * factor);
backward_factors.push(factor);
back_adjusted_closes.push(close * factor);
back_adjusted_closes.push(back_adjusted_close);
}
Some(Self {
dates: market.dates.clone(),
@@ -539,15 +535,12 @@ impl AdjustedCloseSeries {
if end < lookback {
return None;
}
let base_factor = *self.backward_factors.get(end - 1)?;
if !base_factor.is_finite() || base_factor <= 0.0 {
return None;
}
let base_factor = self.backward_factors.get(end - 1).copied().flatten()?;
let start = end - lookback;
let sum = self.back_adjusted_closes[start..end]
.iter()
.copied()
.sum::<f64>();
.try_fold(0.0, |sum, value| value.map(|value| sum + value))?;
if !sum.is_finite() {
return None;
}
@@ -566,6 +559,7 @@ impl AdjustedCloseSeries {
self.back_adjusted_closes
.get(index)
.copied()
.flatten()
.filter(|value| value.is_finite() && *value > 0.0)
}
}
@@ -3710,8 +3704,8 @@ mod tests {
dates: (0..30)
.map(|index| start + chrono::Duration::days(index as i64))
.collect(),
backward_factors: vec![1.0; 30],
back_adjusted_closes: values,
backward_factors: vec![Some(1.0); 30],
back_adjusted_closes: values.into_iter().map(Some).collect(),
};
let date = *series.dates.last().expect("last date");
@@ -3721,6 +3715,77 @@ mod tests {
);
}
#[test]
fn future_missing_adjustment_factor_does_not_invalidate_historical_window() {
let dates = [
NaiveDate::parse_from_str("2025-01-02", "%Y-%m-%d").unwrap(),
NaiveDate::parse_from_str("2025-01-03", "%Y-%m-%d").unwrap(),
NaiveDate::parse_from_str("2025-01-06", "%Y-%m-%d").unwrap(),
NaiveDate::parse_from_str("2025-01-07", "%Y-%m-%d").unwrap(),
];
let data = DataSet::from_components(
vec![Instrument {
symbol: "000001.SZ".to_string(),
name: "000001.SZ".to_string(),
board: "SZ".to_string(),
round_lot: 100,
listed_at: Some(dates[0]),
delisted_at: None,
status: "active".to_string(),
}],
dates
.iter()
.enumerate()
.map(|(index, date)| {
market_row(
&date.format("%Y-%m-%d").to_string(),
10.0 + index as f64,
100,
)
})
.collect(),
dates
.iter()
.map(|date| DailyFactorSnapshot {
date: *date,
symbol: "000001.SZ".to_string(),
market_cap_bn: 10.0,
free_float_cap_bn: 8.0,
pe_ttm: 10.0,
turnover_ratio: None,
effective_turnover_ratio: None,
extra_factors: if *date == dates[3] {
BTreeMap::new()
} else {
BTreeMap::from([("adjustment_factor_backward1".to_string(), 1.0)])
},
})
.collect(),
Vec::new(),
dates
.iter()
.map(|date| BenchmarkSnapshot {
date: *date,
benchmark: "000852.SH".to_string(),
open: 100.0,
close: 100.0,
prev_close: 100.0,
volume: 1_000_000,
})
.collect(),
)
.expect("dataset");
assert_eq!(
data.market_current_numeric_moving_average(dates[2], "000001.SZ", "close", 3),
Some(11.0)
);
assert_eq!(
data.market_current_numeric_moving_average(dates[3], "000001.SZ", "close", 3),
None
);
}
#[test]
fn decision_volume_average_includes_paused_zero_volume_days() {
let mut paused = market_row("2025-01-03", 11.0, 0);
@@ -28800,10 +28800,7 @@ mod tests {
effective_turnover_ratio: None,
extra_factors: BTreeMap::from([
("adjustment_factor_backward1".to_string(), 1.0),
(
"ma5_current_back_adjusted_close".to_string(),
current_close,
),
("ma5_current_back_adjusted_close".to_string(), current_close),
("avg_volume5_current".to_string(), current_volume),
]),
},