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