扩展策略指标因子与滚动函数

This commit is contained in:
boris
2026-04-28 21:01:43 +08:00
parent 236ec62e44
commit c73649012f
3 changed files with 453 additions and 10 deletions

View File

@@ -574,6 +574,18 @@ 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> {
if lookback == 0 {
return None;
@@ -587,6 +599,23 @@ impl SymbolPriceSeries {
Some(sum / lookback as f64)
}
fn decision_volume_values(&self, date: NaiveDate, lookback: usize) -> Option<Vec<f64>> {
if lookback == 0 {
return None;
}
let end = self.previous_completed_end_index(date)?;
if end < lookback {
return None;
}
let start = end - lookback;
Some(
self.snapshots[start..end]
.iter()
.map(|snapshot| snapshot.volume as f64)
.collect(),
)
}
fn end_index(&self, date: NaiveDate) -> Option<usize> {
match self.dates.binary_search(&date) {
Ok(idx) => Some(idx + 1),
@@ -625,6 +654,7 @@ impl SymbolPriceSeries {
#[derive(Debug, Clone)]
struct BenchmarkPriceSeries {
dates: Vec<NaiveDate>,
opens: Vec<f64>,
closes: Vec<f64>,
open_prefix: Vec<f64>,
close_prefix: Vec<f64>,
@@ -641,6 +671,7 @@ impl BenchmarkPriceSeries {
let close_prefix = prefix_sums(&closes);
Self {
dates,
opens,
closes,
open_prefix,
close_prefix,
@@ -678,13 +709,20 @@ impl BenchmarkPriceSeries {
}
fn trailing_values(&self, date: NaiveDate, lookback: usize) -> Vec<f64> {
self.trailing_values_for(date, lookback, PriceField::Close)
}
fn trailing_values_for(&self, date: NaiveDate, lookback: usize, field: PriceField) -> Vec<f64> {
let end = match self.dates.binary_search(&date) {
Ok(idx) => idx + 1,
Err(0) => return Vec::new(),
Err(idx) => idx,
};
let start = end.saturating_sub(lookback);
self.closes[start..end].to_vec()
match field {
PriceField::DayOpen | PriceField::Open => self.opens[start..end].to_vec(),
PriceField::Close | PriceField::Last => self.closes[start..end].to_vec(),
}
}
}
@@ -944,6 +982,7 @@ impl DataSet {
) -> Result<Self, DataSetError> {
let benchmark_code = collect_benchmark_code(&benchmarks)?;
let calendar = TradingCalendar::new(benchmarks.iter().map(|item| item.date).collect());
let factors = normalize_factor_snapshots(factors);
let instruments = instruments
.into_iter()
@@ -2009,6 +2048,65 @@ impl DataSet {
}
}
pub fn market_decision_numeric_values(
&self,
date: NaiveDate,
symbol: &str,
field: &str,
lookback: usize,
) -> Vec<f64> {
if lookback == 0 {
return Vec::new();
}
let field = normalize_field(field);
match field.as_str() {
"close" | "prev_close" | "stock_close" | "price" => self
.market_series_by_symbol
.get(symbol)
.and_then(|series| series.decision_prev_close_values(date, lookback))
.unwrap_or_default(),
"volume" | "stock_volume" => self
.market_series_by_symbol
.get(symbol)
.and_then(|series| series.decision_volume_values(date, lookback))
.unwrap_or_default(),
"day_open" | "dayopen" => self
.market_series_by_symbol
.get(symbol)
.map(|series| series.trailing_values(date, lookback, PriceField::DayOpen))
.unwrap_or_default(),
"open" => self
.market_series_by_symbol
.get(symbol)
.map(|series| series.trailing_values(date, lookback, PriceField::Open))
.unwrap_or_default(),
"last" | "last_price" => self
.market_series_by_symbol
.get(symbol)
.map(|series| series.trailing_values(date, lookback, PriceField::Last))
.unwrap_or_default(),
other => self.factor_numeric_values(date, symbol, other, lookback),
}
}
pub fn factor_numeric_values(
&self,
date: NaiveDate,
symbol: &str,
field: &str,
lookback: usize,
) -> Vec<f64> {
if lookback == 0 {
return Vec::new();
}
self.calendar
.trailing_days(date, lookback)
.into_iter()
.filter_map(|trading_day| self.factor(trading_day, symbol))
.filter_map(|snapshot| factor_numeric_value(snapshot, field))
.collect()
}
pub fn market_moving_average(
&self,
date: NaiveDate,
@@ -2030,6 +2128,21 @@ impl DataSet {
.moving_average_for(date, lookback, PriceField::Open)
}
pub fn benchmark_numeric_values(
&self,
date: NaiveDate,
field: &str,
lookback: usize,
) -> Vec<f64> {
let field = normalize_field(field);
match field.as_str() {
"open" | "day_open" | "dayopen" | "benchmark_open" => self
.benchmark_series_cache
.trailing_values_for(date, lookback, PriceField::Open),
_ => self.benchmark_series_cache.trailing_values(date, lookback),
}
}
pub fn market_open_moving_average(
&self,
date: NaiveDate,
@@ -2400,6 +2513,26 @@ fn factor_numeric_value(snapshot: &DailyFactorSnapshot, field: &str) -> Option<f
"pe_ttm" => Some(snapshot.pe_ttm),
"turnover_ratio" => snapshot.turnover_ratio,
"effective_turnover_ratio" => snapshot.effective_turnover_ratio,
"ths_market_value_stock" | "ths_market_value_stock_bn" => snapshot
.extra_factors
.get(field.as_str())
.copied()
.or(Some(snapshot.market_cap_bn)),
"ths_current_mv_stock" | "ths_current_mv_stock_bn" => snapshot
.extra_factors
.get(field.as_str())
.copied()
.or(Some(snapshot.free_float_cap_bn)),
"ths_turnover_ratio_stock" => snapshot
.extra_factors
.get(field.as_str())
.copied()
.or(snapshot.turnover_ratio),
"ths_vaild_turnover_stock" | "ths_valid_turnover_stock" => snapshot
.extra_factors
.get(field.as_str())
.copied()
.or(snapshot.effective_turnover_ratio),
other => snapshot.extra_factors.get(other).copied(),
}
}
@@ -2509,6 +2642,27 @@ fn normalize_field(field: &str) -> String {
.to_ascii_lowercase()
}
fn normalize_factor_snapshots(factors: Vec<DailyFactorSnapshot>) -> Vec<DailyFactorSnapshot> {
factors
.into_iter()
.map(|mut snapshot| {
snapshot.extra_factors = snapshot
.extra_factors
.into_iter()
.filter_map(|(field, value)| {
let normalized = normalize_field(&field);
if normalized.is_empty() || !value.is_finite() {
None
} else {
Some((normalized, value))
}
})
.collect();
snapshot
})
.collect()
}
fn normalize_history_frequency(frequency: &str) -> Option<String> {
let normalized = normalize_field(frequency);
match normalized.as_str() {