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