统一复权滚动因子计算口径

This commit is contained in:
boris
2026-08-24 02:51:58 +08:00
parent 9a7e5c7903
commit cea079a770
2 changed files with 197 additions and 243 deletions
+95 -38
View File
@@ -11,7 +11,6 @@ use crate::futures::FuturesTradingParameter;
use crate::instrument::Instrument;
use crate::risk_control::{ChinaAShareRiskControl, FidcRiskControlConfig};
mod date_format {
use chrono::NaiveDate;
use serde::{self, Deserialize, Deserializer, Serializer};
@@ -575,6 +574,64 @@ impl AdjustedCloseSeries {
))
}
fn decision_moving_average(&self, date: NaiveDate, lookback: usize) -> Option<f64> {
if lookback == 0 {
return None;
}
let end = match self.dates.binary_search(&date) {
Ok(index) => index,
Err(0) => return None,
Err(index) => index,
};
if end < lookback {
return None;
}
let base_factor = self.backward_factors.get(end - 1).copied().flatten()?;
let start = end - lookback;
if self.missing_back_adjusted_close_prefix[end]
!= self.missing_back_adjusted_close_prefix[start]
{
return None;
}
let sum = self.back_adjusted_close_prefix[end] - self.back_adjusted_close_prefix[start];
if !sum.is_finite() {
return None;
}
Some(normalize_rolling_factor(
sum / lookback as f64 / base_factor,
12,
))
}
fn values(&self, date: NaiveDate, lookback: usize, include_now: bool) -> Vec<f64> {
if lookback == 0 {
return Vec::new();
}
let end = match self.dates.binary_search(&date) {
Ok(index) => index + usize::from(include_now),
Err(0) => return Vec::new(),
Err(index) => index,
};
if end == 0 {
return Vec::new();
}
let start = end.saturating_sub(lookback);
let Some(base_factor) = self.backward_factors.get(end - 1).copied().flatten() else {
return Vec::new();
};
self.back_adjusted_closes[start..end]
.iter()
.copied()
.collect::<Option<Vec<_>>>()
.map(|values| {
values
.into_iter()
.map(|value| normalize_rolling_factor(value / base_factor, 12))
.collect()
})
.unwrap_or_default()
}
fn latest_back_adjusted_close(&self, date: NaiveDate) -> Option<f64> {
let index = match self.dates.binary_search(&date) {
Ok(index) => index,
@@ -641,7 +698,10 @@ impl SymbolPriceSeries {
+ if valid { *volume as f64 } else { 0.0 },
);
valid_volume_count_prefix.push(
valid_volume_count_prefix.last().copied().unwrap_or_default()
valid_volume_count_prefix
.last()
.copied()
.unwrap_or_default()
+ usize::from(valid),
);
}
@@ -781,40 +841,26 @@ impl SymbolPriceSeries {
Some(sum / lookback as f64)
}
fn decision_prev_close_values(&self, date: NaiveDate, lookback: usize) -> Option<Vec<f64>> {
if lookback == 0 {
return None;
}
let end = self.decision_end_index(date)?;
if end < lookback {
return None;
}
let start = end - lookback;
Some(self.prev_closes[start..end].to_vec())
}
fn decision_volume_moving_average(&self, date: NaiveDate, lookback: usize) -> Option<f64> {
let end = self.previous_completed_end_index(date)?;
self.valid_volume_window(end, lookback)
.map(|(start, end)| {
normalize_rolling_factor(
(self.valid_volume_sum_prefix[end] - self.valid_volume_sum_prefix[start])
/ lookback as f64,
12,
)
})
self.valid_volume_window(end, lookback).map(|(start, end)| {
normalize_rolling_factor(
(self.valid_volume_sum_prefix[end] - self.valid_volume_sum_prefix[start])
/ lookback as f64,
12,
)
})
}
fn current_volume_moving_average(&self, date: NaiveDate, lookback: usize) -> Option<f64> {
let end = self.end_index(date)?;
self.valid_volume_window(end, lookback)
.map(|(start, end)| {
normalize_rolling_factor(
(self.valid_volume_sum_prefix[end] - self.valid_volume_sum_prefix[start])
/ lookback as f64,
12,
)
})
self.valid_volume_window(end, lookback).map(|(start, end)| {
normalize_rolling_factor(
(self.valid_volume_sum_prefix[end] - self.valid_volume_sum_prefix[start])
/ lookback as f64,
12,
)
})
}
fn decision_volume_values(&self, date: NaiveDate, lookback: usize) -> Option<Vec<f64>> {
@@ -2409,8 +2455,8 @@ impl DataSet {
let field = normalize_field(field);
match field.as_str() {
"close" | "prev_close" | "stock_close" | "price" => self
.market_series(symbol)
.and_then(|series| series.decision_close_moving_average(date, lookback)),
.adjusted_close_series(symbol)
.and_then(|series| series.decision_moving_average(date, lookback)),
"volume" | "stock_volume" => {
if !self.source_daily_volume_window_available(date, symbol, lookback, false) {
None
@@ -2482,8 +2528,8 @@ impl DataSet {
let field = normalize_field(field);
match field.as_str() {
"close" | "prev_close" | "stock_close" | "price" => self
.market_series(symbol)
.and_then(|series| series.decision_prev_close_values(date, lookback))
.adjusted_close_series(symbol)
.map(|series| series.values(date, lookback, false))
.unwrap_or_default(),
"volume" | "stock_volume" => {
if !self.source_daily_volume_window_available(date, symbol, lookback, false) {
@@ -2523,6 +2569,15 @@ impl DataSet {
{
return Vec::new();
}
if matches!(
field.as_str(),
"close" | "prev_close" | "stock_close" | "price"
) {
return self
.adjusted_close_series(symbol)
.map(|series| series.values(date, lookback, true))
.unwrap_or_default();
}
if matches!(field.as_str(), "volume" | "stock_volume") {
return self
.market_series(symbol)
@@ -3500,10 +3555,8 @@ mod tests {
.map(|(index, date)| {
let mut extra_factors = BTreeMap::new();
if let Some(values) = availability {
extra_factors.insert(
"source_daily_volume_available".to_string(),
values[index],
);
extra_factors
.insert("source_daily_volume_available".to_string(), values[index]);
if values[index] >= 0.5 {
extra_factors.insert("daily_volume".to_string(), volumes[index] as f64);
}
@@ -3674,6 +3727,10 @@ mod tests {
data.market_current_numeric_moving_average(dates[2], "000001.SZ", "close", 3),
Some(5.5)
);
assert_eq!(
data.market_decision_numeric_moving_average(dates[2], "000001.SZ", "close", 2),
Some(10.5)
);
assert_ne!(
data.market_current_numeric_moving_average(dates[2], "000001.SZ", "close", 3),
data.market_moving_average(dates[2], "000001.SZ", 3, PriceField::Close)