ta_statistics/
single_statistics.rs

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