// O(n) rolling-window sum.
///|
/// Rolling sum over a window of `period` values, computed in O(n).
///
/// Implemented as a running sum with an exact re-accumulation every
/// `period` bars (and whenever a NaN leaves the window), which bounds
/// floating-point drift to roughly one slide. A window containing NaN
/// yields NaN; the output has the same length as the input with a
/// `period - 1` leading NaN prefix.
pub fn rolling_sum(
data : Array[Double],
period : Int32,
) -> Array[Double] raise TaError {
guard_period(data, period)
let n = data.length()
let out : Array[Double] = []
let p = period
let mut sum = 0.0
let mut dirty = true
let mut last_nan = -1
for i in 0..= p {
sum += data[i] - data[i - p]
if is_nan(sum) {
dirty = true
}
} else {
sum += data[i]
}
if i < p - 1 || last_nan >= i - p + 1 {
out.push(nan())
dirty = true
} else {
if dirty || (i - (p - 1)) % p == 0 {
// exact re-accumulation bounds drift to at most one slide
sum = 0.0
for j in (i - p + 1)..<(i + 1) {
sum += data[j]
}
dirty = false
}
out.push(sum)
}
}
out
}