finlib_ta/indicators/
efficiency_ratio.rs

1use alloc::boxed::Box;
2use alloc::vec;
3use core::fmt;
4
5use crate::errors::{Result, TaError};
6use crate::traits::{Close, Next, Period, Reset};
7#[cfg(feature = "serde")]
8use serde::{Deserialize, Serialize};
9
10/// Kaufman's Efficiency Ratio (ER).
11///
12/// It is calculated by dividing the price change over a period by the absolute sum of the price movements that occurred to achieve that change.
13/// The resulting ratio ranges between 0.0 and 1.0 with higher values representing a more efficient or trending market.
14///
15/// # Parameters
16///
17/// * _period_ - number of periods (integer greater than 0)
18///
19/// # Example
20///
21/// ```
22/// use finlib_ta::indicators::EfficiencyRatio;
23/// use finlib_ta::Next;
24///
25/// let mut er = EfficiencyRatio::new(4).unwrap();
26/// assert_eq!(er.next(10.0), 1.0);
27/// assert_eq!(er.next(13.0), 1.0);
28/// assert_eq!(er.next(12.0), 0.5);
29/// assert_eq!(er.next(13.0), 0.6);
30/// assert_eq!(er.next(18.0), 0.8);
31/// assert_eq!(er.next(19.0), 0.75);
32/// ```
33
34#[doc(alias = "ER")]
35#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
36#[derive(Debug, Clone)]
37pub struct EfficiencyRatio {
38    period: usize,
39    index: usize,
40    count: usize,
41    deque: Box<[f64]>,
42}
43
44impl EfficiencyRatio {
45    pub fn new(period: usize) -> Result<Self> {
46        match period {
47            0 => Err(TaError::InvalidParameter),
48            _ => Ok(Self {
49                period,
50                index: 0,
51                count: 0,
52                deque: vec![0.0; period].into_boxed_slice(),
53            }),
54        }
55    }
56}
57
58impl Period for EfficiencyRatio {
59    fn period(&self) -> usize {
60        self.period
61    }
62}
63
64impl Next<f64> for EfficiencyRatio {
65    type Output = f64;
66
67    fn next(&mut self, input: f64) -> f64 {
68        let first = if self.count >= self.period {
69            self.deque[self.index]
70        } else {
71            self.count += 1;
72            self.deque[0]
73        };
74        self.deque[self.index] = input;
75
76        self.index = if self.index + 1 < self.period {
77            self.index + 1
78        } else {
79            0
80        };
81
82        let mut volatility = 0.0;
83        let mut previous = first;
84        for n in &self.deque[self.index..self.count] {
85            volatility += (previous - n).abs();
86            previous = *n;
87        }
88        for n in &self.deque[0..self.index] {
89            volatility += (previous - n).abs();
90            previous = *n;
91        }
92
93        (first - input).abs() / volatility
94    }
95}
96
97impl<T: Close> Next<&T> for EfficiencyRatio {
98    type Output = f64;
99
100    fn next(&mut self, input: &T) -> f64 {
101        self.next(input.close())
102    }
103}
104
105impl Reset for EfficiencyRatio {
106    fn reset(&mut self) {
107        self.index = 0;
108        self.count = 0;
109        for i in 0..self.period {
110            self.deque[i] = 0.0;
111        }
112    }
113}
114
115impl Default for EfficiencyRatio {
116    fn default() -> Self {
117        Self::new(14).unwrap()
118    }
119}
120
121impl fmt::Display for EfficiencyRatio {
122    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
123        write!(f, "ER({})", self.period)
124    }
125}
126
127#[cfg(test)]
128mod tests {
129    use super::*;
130    use crate::test_helper::*;
131    use alloc::format;
132
133    test_indicator!(EfficiencyRatio);
134
135    #[test]
136    fn test_new() {
137        assert!(EfficiencyRatio::new(0).is_err());
138        assert!(EfficiencyRatio::new(1).is_ok());
139    }
140
141    #[test]
142    fn test_next() {
143        let mut er = EfficiencyRatio::new(3).unwrap();
144
145        assert_eq!(round(er.next(3.0)), 1.0);
146        assert_eq!(round(er.next(5.0)), 1.0);
147        assert_eq!(round(er.next(2.0)), 0.2);
148        assert_eq!(round(er.next(3.0)), 0.0);
149        assert_eq!(round(er.next(1.0)), 0.667);
150        assert_eq!(round(er.next(3.0)), 0.2);
151        assert_eq!(round(er.next(4.0)), 0.2);
152        assert_eq!(round(er.next(6.0)), 1.0);
153    }
154
155    #[test]
156    fn test_reset() {
157        let mut er = EfficiencyRatio::new(3).unwrap();
158
159        er.next(3.0);
160        er.next(5.0);
161
162        er.reset();
163
164        assert_eq!(round(er.next(3.0)), 1.0);
165        assert_eq!(round(er.next(5.0)), 1.0);
166        assert_eq!(round(er.next(2.0)), 0.2);
167        assert_eq!(round(er.next(3.0)), 0.0);
168    }
169
170    #[test]
171    fn test_display() {
172        let er = EfficiencyRatio::new(17).unwrap();
173        assert_eq!(format!("{}", er), "ER(17)");
174    }
175}