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