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