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