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