quantaxis_rs/indicators/
efficiency_ratio.rs

1use std::collections::VecDeque;
2use std::fmt;
3
4use crate::errors::*;
5use crate::traits::{Close, Next, Reset};
6
7/// Kaufman's Efficiency Ratio (ER).
8///
9/// 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.
10/// The resulting ratio ranges between 0.0 and 1.0 with higher values representing a more efficient or trending market.
11///
12/// # Parameters
13///
14/// * _length_ - number of periods (integer greater than 0)
15///
16/// # Example
17///
18/// ```
19/// use quantaxis_rs::indicators::EfficiencyRatio;
20/// use quantaxis_rs::Next;
21///
22/// let mut er = EfficiencyRatio::new(4).unwrap();
23/// assert_eq!(er.next(10.0), 1.0);
24/// assert_eq!(er.next(13.0), 1.0);
25/// assert_eq!(er.next(12.0), 0.5);
26/// assert_eq!(er.next(13.0), 0.6);
27/// assert_eq!(er.next(18.0), 0.8);
28/// assert_eq!(er.next(19.0), 0.75);
29/// ```
30
31pub struct EfficiencyRatio {
32    length: u32,
33    prices: VecDeque<f64>,
34}
35
36impl EfficiencyRatio {
37    pub fn new(length: u32) -> Result<Self> {
38        if length == 0 {
39            Err(Error::from_kind(ErrorKind::InvalidParameter))
40        } else {
41            let indicator = Self {
42                length: length,
43                prices: VecDeque::with_capacity(length as usize + 1),
44            };
45            Ok(indicator)
46        }
47    }
48}
49
50impl Next<f64> for EfficiencyRatio {
51    type Output = f64;
52
53    fn next(&mut self, input: f64) -> f64 {
54        self.prices.push_back(input);
55
56        if self.prices.len() <= 2 {
57            return 1.0;
58        }
59
60        let first = self.prices[0];
61
62        // Calculate volatility
63        let volatility = self
64            .prices
65            .iter()
66            .skip(1)
67            .fold((first, 0.0), |(prev, sum), &val| {
68                (val, sum + (prev - val).abs())
69            })
70            .1;
71
72        // Calculate direction
73        let last_index = self.prices.len() - 1;
74        let direction = (first - self.prices[last_index]).abs();
75
76        // Get rid of the first element
77        if self.prices.len() > (self.length as usize) {
78            self.prices.pop_front();
79        }
80
81        // Return actual efficiency ratio
82        direction / volatility
83    }
84}
85
86impl<'a, T: Close> Next<&'a T> for EfficiencyRatio {
87    type Output = f64;
88
89    fn next(&mut self, input: &'a T) -> f64 {
90        self.next(input.close())
91    }
92}
93
94impl Reset for EfficiencyRatio {
95    fn reset(&mut self) {
96        self.prices.clear();
97    }
98}
99
100impl Default for EfficiencyRatio {
101    fn default() -> Self {
102        Self::new(14).unwrap()
103    }
104}
105
106impl fmt::Display for EfficiencyRatio {
107    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
108        write!(f, "ER({})", self.length)
109    }
110}
111
112#[cfg(test)]
113mod tests {
114    use super::*;
115    use crate::test_helper::*;
116    macro_rules! test_indicator {
117        ($i:tt) => {
118            #[test]
119            fn test_indicator() {
120                let bar = Bar::new();
121
122                // ensure Default trait is implemented
123                let mut indicator = $i::default();
124
125                // ensure Next<f64> is implemented
126                let first_output = indicator.next(12.3);
127
128                // ensure next accepts &DataItem as well
129                indicator.next(&bar);
130
131                // ensure Reset is implemented and works correctly
132                indicator.reset();
133                assert_eq!(indicator.next(12.3), first_output);
134
135                // ensure Display is implemented
136                format!("{}", indicator);
137            }
138        };
139    }
140    test_indicator!(EfficiencyRatio);
141
142    #[test]
143    fn test_new() {
144        assert!(EfficiencyRatio::new(0).is_err());
145        assert!(EfficiencyRatio::new(1).is_ok());
146    }
147
148    #[test]
149    fn test_next_f64() {
150        let mut er = EfficiencyRatio::new(3).unwrap();
151
152        assert_eq!(round(er.next(3.0)), 1.0);
153        assert_eq!(round(er.next(5.0)), 1.0);
154        assert_eq!(round(er.next(2.0)), 0.2);
155        assert_eq!(round(er.next(3.0)), 0.0);
156        assert_eq!(round(er.next(1.0)), 0.667);
157        assert_eq!(round(er.next(3.0)), 0.2);
158        assert_eq!(round(er.next(4.0)), 0.2);
159        assert_eq!(round(er.next(6.0)), 1.0);
160
161        er.reset();
162        assert_eq!(round(er.next(3.0)), 1.0);
163        assert_eq!(round(er.next(5.0)), 1.0);
164        assert_eq!(round(er.next(2.0)), 0.2);
165        assert_eq!(round(er.next(3.0)), 0.0);
166    }
167
168    #[test]
169    fn test_display() {
170        let er = EfficiencyRatio::new(17).unwrap();
171        assert_eq!(format!("{}", er), "ER(17)");
172    }
173}