ta_statistics/
single_statistics.rs

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