扩展策略指标因子与滚动函数
This commit is contained in:
@@ -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() {
|
||||
|
||||
Reference in New Issue
Block a user