perf: reuse current rolling series boundary
This commit is contained in:
@@ -2012,6 +2012,14 @@ impl DataSet {
|
||||
Some(*end as usize)
|
||||
}
|
||||
|
||||
pub(crate) fn market_current_series_end_index_by_symbol_id(
|
||||
&self,
|
||||
date: NaiveDate,
|
||||
symbol_id: u32,
|
||||
) -> Option<usize> {
|
||||
self.market_series_end_index_by_symbol_id(date, symbol_id, true)
|
||||
}
|
||||
|
||||
pub fn factor(&self, date: NaiveDate, symbol: &str) -> Option<&DailyFactorSnapshot> {
|
||||
let symbol_id = self.symbol_id(symbol)?;
|
||||
self.factor_by_symbol_id(date, symbol_id)
|
||||
@@ -3347,20 +3355,56 @@ impl DataSet {
|
||||
symbol: &str,
|
||||
field: &str,
|
||||
lookback: usize,
|
||||
) -> Option<f64> {
|
||||
let normalized = normalized_field(field);
|
||||
let series_end = matches!(
|
||||
normalized.as_ref(),
|
||||
"close"
|
||||
| "prev_close"
|
||||
| "stock_close"
|
||||
| "price"
|
||||
| "volume"
|
||||
| "stock_volume"
|
||||
| "day_open"
|
||||
| "dayopen"
|
||||
| "open"
|
||||
| "last"
|
||||
| "last_price"
|
||||
)
|
||||
.then(|| self.market_current_series_end_index_by_symbol_id(date, symbol_id))
|
||||
.flatten();
|
||||
self.market_current_numeric_moving_average_with_end_by_symbol_id(
|
||||
date,
|
||||
symbol_id,
|
||||
symbol,
|
||||
normalized.as_ref(),
|
||||
lookback,
|
||||
series_end,
|
||||
)
|
||||
}
|
||||
|
||||
pub(crate) fn market_current_numeric_moving_average_with_end_by_symbol_id(
|
||||
&self,
|
||||
date: NaiveDate,
|
||||
symbol_id: u32,
|
||||
symbol: &str,
|
||||
field: &str,
|
||||
lookback: usize,
|
||||
series_end: Option<usize>,
|
||||
) -> Option<f64> {
|
||||
let field = normalized_field(field);
|
||||
match field.as_ref() {
|
||||
"close" | "prev_close" | "stock_close" | "price" => self
|
||||
.adjusted_close_series_by_symbol_id(symbol_id)
|
||||
.and_then(|series| {
|
||||
self.market_series_end_index_by_symbol_id(date, symbol_id, true)
|
||||
series_end
|
||||
.map(|end| series.moving_average_at_end(end, lookback))
|
||||
.unwrap_or_else(|| series.current_moving_average(date, lookback))
|
||||
}),
|
||||
"volume" | "stock_volume" => {
|
||||
self.market_series_by_symbol_id(symbol_id)
|
||||
.and_then(|series| {
|
||||
self.market_series_end_index_by_symbol_id(date, symbol_id, true)
|
||||
series_end
|
||||
.map(|end| {
|
||||
series
|
||||
.valid_volume_window(end, lookback)
|
||||
@@ -3379,7 +3423,7 @@ impl DataSet {
|
||||
"day_open" | "dayopen" => {
|
||||
self.market_series_by_symbol_id(symbol_id)
|
||||
.and_then(|series| {
|
||||
self.market_series_end_index_by_symbol_id(date, symbol_id, true)
|
||||
series_end
|
||||
.map(|end| {
|
||||
series.moving_average_at_end(end, lookback, PriceField::DayOpen)
|
||||
})
|
||||
@@ -3391,14 +3435,14 @@ impl DataSet {
|
||||
"open" => self
|
||||
.market_series_by_symbol_id(symbol_id)
|
||||
.and_then(|series| {
|
||||
self.market_series_end_index_by_symbol_id(date, symbol_id, true)
|
||||
series_end
|
||||
.map(|end| series.moving_average_at_end(end, lookback, PriceField::Open))
|
||||
.unwrap_or_else(|| series.moving_average(date, lookback, PriceField::Open))
|
||||
}),
|
||||
"last" | "last_price" => {
|
||||
self.market_series_by_symbol_id(symbol_id)
|
||||
.and_then(|series| {
|
||||
self.market_series_end_index_by_symbol_id(date, symbol_id, true)
|
||||
series_end
|
||||
.map(|end| {
|
||||
series.moving_average_at_end(end, lookback, PriceField::Last)
|
||||
})
|
||||
@@ -5052,6 +5096,137 @@ mod tests {
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[ignore = "manual release-mode current rolling boundary benchmark"]
|
||||
fn benchmark_current_rolling_reuses_symbol_boundary() {
|
||||
let start = NaiveDate::from_ymd_opt(2025, 1, 1).unwrap();
|
||||
let dates = (0..160)
|
||||
.map(|offset| start + chrono::Duration::days(offset))
|
||||
.collect::<Vec<_>>();
|
||||
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: None,
|
||||
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.0,
|
||||
100_000 + index as u64,
|
||||
)
|
||||
})
|
||||
.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: NumericFactorMap::from([(
|
||||
Cow::Borrowed(BACKWARD_ADJUSTMENT_FACTOR_FIELD),
|
||||
1.0,
|
||||
)]),
|
||||
})
|
||||
.collect(),
|
||||
Vec::new(),
|
||||
dates
|
||||
.iter()
|
||||
.enumerate()
|
||||
.map(|(index, date)| {
|
||||
benchmark_row(&date.format("%Y-%m-%d").to_string(), 1_000.0 + index as f64)
|
||||
})
|
||||
.collect(),
|
||||
)
|
||||
.unwrap();
|
||||
let date = *dates.last().unwrap();
|
||||
let symbol = "000001.SZ";
|
||||
let symbol_id = data.symbol_id(symbol).unwrap();
|
||||
let requirements = [
|
||||
("close", 5usize),
|
||||
("close", 10usize),
|
||||
("close", 30usize),
|
||||
("volume", 5usize),
|
||||
("volume", 100usize),
|
||||
];
|
||||
let iterations = 100_000usize;
|
||||
let mut repeated_nanos = 0u128;
|
||||
let mut reused_nanos = 0u128;
|
||||
let mut repeated_checksum = 0.0;
|
||||
let mut reused_checksum = 0.0;
|
||||
|
||||
for iteration in 0..iterations {
|
||||
if iteration % 2 == 0 {
|
||||
let started = std::time::Instant::now();
|
||||
for (field, lookback) in requirements {
|
||||
repeated_checksum += data
|
||||
.market_current_numeric_moving_average_by_symbol_id(
|
||||
date, symbol_id, symbol, field, lookback,
|
||||
)
|
||||
.unwrap();
|
||||
}
|
||||
repeated_nanos += started.elapsed().as_nanos();
|
||||
let started = std::time::Instant::now();
|
||||
let series_end = data.market_current_series_end_index_by_symbol_id(date, symbol_id);
|
||||
for (field, lookback) in requirements {
|
||||
reused_checksum += data
|
||||
.market_current_numeric_moving_average_with_end_by_symbol_id(
|
||||
date, symbol_id, symbol, field, lookback, series_end,
|
||||
)
|
||||
.unwrap();
|
||||
}
|
||||
reused_nanos += started.elapsed().as_nanos();
|
||||
} else {
|
||||
let started = std::time::Instant::now();
|
||||
let series_end = data.market_current_series_end_index_by_symbol_id(date, symbol_id);
|
||||
for (field, lookback) in requirements {
|
||||
reused_checksum += data
|
||||
.market_current_numeric_moving_average_with_end_by_symbol_id(
|
||||
date, symbol_id, symbol, field, lookback, series_end,
|
||||
)
|
||||
.unwrap();
|
||||
}
|
||||
reused_nanos += started.elapsed().as_nanos();
|
||||
let started = std::time::Instant::now();
|
||||
for (field, lookback) in requirements {
|
||||
repeated_checksum += data
|
||||
.market_current_numeric_moving_average_by_symbol_id(
|
||||
date, symbol_id, symbol, field, lookback,
|
||||
)
|
||||
.unwrap();
|
||||
}
|
||||
repeated_nanos += started.elapsed().as_nanos();
|
||||
}
|
||||
}
|
||||
|
||||
assert!((repeated_checksum - reused_checksum).abs() < 1e-6);
|
||||
let repeated_seconds = repeated_nanos as f64 / 1_000_000_000.0;
|
||||
let reused_seconds = reused_nanos as f64 / 1_000_000_000.0;
|
||||
eprintln!(
|
||||
"{}",
|
||||
serde_json::json!({
|
||||
"schemaVersion": "fidc-current-rolling-boundary-benchmark/v1",
|
||||
"iterations": iterations,
|
||||
"helperCallsPerIteration": requirements.len(),
|
||||
"repeatedLookupSeconds": repeated_seconds,
|
||||
"reusedBoundarySeconds": reused_seconds,
|
||||
"speedup": repeated_seconds / reused_seconds,
|
||||
"equal": true,
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[ignore = "manual component benchmark"]
|
||||
fn benchmark_daily_snapshot_view_lookup() {
|
||||
|
||||
Reference in New Issue
Block a user