统一复权滚动因子计算口径
This commit is contained in:
@@ -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)
|
||||
|
||||
Reference in New Issue
Block a user