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