use super::funejson::Value; use super::math_round_float::round_float; // Summed afresh, left to right, so the result never depends on the history // a running sum would carry. fn window_mean(values: &[f64], end: usize, window: usize) -> f64 { let mut sum = 0.0; for v in &values[end + 1 - window..=end] { sum += *v; } sum / window as f64 } /// Simple or exponential moving average, one value per full window. /// /// The exponential average is seeded with the simple average of the first /// window and carried unrounded; only the returned values are rounded, by /// math.round-float on the exact value of each double (2.675 gives 2.67). /// /// # Panics /// Panics on a non-finite value, a window below 1, an unknown kind, or /// decimals outside 0..=12. pub fn moving_average(values: &[f64], window: i64, kind: &str, decimals: i64) -> Vec { for v in values { if !v.is_finite() { panic!("values must be finite numbers, received {}", v); } } if window < 1 { panic!("window must be a whole number of 1 or greater, received {}", window); } if kind != "simple" && kind != "exponential" { panic!("unknown moving average kind \"{}\"", kind); } // Checked here as well as in round_float (same wording), so a series too // short to produce any output still refuses bad decimals. if !(0..=12).contains(&decimals) { panic!("decimals must be a whole number from 0 to 12, received {}", decimals); } let window = window as usize; let mut out = Vec::new(); if values.len() < window { return out; } if kind == "simple" { for end in window - 1..values.len() { out.push(round_float(window_mean(values, end, window), decimals)); } return out; } let alpha = 2.0 / (window as f64 + 1.0); let mut ema = window_mean(values, window - 1, window); out.push(round_float(ema, decimals)); for v in &values[window..] { ema = ema + alpha * (*v - ema); out.push(round_float(ema, decimals)); } out } pub fn fune_vector(args: &[Value]) -> Value { // Refuse what the typed signature cannot hold, with the wording TypeScript // and Python use, rather than let the conversion below quietly change it. for v in args[0].as_arr() { if !matches!(v, Value::Int(_) | Value::Float(_)) { panic!("values must be finite numbers, received {:?}", v); } } let values: Vec = args[0].as_arr().iter().map(|v| v.as_f64()).collect(); let out = moving_average(&values, args[1].as_i64(), args[2].as_str(), args[3].as_i64()); Value::Arr(out.into_iter().map(Value::Float).collect()) }