use std::collections::VecDeque;
use std::fmt;
use std::time::Duration;
use crate::errors::Result;
use crate::indicators::AdaptiveTimeDetector;
use crate::{Next, NextBatch, Reset};
use chrono::{DateTime, Utc};
#[cfg(feature = "serde")]
use serde::{Deserialize, Serialize};
const MAX_WINDOW_SIZE: usize = 500;
const KEEP_OLDEST: usize = 10;
const KEEP_RECENT: usize = 100;
#[doc(alias = "SD")]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[derive(Debug, Clone)]
pub struct StandardDeviation {
duration: Duration,
window: VecDeque<(DateTime<Utc>, f64)>,
sum: f64,
sum_sq: f64,
detector: AdaptiveTimeDetector,
#[cfg_attr(feature = "serde", serde(skip))]
cached_window: Option<i64>,
}
impl StandardDeviation {
pub fn get_window(&self) -> VecDeque<(DateTime<Utc>, f64)> {
self.window.clone()
}
pub fn new(duration: Duration) -> Result<Self> {
if duration.as_secs() == 0 && duration.subsec_nanos() == 0 {
return Err(crate::errors::TaError::InvalidParameter);
}
Ok(Self {
duration,
window: VecDeque::new(),
sum: 0.0,
sum_sq: 0.0,
detector: AdaptiveTimeDetector::new(duration),
cached_window: None,
})
}
fn remove_old_data(&mut self, current_time: DateTime<Utc>) {
let dur_nanos = *self
.cached_window
.get_or_insert_with(|| self.duration.as_nanos() as i64);
let cutoff_nanos = current_time.timestamp_nanos_opt().unwrap_or(i64::MIN) - dur_nanos;
while self.window.front().map_or(false, |(time, _)| {
time.timestamp_nanos_opt().unwrap_or(i64::MIN) <= cutoff_nanos
}) {
if let Some((_, old_value)) = self.window.pop_front() {
self.sum -= old_value;
self.sum_sq -= old_value * old_value;
}
}
}
fn thin_window(&mut self) {
if self.window.len() <= MAX_WINDOW_SIZE {
return;
}
let len = self.window.len();
let middle_start = KEEP_OLDEST;
let middle_end = len.saturating_sub(KEEP_RECENT);
if middle_end <= middle_start {
return;
}
let mut new_window = VecDeque::with_capacity(MAX_WINDOW_SIZE);
let mut new_sum = 0.0;
let mut new_sum_sq = 0.0;
for i in 0..middle_start.min(len) {
let (ts, val) = self.window[i];
new_sum += val;
new_sum_sq += val * val;
new_window.push_back((ts, val));
}
let mut keep = true;
for i in middle_start..middle_end {
if keep {
let (ts, val) = self.window[i];
new_sum += val;
new_sum_sq += val * val;
new_window.push_back((ts, val));
}
keep = !keep;
}
for i in middle_end..len {
let (ts, val) = self.window[i];
new_sum += val;
new_sum_sq += val * val;
new_window.push_back((ts, val));
}
self.window = new_window;
self.sum = new_sum;
self.sum_sq = new_sum_sq;
}
}
impl Next<f64> for StandardDeviation {
type Output = f64;
fn next(&mut self, input: (DateTime<Utc>, f64)) -> Self::Output {
let (timestamp, value) = input;
let should_replace = self.detector.should_replace(timestamp);
self.remove_old_data(timestamp);
if should_replace && !self.window.is_empty() {
if let Some((_, old_value)) = self.window.pop_back() {
self.sum -= old_value;
self.sum_sq -= old_value * old_value;
}
}
self.window.push_back((timestamp, value));
self.sum += value;
self.sum_sq += value * value;
self.thin_window();
let n = self.window.len() as f64;
if n == 0.0 {
0.0
} else {
let mean = self.sum / n;
let variance = (self.sum_sq - (self.sum * mean)) / n;
variance.sqrt()
}
}
}
impl NextBatch<f64> for StandardDeviation {}
impl Reset for StandardDeviation {
fn reset(&mut self) {
self.window.clear();
self.sum = 0.0;
self.sum_sq = 0.0;
self.detector.reset();
}
}
impl Default for StandardDeviation {
fn default() -> Self {
Self::new(Duration::from_secs(14 * 24 * 60 * 60)).unwrap() }
}
impl fmt::Display for StandardDeviation {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "SD({}s)", self.duration.as_secs())
}
}
#[cfg(test)]
mod tests {
use crate::test_helper::round;
use super::*;
use chrono::{TimeZone, Utc};
#[test]
fn test_new() {
assert!(StandardDeviation::new(Duration::from_secs(0)).is_err());
assert!(StandardDeviation::new(Duration::from_secs(1)).is_ok());
}
#[test]
fn test_next() {
let duration = Duration::from_secs(4);
let mut sd = StandardDeviation::new(duration).unwrap();
let now = Utc::now();
assert_eq!(sd.next((now + chrono::Duration::seconds(1), 10.0)), 0.0);
assert_eq!(sd.next((now + chrono::Duration::seconds(2), 20.0)), 5.0);
assert_eq!(
round(sd.next((now + chrono::Duration::seconds(3), 30.0))),
8.165
);
assert_eq!(
round(sd.next((now + chrono::Duration::seconds(4), 20.0))),
7.071
);
assert_eq!(
round(sd.next((now + chrono::Duration::seconds(5), 10.0))),
7.071
);
assert_eq!(
round(sd.next((now + chrono::Duration::seconds(6), 100.0))),
35.355
);
}
#[test]
fn test_reset() {
let duration = Duration::from_secs(4);
let mut sd = StandardDeviation::new(duration).unwrap();
let now = Utc::now();
assert_eq!(sd.next((now, 10.0)), 0.0);
assert_eq!(sd.next((now + chrono::Duration::seconds(1), 20.0)), 5.0);
assert_eq!(
round(sd.next((now + chrono::Duration::seconds(2), 30.0))),
8.165
);
sd.reset();
assert_eq!(sd.next((now + chrono::Duration::seconds(3), 20.0)), 0.0);
}
#[test]
fn test_default() {
let _sd = StandardDeviation::default();
}
#[test]
fn test_display() {
let indicator = StandardDeviation::new(Duration::from_secs(7)).unwrap();
assert_eq!(format!("{}", indicator), "SD(7s)");
}
}