ta_statistics/
single_statistics.rs

1use num_traits::Float;
2
3use core::iter::Sum;
4
5use crate::{
6    PairedStatistics, RingBuffer, RollingMoments,
7    helper::{median_from_sorted_slice, quantile_from_sorted_slice},
8};
9
10/// A structure that computes various statistics over a fixed-size window of values.
11/// A specialized statistics implementation for single time-series data analysis.
12///
13/// This structure provides comprehensive statistical calculations for financial
14/// time-series data, optimized for algorithmic trading applications. It maintains
15/// a fixed-size window of values and efficiently updates statistics as new data
16/// points arrive in a streaming fashion.
17///
18/// The structure is particularly useful for technical analysis, risk management,
19/// and alpha generation in quantitative trading strategies.
20#[derive(Debug, Clone)]
21pub struct SingleStatistics<T> {
22    /// Rolling moments
23    moments: RollingMoments<T>,
24    /// Fixed buffer for sorting on demand
25    sorted_buf: RingBuffer<T>,
26    /// Current minimum value
27    min: Option<T>,
28    /// Current maximum value
29    max: Option<T>,
30    /// Maximum drawdown
31    max_drawdown: Option<T>,
32}
33
34impl<T> SingleStatistics<T>
35where
36    T: Default + Clone + Float,
37{
38    /// Creates a new `SingleStatistics` instance with the specified period.
39    ///
40    /// # Arguments
41    ///
42    /// * `period` - The period of the statistics
43    ///
44    /// # Returns
45    ///
46    /// * `Self` - The statistics object
47    pub fn new(period: usize) -> Self {
48        Self {
49            moments: RollingMoments::new(period),
50            sorted_buf: RingBuffer::new(period),
51            min: None,
52            max: None,
53            max_drawdown: None,
54        }
55    }
56
57    /// Returns the period of the statistics
58    ///
59    /// # Returns
60    ///
61    /// * `usize` - The period of the statistics
62    pub fn period(&self) -> usize {
63        self.moments.period()
64    }
65
66    /// Resets the statistics
67    ///
68    /// # Returns
69    ///
70    /// * `&mut Self` - The statistics object
71    pub fn reset(&mut self) -> &mut Self {
72        self.moments.reset();
73        self.sorted_buf.reset();
74        self.min = None;
75        self.max = None;
76        self.max_drawdown = None;
77        self
78    }
79
80    /// Recomputes the rolling statistics, could be called to avoid
81    /// prolonged compounding of floating rounding errors
82    ///
83    /// # Returns
84    ///
85    /// * `&mut Self` - The rolling moments object
86    pub fn recompute(&mut self) -> &mut Self {
87        self.moments.recompute();
88        self
89    }
90
91    fn period_t(&self) -> Option<T>
92    where
93        T: Float,
94    {
95        T::from(self.period())
96    }
97
98    // Copies and sorts the buf
99    fn sorted_buf(&mut self) -> &[T]
100    where
101        T: Copy + Default + PartialOrd,
102    {
103        self.sorted_buf.copy_from_slice(self.moments.as_slice());
104        self.sorted_buf.sort()
105    }
106
107    /// Returns the Delta Degrees of Freedom
108    ///
109    /// # Returns
110    ///
111    /// * `bool` - The Delta Degrees of Freedom
112    pub const fn ddof(&self) -> bool {
113        self.moments.ddof()
114    }
115
116    /// Sets the Delta Degrees of Freedom
117    ///
118    /// # Arguments
119    ///
120    /// * `ddof` - The Delta Degrees of Freedom
121    ///
122    /// # Returns
123    ///
124    /// * `&mut Self` - The statistics object
125    pub const fn set_ddof(&mut self, ddof: bool) -> &mut Self {
126        self.moments.set_ddof(ddof);
127        self
128    }
129
130    /// Updates the statistical calculations with a new value in the time series
131    ///
132    /// Incorporates a new data point into the rolling window, maintaining the specified
133    /// window size by removing the oldest value when necessary. This is the core method
134    /// that should be called whenever new data is available for processing.
135    ///
136    /// The statistics are calculated using the Kahan-Babuška-Neumaier (Kbn) algorithm
137    /// for numerically stable summation. This compensated summation technique minimizes
138    /// floating-point errors that would otherwise accumulate in long-running calculations,
139    /// particularly important for financial time-series analysis where precision is critical.
140    ///
141    /// # Arguments
142    ///
143    /// * `value` - The new value to be added to the time series
144    ///
145    /// # Returns
146    ///
147    /// * `&mut Self` - The statistics object
148    pub fn next(&mut self, value: T) -> &mut Self {
149        self.moments.next(value);
150        self
151    }
152
153    /// Returns the sum of all values in the rolling window
154    ///
155    /// This fundamental calculation serves as the basis for numerous higher-order statistics
156    /// and provides key insights for:
157    ///
158    /// - Aggregating transaction volumes to identify participant interest levels
159    /// - Constructing accumulation/distribution profiles across price action
160    /// - Measuring net directional pressure in time series data
161    /// - Quantifying capital flows between market segments
162    ///
163    /// # Returns
164    ///
165    /// * `Option<T>` - The sum of all values in the window, or `None` if the window is not full
166    ///
167    /// # Examples
168    ///
169    /// ```
170    /// # use ta_statistics::SingleStatistics;
171    /// let mut stats = SingleStatistics::new(3);
172    /// let inputs = [1_000_000.1, 1_000_000.2, 1_000_000.3, 1_000_000.4, 1_000_000.5, 1_000_000.6, 1_000_000.7];
173    /// let mut results = vec![];
174    ///
175    /// inputs.iter().for_each(|i| {
176    ///     stats.next(*i).sum().map(|v| results.push(v));
177    /// });
178    ///
179    /// let expected = [3000000.6, 3000000.9, 3000001.2, 3000001.5, 3000001.8];
180    /// assert_eq!(&results, &expected);
181    /// ```
182    pub fn sum(&self) -> Option<T> {
183        self.moments.sum()
184    }
185
186    /// Returns the sum of squares of all values in the rolling window
187    ///
188    /// This calculation provides the sum of squared values in the series, offering insights into
189    /// the magnitude of values regardless of sign:
190    ///
191    /// - Serves as a component for calculating variance and other dispersion measures
192    /// - Emphasizes larger values due to the squaring operation
193    /// - Provides power estimation in signal processing applications
194    /// - Helps detect significant deviations from expected behavior
195    ///
196    /// # Returns
197    ///
198    /// * `Option<T>` - The sum of squares in the window, or `None` if the window is not full
199    ///
200    /// # Examples
201    ///
202    /// ```
203    /// # use ta_statistics::SingleStatistics;
204    /// let mut stats = SingleStatistics::new(3);
205    /// let inputs = [1_000_000.1, 1_000_000.2, 1_000_000.3, 1_000_000.4, 1_000_000.5, 1_000_000.6, 1_000_000.7];
206    /// let mut results = vec![];
207    ///
208    /// inputs.iter().for_each(|i| {
209    ///     stats.next(*i).sum_sq().map(|v| results.push(v));
210    /// });
211    /// let expected = [3000001200000.14, 3000001800000.29, 3000002400000.5, 3000003000000.77, 3000003600001.0996];
212    /// assert_eq!(&results, &expected);
213    /// ```
214    pub fn sum_sq(&self) -> Option<T> {
215        self.moments.sum_sq()
216    }
217
218    /// Returns the arithmetic mean of all values in the rolling window
219    ///
220    /// This central tendency measure forms the foundation for numerous statistical calculations
221    /// and serves as a reference point for analyzing data distributions:
222    ///
223    /// - Establishes equilibrium levels for reversion-based analytical models
224    /// - Provides baseline reference for filtering noisy sequential data
225    /// - Creates statistical foundation for pattern detection in time series
226    /// - Enables feature normalization in advanced predictive modeling
227    ///
228    /// # Returns
229    ///
230    /// * `Option<T>` - The arithmetic mean of values in the window, or `None` if the window is not full
231    ///
232    /// # Examples
233    ///
234    /// ```
235    /// # use ta_statistics::SingleStatistics;
236    /// # use assert_approx_eq::assert_approx_eq;
237    /// let mut stats = SingleStatistics::new(3);
238    /// let inputs = [1_000_000.1, 1_000_000.2, 1_000_000.3, 1_000_000.4, 1_000_000.5, 1_000_000.6, 1_000_000.7];
239    /// let mut results = vec![];
240    ///
241    /// inputs.iter().for_each(|i| {
242    ///     stats.next(*i).mean().map(|v| results.push(v));
243    /// });
244    ///
245    /// let expected: [f64; 5] = [1000000.2, 1000000.3, 1000000.4, 1000000.5, 1000000.6];
246    /// for (i, e) in expected.iter().enumerate() {
247    ///     assert_approx_eq!(e, results[i], 0.0001);
248    /// }
249    /// ```
250    pub fn mean(&self) -> Option<T> {
251        self.moments.mean()
252    }
253
254    /// Returns the mean of squares of all values in the rolling window
255    ///
256    /// This calculation provides the average of squared values in the series, offering
257    /// insights into the magnitude of values regardless of sign:
258    ///
259    /// - Serves as a component for calculating variance and other dispersion measures
260    /// - Emphasizes larger values due to the squaring operation
261    /// - Provides power estimation in signal processing applications
262    /// - Helps detect significant deviations from expected behavior
263    ///
264    /// # Returns
265    ///
266    /// * `Option<T>` - The mean of squared values in the window, or `None` if the window is not full
267    ///
268    /// # Examples
269    ///
270    /// ```
271    /// # use ta_statistics::SingleStatistics;
272    /// # use assert_approx_eq::assert_approx_eq;
273    /// let mut stats = SingleStatistics::new(3);
274    /// let inputs = [1_000_000.1, 1_000_000.2, 1_000_000.3, 1_000_000.4, 1_000_000.5, 1_000_000.6, 1_000_000.7];
275    /// let mut results = vec![];
276    ///
277    /// inputs.iter().for_each(|i| {
278    ///     stats.next(*i).mean_sq().map(|v| results.push(v));
279    /// });
280    ///
281    /// let expected: [f64; 5] = [1000000400000.05,1000000600000.1,1000000800000.17,1000001000000.26,1000001200000.37];
282    /// for (i, e) in expected.iter().enumerate() {
283    ///     assert_approx_eq!(e, results[i], 0.01);
284    /// }
285    /// ```
286    pub fn mean_sq(&self) -> Option<T> {
287        self.moments.mean_sq()
288    }
289
290    /// Returns the mode (most frequently occurring value) in the rolling window
291    ///
292    /// The mode identifies the most common value within a distribution, providing
293    /// insight into clustering behavior and prevalent conditions:
294    ///
295    /// - Identifies common price points that may act as magnets or barriers
296    /// - Detects clustering in volume or activity patterns
297    /// - Provides non-parametric central tendency alternative to mean/median
298    /// - Highlights dominant price levels where transactions concentrate
299    ///
300    /// # Returns
301    ///
302    /// * `Option<T>` - The mode of values in the window, or `None` if the window is not full
303    ///
304    /// # Examples
305    ///
306    /// ```
307    /// # use ta_statistics::SingleStatistics;
308    /// # use assert_approx_eq::assert_approx_eq;
309    /// let mut stats = SingleStatistics::new(3);
310    /// let inputs = [1.0, 2.0, 1.0, 2.0, 3.0, 3.0, 3.0, 2.0, 2.0, 1.0];
311    /// let mut results = vec![];
312    ///
313    /// inputs.iter().for_each(|i| {
314    ///     stats.next(*i).mode().map(|v| results.push(v));
315    /// });
316    ///
317    /// let expected: [f64; 8] = [1.0, 2.0, 1.0, 3.0, 3.0, 3.0, 2.0, 2.0];
318    /// assert_eq!(&results, &expected);
319    /// ```
320    pub fn mode(&mut self) -> Option<T> {
321        if !self.moments.is_ready() {
322            return None;
323        }
324
325        let slice = self.sorted_buf();
326
327        let mut mode = slice[0];
328        let mut mode_count = 1;
329
330        let mut current = slice[0];
331        let mut current_count = 1;
332
333        for &value in &slice[1..] {
334            if value == current {
335                current_count += 1;
336            } else {
337                if current_count > mode_count || (current_count == mode_count && current < mode) {
338                    mode = current;
339                    mode_count = current_count;
340                }
341                current = value;
342                current_count = 1;
343            }
344        }
345
346        if current_count > mode_count || (current_count == mode_count && current < mode) {
347            mode = current;
348        }
349
350        Some(mode)
351    }
352
353    /// Returns the median (middle value) of the rolling window
354    ///
355    /// The median represents the central value when data is sorted, providing a robust
356    /// measure of central tendency that's less affected by outliers than the mean:
357    ///
358    /// - Offers resilient central price estimation in volatile conditions
359    /// - Establishes more stable reference points during extreme events
360    /// - Provides core input for non-parametric statistical models
361    /// - Creates baseline for interquartile range and other robust dispersion measures
362    ///
363    /// # Returns
364    ///
365    /// * `Option<T>` - The median of values in the window, or `None` if the window is not full
366    ///
367    /// # Examples
368    ///
369    /// ```
370    /// # use ta_statistics::SingleStatistics;
371    /// # use assert_approx_eq::assert_approx_eq;
372    /// let mut stats = SingleStatistics::new(3);
373    /// let inputs = [5.0, 2.0, 8.0, 1.0, 7.0, 3.0, 9.0];
374    /// let mut results = vec![];
375    ///
376    /// inputs.iter().for_each(|i| {
377    ///     stats.next(*i).median().map(|v| results.push(v));
378    /// });
379    ///
380    /// let expected: [f64; 5] = [5.0, 2.0, 7.0, 3.0, 7.0];
381    /// assert_eq!(&results, &expected);
382    /// ```
383    pub fn median(&mut self) -> Option<T> {
384        self.moments
385            .is_ready()
386            .then_some(median_from_sorted_slice(self.sorted_buf()))
387    }
388
389    /// Returns the minimum value in the rolling window
390    ///
391    /// The minimum value represents the lower bound of a data series over the observation
392    /// period, providing key reference points for analysis and decision-making:
393    ///
394    /// - Establishes potential support levels in price-based analysis
395    /// - Identifies optimal entry thresholds for mean-reverting sequences
396    /// - Sets critical risk boundaries for position management systems
397    /// - Provides baseline scenarios for stress-testing and risk modeling
398    ///
399    /// # Returns
400    ///
401    /// * `Option<T>` - The minimum value in the window, or `None` if the window is not full
402    ///
403    /// # Examples
404    ///
405    /// ```
406    /// # use ta_statistics::SingleStatistics;
407    /// # use assert_approx_eq::assert_approx_eq;
408    /// let mut stats = SingleStatistics::new(3);
409    /// let inputs = [25.4, 26.2, 26.0, 26.1, 25.8, 25.9, 26.3, 26.2, 26.5];
410    /// let mut results = vec![];
411    ///
412    /// inputs.iter().for_each(|i| {
413    ///     stats.next(*i).min().map(|v| results.push(v));
414    /// });
415    ///
416    /// let expected: [f64; 7] = [25.4, 26.0, 25.8, 25.8, 25.8, 25.9, 26.2];
417    /// assert_eq!(&results, &expected);
418    /// ```
419    pub fn min(&mut self) -> Option<T> {
420        if !self.moments.is_ready() {
421            return None;
422        }
423
424        self.min = match self.min {
425            None => self.moments.min(),
426            Some(min) => {
427                if self.moments.popped() == Some(min) {
428                    self.moments.min()
429                } else if self.moments.value() < Some(min) {
430                    self.moments.value()
431                } else {
432                    Some(min)
433                }
434            }
435        };
436        self.min
437    }
438
439    /// Returns the maximum value in the rolling window
440    ///
441    /// The maximum value identifies the upper bound of a data series over the observation
442    /// period, establishing crucial reference points for analytical frameworks:
443    ///
444    /// - Identifies potential resistance zones in technical analysis
445    /// - Optimizes profit-taking thresholds based on historical precedent
446    /// - Confirms genuine breakouts from established trading ranges
447    /// - Defines upper boundaries for range-bound trading approaches
448    ///
449    /// # Returns
450    ///
451    /// * `Option<T>` - The maximum value in the window, or `None` if the window is not full
452    ///
453    /// # Examples
454    ///
455    /// ```
456    /// # use ta_statistics::SingleStatistics;
457    /// # use assert_approx_eq::assert_approx_eq;
458    /// let mut stats = SingleStatistics::new(3);
459    /// let inputs = [25.4, 26.2, 26.0, 26.1, 25.8, 25.9, 26.3, 26.2, 26.5];
460    /// let mut results = vec![];
461    ///
462    /// inputs.iter().for_each(|i| {
463    ///     stats.next(*i).max().map(|v| results.push(v));
464    /// });
465    ///
466    /// let expected: [f64; 7] = [26.2, 26.2, 26.1, 26.1, 26.3, 26.3, 26.5];
467    /// assert_eq!(&results, &expected);
468    /// ```
469    pub fn max(&mut self) -> Option<T> {
470        if !self.moments.is_ready() {
471            return None;
472        }
473
474        self.max = match self.max {
475            None => self.moments.max(),
476            Some(max) => {
477                if self.moments.popped() == Some(max) {
478                    self.moments.max()
479                } else if self.moments.value() > Some(max) {
480                    self.moments.value()
481                } else {
482                    Some(max)
483                }
484            }
485        };
486
487        self.max
488    }
489
490    /// Returns the mean absolute deviation of values in the rolling window
491    ///
492    /// This robust dispersion measure calculates the average absolute difference from the mean,
493    /// offering advantages over variance in certain analytical contexts:
494    ///
495    /// - Provides volatility measurement that's less sensitive to extreme outliers
496    /// - Quantifies data noise levels to enhance signal processing accuracy
497    /// - Complements standard risk models with an alternative dispersion metric
498    /// - Establishes more stable thresholds for adaptive signal generation
499    ///
500    /// # Returns
501    ///
502    /// * `Option<T>` - The mean absolute deviation of values, or `None` if the window is not full
503    ///
504    /// # Examples
505    ///
506    /// ```
507    /// # use ta_statistics::SingleStatistics;
508    /// # use assert_approx_eq::assert_approx_eq;
509    /// let mut stats = SingleStatistics::new(3);
510    /// let mut results = vec![];
511    /// let inputs = [2.0, 4.0, 6.0, 8.0, 10.0];
512    /// inputs.iter().for_each(|i| {
513    ///     stats.next(*i).mean_absolute_deviation().map(|v| results.push(v));
514    /// });
515    ///
516    /// let expected: [f64; 3] = [1.3333, 1.3333, 1.3333];
517    /// for (i, e) in expected.iter().enumerate() {
518    ///     assert_approx_eq!(e, results[i], 0.1);
519    /// }
520    ///
521    /// ```
522    pub fn mean_absolute_deviation(&self) -> Option<T>
523    where
524        T: Sum,
525    {
526        let mean = self.mean()?;
527        let abs_sum = self.moments.iter().map(|&x| (x - mean).abs()).sum::<T>();
528        self.period_t().map(|n| abs_sum / n)
529    }
530
531    /// Returns the median absolute deviation of values in the rolling window
532    ///
533    /// This exceptionally robust dispersion measure calculates the median of absolute differences
534    /// from the median, offering superior resistance to outliers:
535    ///
536    /// - Provides reliable volatility assessment in erratic or noisy environments
537    /// - Serves as a foundation for robust anomaly detection systems
538    /// - Enables stable threshold calibration for adaptive decision systems
539    /// - Forms basis for robust statistical estimators in non-normal distributions
540    ///
541    /// # Returns
542    ///
543    /// * `Option<T>` - The median absolute deviation of values, or `None` if the window is not full
544    ///
545    /// # Examples
546    ///
547    /// ```
548    /// # use ta_statistics::SingleStatistics;
549    /// # use assert_approx_eq::assert_approx_eq;
550    /// let mut stats = SingleStatistics::new(3);
551    /// let mut results = vec![];
552    /// let inputs = [5.0, 2.0, 8.0, 1.0, 7.0, 3.0, 9.0];
553    /// inputs.iter().for_each(|i| {
554    ///     stats.next(*i).median_absolute_deviation().map(|v| results.push(v));
555    /// });
556    ///
557    /// let expected: [f64; 5] = [3.0, 1.0, 1.0, 2.0, 2.0];
558    /// for (i, e) in expected.iter().enumerate() {
559    ///     assert_approx_eq!(e, results[i], 0.1);
560    /// }
561    ///
562    /// ```
563    pub fn median_absolute_deviation(&mut self) -> Option<T> {
564        let median = self.median()?;
565        self.sorted_buf
566            .iter_mut()
567            .zip(self.moments.as_slice())
568            .for_each(|(dev, &x)| *dev = (x - median).abs());
569
570        Some(median_from_sorted_slice(self.sorted_buf.sort()))
571    }
572
573    /// Returns the variance of values in the rolling window
574    ///
575    /// This second-moment statistical measure quantifies dispersion around the mean
576    /// and serves multiple analytical purposes:
577    ///
578    /// - Providing core risk assessment metrics for position sizing decisions
579    /// - Enabling volatility regime detection to adapt methodologies appropriately
580    /// - Filtering signal noise to improve discriminatory power
581    /// - Identifying dispersion-based opportunities in related instrument groups
582    ///
583    /// # Returns
584    ///
585    /// * `Option<T>` - The variance of values in the window, or `None` if the window is not full
586    ///
587    /// # Examples
588    ///
589    /// ```
590    /// # use ta_statistics::SingleStatistics;
591    /// # use assert_approx_eq::assert_approx_eq;
592    /// let mut stats = SingleStatistics::new(3);
593    /// let mut results = vec![];
594    /// let inputs = [25.4, 26.2, 26.0, 26.1, 25.8, 25.9, 26.3, 26.2, 26.5];
595    /// inputs.iter().for_each(|i| {
596    ///     stats.next(*i).variance().map(|v| results.push(v));
597    /// });
598    ///
599    /// let expected: [f64; 7] = [0.1156, 0.0067, 0.0156, 0.0156, 0.0467, 0.0289, 0.0156];
600    /// for (i, e) in expected.iter().enumerate() {
601    ///     assert_approx_eq!(e, results[i], 0.0001);
602    /// }
603    ///
604    /// stats.reset().set_ddof(true);
605    /// results = vec![];
606    /// inputs.iter().for_each(|i| {
607    ///     stats.next(*i).variance().map(|v| results.push(v));
608    /// });
609    ///
610    /// let expected: [f64; 7] = [0.1733, 0.01, 0.0233, 0.0233, 0.07, 0.0433, 0.0233];
611    /// for (i, e) in expected.iter().enumerate() {
612    ///     assert_approx_eq!(e, results[i], 0.0001);
613    /// }
614    /// ```
615    pub fn variance(&self) -> Option<T> {
616        self.moments.variance()
617    }
618
619    /// Returns the standard deviation of values in the rolling window
620    ///
621    /// As the square root of variance, this statistic provides an intuitive measure
622    /// of data dispersion in the original units and enables:
623    ///
624    /// - Setting dynamic volatility thresholds for risk boundaries
625    /// - Detecting potential mean-reversion opportunities when values deviate significantly
626    /// - Normalizing position sizing across different volatility environments
627    /// - Identifying market regime changes to adapt strategic approaches
628    ///
629    /// # Returns
630    ///
631    /// * `Option<T>` - The standard deviation of values in the window, or `None` if the window is not full
632    ///
633    /// # Examples
634    ///
635    /// ```
636    /// # use ta_statistics::SingleStatistics;
637    /// # use assert_approx_eq::assert_approx_eq;
638    /// let mut stats = SingleStatistics::new(3);
639    /// let mut results = vec![];
640    /// let inputs = [25.4, 26.2, 26.0, 26.1, 25.8, 25.9, 26.3, 26.2, 26.5];
641    /// inputs.iter().for_each(|i| {
642    ///     stats.next(*i).stddev().map(|v| results.push(v));
643    /// });
644    ///
645    /// let expected: [f64; 7] = [0.3399, 0.0816, 0.1247, 0.1247, 0.216, 0.17, 0.1247];
646    /// for (i, e) in expected.iter().enumerate() {
647    ///     assert_approx_eq!(e, results[i], 0.0001);
648    /// }
649    ///
650    /// stats.reset().set_ddof(true);
651    /// results = vec![];
652    /// inputs.iter().for_each(|i| {
653    ///     stats.next(*i).stddev().map(|v| results.push(v));
654    /// });
655    ///
656    /// let expected: [f64; 7] = [0.4163, 0.1, 0.1528, 0.1528, 0.2646, 0.2082, 0.1528];
657    /// for (i, e) in expected.iter().enumerate() {
658    ///     assert_approx_eq!(e, results[i], 0.0001);
659    /// }
660    /// ```
661    pub fn stddev(&self) -> Option<T> {
662        self.moments.stddev()
663    }
664
665    /// Returns the z-score of the most recent value relative to the rolling window
666    ///
667    /// Z-scores express how many standard deviations a value deviates from the mean,
668    /// providing a normalized measure that facilitates:
669    ///
670    /// - Statistical arbitrage through relative valuation in correlated series
671    /// - Robust outlier detection across varying market conditions
672    /// - Cross-instrument comparisons on a standardized scale
673    /// - Setting consistent thresholds that remain valid across changing volatility regimes
674    ///
675    /// # Returns
676    ///
677    /// * `Option<T>` - The z-score of the most recent value, or `None` if the window is not full
678    ///
679    /// # Examples
680    ///
681    /// ```
682    /// # use ta_statistics::SingleStatistics;
683    /// # use assert_approx_eq::assert_approx_eq;
684    /// let mut stats = SingleStatistics::new(3);
685    /// let mut results = vec![];
686    /// let inputs = [1.2, -0.7, 3.4, 2.1, -1.5, 0.0, 2.2, -0.3, 1.5, -2.0];
687    /// inputs.iter().for_each(|i| {
688    ///     stats.next(*i).zscore().map(|v| results.push(v));
689    /// });
690    ///
691    /// let expected: [f64; 8] = [1.2535, 0.2923, -1.3671, -0.1355, 1.2943, -0.8374, 0.3482, -1.2129];
692    /// for (i, e) in expected.iter().enumerate() {
693    ///     assert_approx_eq!(e, results[i], 0.0001);
694    /// }
695    ///
696    /// stats.reset().set_ddof(true);
697    /// results = vec![];
698    /// inputs.iter().for_each(|i| {
699    ///     stats.next(*i).zscore().map(|v| results.push(v));
700    /// });
701    ///
702    /// let expected: [f64; 8] = [1.0235, 0.2386, -1.1162, -0.1106, 1.0568, -0.6837, 0.2843, -0.9903];
703    /// for (i, e) in expected.iter().enumerate() {
704    ///     assert_approx_eq!(e, results[i], 0.0001);
705    /// }
706    /// ```
707    pub fn zscore(&self) -> Option<T> {
708        self.moments.zscore()
709    }
710
711    /// Returns the skewness of values in the rolling window
712    ///
713    /// This third-moment statistic measures distribution asymmetry, revealing whether
714    /// extreme values tend toward one direction. A comprehensive analysis of skewness:
715    ///
716    /// - Detects asymmetric return distributions critical for accurate risk modeling
717    /// - Reveals directional biases in market microstructure that may predict future movements
718    /// - Provides early signals of potential regime transitions in volatile environments
719    /// - Enables refined models for derivative pricing beyond simple volatility assumptions
720    ///
721    /// # Returns
722    ///
723    /// * `Option<T>` - The skewness of values in the window, or `None` if the window is not full
724    ///
725    /// # Examples
726    ///
727    /// ```
728    /// # use ta_statistics::SingleStatistics;
729    /// # use assert_approx_eq::assert_approx_eq;
730    /// let mut stats = SingleStatistics::new(4);
731    /// let mut results = vec![];
732    /// let inputs = [25.4, 26.2, 26.0, 26.1, 25.8, 25.9, 26.3, 26.2, 26.5];
733    /// inputs.iter().for_each(|i| {
734    ///     stats.next(*i).skew().map(|v| results.push(v));
735    /// });
736    ///
737    /// let expected: [f64; 6] =  [-0.97941, -0.43465, 0.0, 0.27803, 0.0, -0.32332];
738    /// for (i, e) in expected.iter().enumerate() {
739    ///     assert_approx_eq!(e, results[i], 0.0001);
740    /// }
741    ///
742    /// stats.reset().set_ddof(true);
743    /// results = vec![];
744    /// inputs.iter().for_each(|i| {
745    ///     stats.next(*i).skew().map(|v| results.push(v));
746    /// });
747    ///
748    /// let expected: [f64; 6] = [-1.69639, -0.75284, 0.0, 0.48156, 0.0, -0.56];
749    /// for (i, e) in expected.iter().enumerate() {
750    ///     assert_approx_eq!(e, results[i], 0.0001);
751    /// }
752    /// ```
753    pub fn skew(&self) -> Option<T> {
754        self.moments.skew()
755    }
756
757    /// Returns the kurtosis of values in the rolling window
758    ///
759    /// This fourth-moment statistic measures the 'tailedness' of a distribution, describing
760    /// the frequency of extreme values compared to a normal distribution:
761    ///
762    /// - Quantifies fat-tail risk exposure essential for anticipating extreme market movements
763    /// - Signals potentially exploitable market inefficiencies through distribution analysis
764    /// - Provides critical parameters for selecting appropriate derivatives strategies
765    /// - Enhances Value-at-Risk models by incorporating more realistic tail behavior
766    ///
767    /// # Returns
768    ///
769    /// * `Option<T>` - The kurtosis of values in the window, or `None` if the window is not full
770    ///
771    /// # Examples
772    ///
773    /// ```
774    /// # use ta_statistics::SingleStatistics;
775    /// # use assert_approx_eq::assert_approx_eq;
776    /// let mut stats = SingleStatistics::new(4);
777    /// let mut results = vec![];
778    /// let inputs = [25.4, 26.2, 26.0, 26.1, 25.8, 25.9, 26.3, 26.2, 26.5];
779    /// inputs.iter().for_each(|i| {
780    ///     stats.next(*i).kurt().map(|v| results.push(v));
781    /// });
782    ///
783    /// let expected: [f64; 6] = [-0.7981, -1.1543, -1.3600, -1.4266, -1.7785, -1.0763];
784    /// for (i, e) in expected.iter().enumerate() {
785    ///     assert_approx_eq!(e, results[i], 0.0001);
786    /// }
787    ///
788    /// stats.reset().set_ddof(true);
789    /// results = vec![];
790    /// inputs.iter().for_each(|i| {
791    ///     stats.next(*i).kurt().map(|v| results.push(v));
792    /// });
793    ///
794    /// let expected: [f64; 6] = [3.0144, 0.3429, -1.2, -1.6995, -4.3391, 0.928];
795    /// for (i, e) in expected.iter().enumerate() {
796    ///   assert_approx_eq!(e, results[i], 0.0001);
797    /// }
798    /// ```
799    pub fn kurt(&self) -> Option<T> {
800        self.moments.kurt()
801    }
802
803    /// Returns the slope of the linear regression line
804    ///
805    /// The regression slope represents the rate of change in the best-fit linear model,
806    /// quantifying the directional movement and its magnitude within the data:
807    ///
808    /// - Provides precise measurement of trend strength and conviction
809    /// - Quantifies velocity of change for optimal timing decisions
810    /// - Signals potential reversion points when diverging from historical patterns
811    /// - Measures the relative imbalance between supply and demand forces
812    ///
813    /// # Returns
814    ///
815    /// * `Option<T>` - The slope of the linear regression line, or `None` if the window is not full
816    ///
817    /// # Examples
818    ///
819    /// ```
820    /// # use ta_statistics::SingleStatistics;
821    /// # use assert_approx_eq::assert_approx_eq;
822    /// let mut stats = SingleStatistics::new(5);
823    /// let mut results = vec![];
824    /// let inputs = [10.0, 10.5, 11.2, 10.9, 11.5, 11.9, 12.3, 12.1, 11.8, 12.5];
825    /// inputs.iter().for_each(|i| {
826    ///     stats.next(*i).linreg_slope().map(|v| results.push(v));
827    /// });
828    ///
829    /// let expected: [f64; 6] = [0.34, 0.31, 0.32, 0.32, 0.08, 0.07];
830    /// for (i, e) in expected.iter().enumerate() {
831    ///     assert_approx_eq!(e, results[i], 0.1);
832    /// }
833    /// ```
834    pub fn linreg_slope(&self) -> Option<T> {
835        if !self.moments.is_ready() {
836            return None;
837        }
838
839        let mut s = PairedStatistics::new(self.period());
840        for (i, &x) in self.moments.iter().enumerate() {
841            s.next((x, T::from(i)?));
842        }
843
844        s.beta()
845    }
846
847    /// Returns both slope and intercept of the linear regression line
848    ///
849    /// This comprehensive regression analysis provides the complete linear model,
850    /// enabling more sophisticated trend-based calculations:
851    ///
852    /// - Constructs complete linear models of price or indicator evolution
853    /// - Determines both direction and reference level in a single calculation
854    /// - Enables advanced divergence analysis against actual values
855    /// - Provides foundation for channel-based analytical frameworks
856    ///
857    /// # Returns
858    ///
859    /// * `Option<(T, T)>` - A tuple containing (slope, intercept), or `None` if the window is not full
860    ///
861    /// # Examples
862    ///
863    /// ```
864    /// # use ta_statistics::SingleStatistics;
865    /// # use assert_approx_eq::assert_approx_eq;
866    /// let mut stats = SingleStatistics::new(5);
867    /// let mut ddof = false;
868    /// let mut results = vec![];
869    /// let inputs = [10.0, 10.5, 11.2, 10.9, 11.5, 11.9, 12.3, 12.1, 11.8, 12.5];
870    /// inputs.iter().for_each(|i| {
871    ///     stats.next(*i).linreg_slope_intercept().map(|v| results.push(v));
872    /// });
873    ///
874    /// let expected: [(f64, f64); 6] = [
875    ///     (0.34, 10.14),
876    ///     (0.31, 10.58),
877    ///     (0.32, 10.92),
878    ///     (0.32, 11.1),
879    ///     (0.08, 11.76),
880    ///     (0.07, 11.98),
881    /// ];
882    /// for (i, e) in expected.iter().enumerate() {
883    ///     assert_approx_eq!(e.0, results[i].0, 0.1);
884    ///     assert_approx_eq!(e.1, results[i].1, 0.1);
885    /// }
886    /// ```
887    pub fn linreg_slope_intercept(&self) -> Option<(T, T)> {
888        let (mean, slope) = self.mean().zip(self.linreg_slope())?;
889        let _1 = T::one();
890        self.period_t()
891            .zip(T::from(2))
892            .map(|(p, _2)| (p - _1) / _2)
893            .map(|mt| (slope, mean - slope * mt))
894    }
895
896    /// Returns the y-intercept of the linear regression line
897    ///
898    /// The regression intercept represents the base level or starting point of the
899    /// best-fit linear model, providing key reference information:
900    ///
901    /// - Establishes the theoretical zero-point reference level
902    /// - Complements slope calculations to complete linear projections
903    /// - Assists in fair value determination for mean-reversion models
904    /// - Provides a fixed component for decomposing price into trend and oscillation
905    ///
906    /// # Returns
907    ///
908    /// * `Option<T>` - The y-intercept of the regression line, or `None` if the window is not full
909    ///
910    /// # Examples
911    ///
912    /// ```
913    /// # use ta_statistics::SingleStatistics;
914    /// # use assert_approx_eq::assert_approx_eq;
915    /// let mut stats = SingleStatistics::new(5);
916    /// let mut results = vec![];
917    /// let inputs = [10.0, 10.5, 11.2, 10.9, 11.5, 11.9, 12.3, 12.1, 11.8, 12.5];
918    /// inputs.iter().for_each(|i| {
919    ///     stats.next(*i).linreg_intercept().map(|v| results.push(v));
920    /// });
921    ///
922    /// let expected: [f64; 6] = [10.14, 10.58, 10.92, 11.1, 11.76, 11.98];
923    /// for (i, e) in expected.iter().enumerate() {
924    ///     assert_approx_eq!(e, results[i], 0.1);
925    /// }
926    ///
927    /// ```
928    pub fn linreg_intercept(&self) -> Option<T> {
929        self.linreg_slope_intercept()
930            .map(|(_, intercept)| intercept)
931    }
932
933    /// Returns the angle (in degrees) of the linear regression line
934    ///
935    /// The regression angle converts the slope into degrees, providing a more intuitive
936    /// measure of trend inclination that's bounded between -90 and 90 degrees:
937    ///
938    /// - Offers an easily interpretable measure of trend strength
939    /// - Provides normalized measurement across different scaling contexts
940    /// - Enables clear categorization of trend intensity
941    /// - Simplifies visual representation of directional movement
942    ///
943    /// # Returns
944    ///
945    /// * `Option<T>` - The angle of the regression line in degrees, or `None` if the window is not full
946    ///
947    /// # Examples
948    ///
949    /// ```
950    /// # use ta_statistics::SingleStatistics;
951    /// # use assert_approx_eq::assert_approx_eq;
952    /// let mut stats = SingleStatistics::new(5);
953    /// let mut ddof = false;
954    /// let mut results = vec![];
955    /// let inputs = [10.0, 10.5, 11.2, 10.9, 11.5, 11.9, 12.3, 12.1, 11.8, 12.5];
956    /// inputs.iter().for_each(|i| {
957    ///     stats.next(*i).linreg_angle().map(|v| results.push(v));
958    /// });
959    ///
960    /// let expected: [f64; 6] = [0.3396, 0.3100, 0.3199, 0.3199, 0.0799, 0.0699];
961    /// for (i, e) in expected.iter().enumerate() {
962    ///     assert_approx_eq!(e, results[i], 0.1);
963    /// }
964    ///
965    /// ```
966    pub fn linreg_angle(&self) -> Option<T> {
967        self.linreg_slope().map(|slope| slope.atan())
968    }
969
970    /// Returns the linear regression value (predicted y) for the last position
971    ///
972    /// This calculation provides the expected value at the current position according to
973    /// the best-fit linear model across the window period:
974    ///
975    /// - Establishes theoretical fair value targets for mean-reversion analysis
976    /// - Projects trend trajectory for momentum-based methodologies
977    /// - Filters cyclical noise to extract underlying directional bias
978    /// - Provides basis for divergence analysis between actual and expected values
979    ///
980    /// # Returns
981    ///
982    /// * `Option<T>` - The predicted value at the current position, or `None` if the window is not full
983    ///
984    /// # Examples
985    ///
986    /// ```
987    /// # use ta_statistics::SingleStatistics;
988    /// # use assert_approx_eq::assert_approx_eq;
989    /// let mut stats = SingleStatistics::new(5);
990    /// let mut ddof = false;
991    /// let mut results = vec![];
992    /// let inputs = [10.0, 10.5, 11.2, 10.9, 11.5, 11.9, 12.3, 12.1, 11.8, 12.5];
993    /// inputs.iter().for_each(|i| {
994    ///     stats.next(*i).linreg().map(|v| results.push(v));
995    /// });
996    ///
997    /// let expected: [f64; 6] = [11.5, 11.82, 12.2, 12.38, 12.08, 12.26];
998    /// for (i, e) in expected.iter().enumerate() {
999    ///     assert_approx_eq!(e, results[i], 0.1);
1000    /// }
1001    ///
1002    /// ```
1003    pub fn linreg(&self) -> Option<T> {
1004        let _1 = T::one();
1005        self.linreg_slope_intercept()
1006            .zip(self.period_t())
1007            .map(|((slope, intercept), period)| slope * (period - _1) + intercept)
1008    }
1009
1010    /// Returns the current drawdown from peak
1011    ///
1012    /// Measures the percentage decline from the highest observed value to the current value,
1013    /// providing crucial insights for risk management and performance evaluation:
1014    ///
1015    /// - Enables dynamic adjustment of risk exposure during challenging conditions
1016    /// - Facilitates strategy rotation based on relative performance metrics
1017    /// - Forms the foundation of capital preservation systems during market stress
1018    /// - Identifies potential opportunities for strategic positioning during dislocations
1019    ///
1020    /// # Returns
1021    ///
1022    /// * `Option<T>` - The current drawdown from peak, or `None` if the window is not full
1023    ///
1024    /// # Examples
1025    ///
1026    /// ```
1027    /// # use ta_statistics::SingleStatistics;
1028    /// # use assert_approx_eq::assert_approx_eq;
1029    /// let mut stats = SingleStatistics::new(3);
1030    /// let mut results = vec![];
1031    /// let inputs = [100.0, 110.0, 105.0, 115.0, 100.0, 95.0, 105.0, 110.0, 100.0];
1032    /// inputs.iter().for_each(|i| {
1033    ///     stats.next(*i).drawdown().map(|v| results.push(v));
1034    /// });
1035    ///
1036    /// let expected: [f64; 7] = [0.045, 0.0, 0.13, 0.174, 0.0, 0.0, 0.091];
1037    /// for (i, e) in expected.iter().enumerate() {
1038    ///     assert_approx_eq!(e, results[i], 0.1);
1039    /// }
1040    /// ```
1041    pub fn drawdown(&mut self) -> Option<T> {
1042        self.max().zip(self.moments.value()).map(|(max, input)| {
1043            if max <= T::zero() || input <= T::zero() {
1044                T::zero()
1045            } else {
1046                ((max - input) / max).max(T::zero())
1047            }
1048        })
1049    }
1050
1051    /// Returns the maximum drawdown in the window
1052    ///
1053    /// Maximum drawdown measures the largest peak-to-trough decline within a time series,
1054    /// serving as a foundational metric for risk assessment and strategy evaluation:
1055    ///
1056    /// - Establishes critical constraints for comprehensive risk management frameworks
1057    /// - Provides an objective metric for evaluating strategy viability under stress
1058    /// - Informs position sizing parameters to maintain proportional risk exposure
1059    /// - Contributes valuable input to market regime classification models
1060    ///
1061    /// # Returns
1062    ///
1063    /// * `Option<T>` - The maximum drawdown in the window, or `None` if the window is not full
1064    ///
1065    /// # Examples
1066    ///
1067    /// ```
1068    /// # use ta_statistics::SingleStatistics;
1069    /// # use assert_approx_eq::assert_approx_eq;
1070    /// let mut stats = SingleStatistics::new(3);
1071    /// let mut results = vec![];
1072    /// let inputs = [100.0, 110.0, 105.0, 115.0, 100.0, 95.0, 105.0, 110.0, 100.0];
1073    /// inputs.iter().for_each(|i| {
1074    ///     stats.next(*i).max_drawdown().map(|v| results.push(v));
1075    /// });
1076    ///
1077    /// let expected: [f64; 7] = [0.045, 0.045, 0.13, 0.174, 0.174, 0.174, 0.174];
1078    /// for (i, e) in expected.iter().enumerate() {
1079    ///     assert_approx_eq!(e, results[i], 0.1);
1080    /// }
1081    ///
1082    /// ```
1083    pub fn max_drawdown(&mut self) -> Option<T> {
1084        let drawdown = self.drawdown()?;
1085        self.max_drawdown = match self.max_drawdown {
1086            Some(md) => Some(md.max(drawdown)),
1087            None => Some(drawdown),
1088        };
1089        self.max_drawdown
1090    }
1091
1092    /// Returns the difference between the last and first values
1093    ///
1094    /// This fundamental calculation of absolute change between two points provides
1095    /// essential directional and magnitude information for time series analysis:
1096    ///
1097    /// - Enables momentum measurement for trend strength evaluation
1098    /// - Quantifies rate-of-change to optimize timing decisions
1099    /// - Serves as a building block for pattern recognition in sequential data
1100    /// - Provides critical inputs for calculating hedge ratios and exposure management
1101    ///
1102    /// # Returns
1103    ///
1104    /// * `Option<T>` - The difference between values, or `None` if the window is not full
1105    ///
1106    /// # Examples
1107    ///
1108    /// ```
1109    /// # use ta_statistics::SingleStatistics;
1110    /// # use assert_approx_eq::assert_approx_eq;
1111    /// let mut stats = SingleStatistics::new(3);
1112    /// let mut results = vec![];
1113    /// let inputs = [100.0, 102.0, 105.0, 101.0, 98.0];
1114    /// inputs.iter().for_each(|i| {
1115    ///     stats.next(*i).diff().map(|v| results.push(v));
1116    /// });
1117    ///
1118    /// let expected: [f64; 2] = [1.0, -4.0];
1119    /// for (i, e) in expected.iter().enumerate() {
1120    ///     assert_approx_eq!(e, results[i], 0.1);
1121    /// }
1122    ///
1123    /// ```
1124    pub fn diff(&self) -> Option<T> {
1125        self.moments
1126            .value()
1127            .zip(self.moments.popped())
1128            .map(|(input, popped)| input - popped)
1129    }
1130
1131    /// Returns the percentage change between the first and last values
1132    ///
1133    /// Percentage change normalizes absolute changes by the starting value, enabling
1134    /// meaningful comparisons across different scales and measurement contexts:
1135    ///
1136    /// - Facilitates cross-asset performance comparison for relative strength analysis
1137    /// - Provides risk-normalized return metrics that account for initial exposure
1138    /// - Enables position sizing that properly adjusts for varying volatility environments
1139    /// - Serves as a key input for comparative performance evaluation across related groups
1140    ///
1141    /// # Returns
1142    ///
1143    /// * `Option<T>` - The percentage change, or `None` if the window is not full
1144    ///
1145    /// # Examples
1146    ///
1147    /// ```
1148    /// # use ta_statistics::SingleStatistics;
1149    /// # use assert_approx_eq::assert_approx_eq;
1150    /// let mut stats = SingleStatistics::new(3);
1151    /// let mut results = vec![];
1152    /// let inputs = [100.0, 105.0, 103.0, 106.0, 110.0, 108.0];
1153    /// inputs.iter().for_each(|i| {
1154    ///     stats.next(*i).pct_change().map(|v| results.push(v));
1155    /// });
1156    ///
1157    /// let expected: [f64; 3] = [0.06, 0.04761905, 0.04854369];
1158    /// for (i, e) in expected.iter().enumerate() {
1159    ///     assert_approx_eq!(e, results[i], 0.1);
1160    /// }
1161    ///
1162    /// ```
1163    pub fn pct_change(&self) -> Option<T> {
1164        self.diff()
1165            .zip(self.moments.popped())
1166            .and_then(|(diff, popped)| {
1167                if popped.is_zero() {
1168                    None
1169                } else {
1170                    Some(diff / popped)
1171                }
1172            })
1173    }
1174
1175    /// Returns the logarithmic return between the first and last values
1176    ///
1177    /// Logarithmic returns (continuous returns) offer mathematical advantages over simple
1178    /// returns, particularly for time series analysis:
1179    ///
1180    /// - Provides time-additive metrics that can be properly aggregated across periods
1181    /// - Normalizes signals in relative-value analysis of related securities
1182    /// - Creates a more consistent volatility scale regardless of price levels
1183    /// - Improves accuracy in long-horizon analyses through proper handling of compounding
1184    ///
1185    /// # Returns
1186    ///
1187    /// * `Option<T>` - The logarithmic return, or `None` if the window is not full
1188    ///
1189    /// # Examples
1190    ///
1191    /// ```
1192    /// # use ta_statistics::SingleStatistics;
1193    /// # use assert_approx_eq::assert_approx_eq;
1194    /// let mut stats = SingleStatistics::new(3);
1195    /// let mut results = vec![];
1196    /// let inputs = [100.0, 105.0, 103.0, 106.0, 110.0, 108.0];
1197    /// inputs.iter().for_each(|i| {
1198    ///     stats.next(*i).log_return().map(|v| results.push(v));
1199    /// });
1200    ///
1201    /// let expected: [f64; 3] = [0.05827, 0.04652, 0.04727];
1202    /// for (i, e) in expected.iter().enumerate() {
1203    ///     assert_approx_eq!(e, results[i], 0.1);
1204    /// }
1205    ///
1206    /// ```
1207    pub fn log_return(&self) -> Option<T> {
1208        self.moments
1209            .value()
1210            .zip(self.moments.popped())
1211            .and_then(|(current, popped)| {
1212                if popped <= T::zero() || current <= T::zero() {
1213                    None
1214                } else {
1215                    Some(current.ln() - popped.ln())
1216                }
1217            })
1218    }
1219
1220    /// Returns the quantile of the values in the window
1221    ///
1222    /// # Arguments
1223    ///
1224    /// * `q` - The quantile to calculate
1225    ///
1226    /// # Returns
1227    ///
1228    /// * `Option<T>` - The quantile, or `None` if the window is not full
1229    ///
1230    /// # Examples
1231    ///
1232    /// ```
1233    /// # use ta_statistics::SingleStatistics;
1234    /// # use assert_approx_eq::assert_approx_eq;
1235    /// let mut stats = SingleStatistics::new(3);
1236    /// let mut results = vec![];
1237    /// let inputs = [10.0, 20.0, 30.0, 40.0, 50.0];
1238    /// inputs.iter().for_each(|i| {
1239    ///     stats.next(*i).quantile(0.5).map(|v| results.push(v));
1240    /// });
1241    ///
1242    /// let expected: [f64; 3] = [20.0, 30.0, 40.0];
1243    /// for (i, e) in expected.iter().enumerate() {
1244    ///     assert_approx_eq!(e, results[i], 0.1);
1245    /// }
1246    /// ```
1247    pub fn quantile(&mut self, q: f64) -> Option<T> {
1248        if !self.moments.is_ready() || !(0.0..=1.0).contains(&q) {
1249            return None;
1250        }
1251        let period = self.period();
1252        let sorted = self.sorted_buf();
1253        quantile_from_sorted_slice(sorted, q, period)
1254    }
1255
1256    /// Returns the interquartile range of the values in the window
1257    ///
1258    /// # Returns
1259    ///
1260    /// * `Option<T>` - The interquartile range, or `None` if the window is not full
1261    ///
1262    /// # Examples
1263    ///
1264    /// ```
1265    /// # use ta_statistics::SingleStatistics;
1266    /// # use assert_approx_eq::assert_approx_eq;
1267    /// let mut stats = SingleStatistics::new(3);
1268    /// let mut results = vec![];
1269    /// let inputs = [10.0, 20.0, 30.0, 40.0, 50.0];
1270    /// inputs.iter().for_each(|i| {
1271    ///     stats.next(*i).iqr().map(|v| results.push(v));
1272    /// });
1273    ///
1274    /// let expected: [f64; 3] = [10.0, 10.0, 10.0];
1275    /// for (i, e) in expected.iter().enumerate() {
1276    ///     assert_approx_eq!(e, results[i], 0.1);
1277    /// }
1278    ///
1279    /// ```
1280    pub fn iqr(&mut self) -> Option<T> {
1281        if !self.moments.is_ready() {
1282            return None;
1283        }
1284
1285        let period = self.period();
1286        let sorted = self.sorted_buf();
1287
1288        let q1 = quantile_from_sorted_slice(sorted, 0.25, period);
1289        let q3 = quantile_from_sorted_slice(sorted, 0.75, period);
1290
1291        q1.zip(q3).map(|(q1, q3)| q3 - q1)
1292    }
1293}