From 6b5d57675ef7add8e5e7d88e31767237e481d913 Mon Sep 17 00:00:00 2001 From: boris Date: Sat, 5 Sep 2026 12:40:40 +0800 Subject: [PATCH] perf: reuse rolling window boundaries --- crates/fidc-core/src/data.rs | 52 ++++++++++++++++++++++++++++++------ 1 file changed, 44 insertions(+), 8 deletions(-) diff --git a/crates/fidc-core/src/data.rs b/crates/fidc-core/src/data.rs index 65dbcd2..dde45cb 100644 --- a/crates/fidc-core/src/data.rs +++ b/crates/fidc-core/src/data.rs @@ -686,7 +686,15 @@ impl AdjustedCloseSeries { end: usize, lookbacks: &[usize; N], ) -> [Option; N] { - std::array::from_fn(|index| self.moving_average_at_end(end, lookbacks[index])) + let Some(base_factor) = end + .checked_sub(1) + .and_then(|index| self.backward_factors.get(index).copied().flatten()) + else { + return [None; N]; + }; + std::array::from_fn(|index| { + self.moving_average_at_end_with_base_factor(end, lookbacks[index], base_factor) + }) } fn moving_average_at_end(&self, end: usize, lookback: usize) -> Option { @@ -694,6 +702,18 @@ impl AdjustedCloseSeries { return None; } let base_factor = self.backward_factors.get(end - 1).copied().flatten()?; + self.moving_average_at_end_with_base_factor(end, lookback, base_factor) + } + + fn moving_average_at_end_with_base_factor( + &self, + end: usize, + lookback: usize, + base_factor: f64, + ) -> Option { + if lookback == 0 || end < lookback { + return None; + } let start = end - lookback; if self.missing_back_adjusted_close_prefix[end] != self.missing_back_adjusted_close_prefix[start] @@ -1022,15 +1042,19 @@ impl SymbolPriceSeries { end: usize, lookbacks: &[usize; N], ) -> [Option; N] { + let Some(valid_count) = self.valid_volume_count_prefix.get(end).copied() else { + return [None; N]; + }; std::array::from_fn(|index| { let lookback = lookbacks[index]; - 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_with_count(end, lookback, valid_count) + .map(|(start, end)| { + normalize_rolling_factor( + (self.valid_volume_sum_prefix[end] - self.valid_volume_sum_prefix[start]) + / lookback as f64, + 12, + ) + }) }) } @@ -1049,6 +1073,18 @@ impl SymbolPriceSeries { return None; } let valid_count = *self.valid_volume_count_prefix.get(end)?; + self.valid_volume_window_with_count(end, lookback, valid_count) + } + + fn valid_volume_window_with_count( + &self, + end: usize, + lookback: usize, + valid_count: usize, + ) -> Option<(usize, usize)> { + if lookback == 0 || end > self.volumes.len() { + return None; + } if valid_count < lookback { return None; }