qta 3.0.0

Streaming technical analysis indicators for quantitative trading
Documentation
//! Segment statistics for tracking swing structure over time.

use std::collections::VecDeque;

/// Rolling statistics over recent completed segments.
pub struct SegmentStats {
    recent_sizes: VecDeque<i64>,
    window_size: usize,
    segments_sum: i64,
}

impl SegmentStats {
    #[must_use]
    pub fn new(window_size: usize) -> Self {
        Self {
            recent_sizes: VecDeque::with_capacity(window_size),
            window_size,
            segments_sum: 0,
        }
    }

    pub fn update(&mut self, segment_size: i64) {
        if self.recent_sizes.len() >= self.window_size {
            if let Some(evicted) = self.recent_sizes.pop_front() {
                self.segments_sum -= evicted;
            }
        }
        self.recent_sizes.push_back(segment_size);
        self.segments_sum += segment_size;
    }

    #[must_use]
    pub fn trend_ratio(&self, current_segment_size: i64) -> Option<f64> {
        if self.segments_sum == 0 {
            return None;
        }
        Some(current_segment_size as f64 / self.segments_sum as f64)
    }

    #[must_use]
    pub fn segments_sum(&self) -> i64 {
        self.segments_sum
    }

    #[must_use]
    pub fn count(&self) -> usize {
        self.recent_sizes.len()
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_empty_stats() {
        let stats = SegmentStats::new(5);
        assert_eq!(stats.segments_sum(), 0);
        assert_eq!(stats.count(), 0);
        assert!(stats.trend_ratio(100).is_none());
    }

    #[test]
    fn test_single_segment() {
        let mut stats = SegmentStats::new(5);
        stats.update(1000);
        assert_eq!(stats.segments_sum(), 1000);
        assert!((stats.trend_ratio(1000).unwrap() - 1.0).abs() < 1e-10);
    }

    #[test]
    fn test_window_eviction() {
        let mut stats = SegmentStats::new(3);
        stats.update(100);
        stats.update(200);
        stats.update(300);
        assert_eq!(stats.segments_sum(), 600);
        stats.update(400);
        assert_eq!(stats.segments_sum(), 900); // 200+300+400
        assert_eq!(stats.count(), 3);
    }

    #[test]
    fn test_trend_ratio() {
        let mut stats = SegmentStats::new(5);
        stats.update(100);
        stats.update(100);
        stats.update(100);
        let ratio = stats.trend_ratio(100).unwrap();
        assert!((ratio - 1.0 / 3.0).abs() < 1e-10);
    }
}