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