quantaxis_rs/indicators/
fast_stochastic.rs

1use std::fmt;
2
3use crate::errors::*;
4use crate::indicators::{Maximum, Minimum};
5use crate::{Close, High, Low, Next, Reset};
6
7/// Fast stochastic oscillator.
8///
9/// The stochastic oscillator is a momentum indicator comparing the closing price
10/// of a security to the range of its prices over a certain period of time.
11///
12/// # Formula
13///
14/// ![Fast stochastic oscillator formula](https://wikimedia.org/api/rest_v1/media/math/render/svg/5a419041034a8044308c999f85661a08bcf91b1d)
15///
16/// Where:
17///
18/// * \%K<sub>t</sub> - value of fast stochastic oscillator
19/// * C<sub>t</sub> - close price of the current period
20/// * L<sub>n</sub> - lowest price for the last _n_ periods
21/// * H<sub>n</sub> - highest price for the last _n_ periods
22///
23///
24/// # Parameters
25///
26/// * _length_ - number of periods (integer greater than 0). Default is 14.
27///
28/// # Example
29///
30/// ```
31/// use quantaxis_rs::indicators::FastStochastic;
32/// use quantaxis_rs::Next;
33///
34/// let mut stoch = FastStochastic::new(5).unwrap();
35/// assert_eq!(stoch.next(20.0), 50.0);
36/// assert_eq!(stoch.next(30.0), 100.0);
37/// assert_eq!(stoch.next(40.0), 100.0);
38/// assert_eq!(stoch.next(35.0), 75.0);
39/// assert_eq!(stoch.next(15.0), 0.0);
40/// ```
41#[derive(Debug, Clone)]
42pub struct FastStochastic {
43    length: u32,
44    minimum: Minimum,
45    maximum: Maximum,
46}
47
48impl FastStochastic {
49    pub fn new(length: u32) -> Result<Self> {
50        let indicator = Self {
51            length: length,
52            minimum: Minimum::new(length)?,
53            maximum: Maximum::new(length)?,
54        };
55        Ok(indicator)
56    }
57
58    pub fn length(&self) -> u32 {
59        self.length
60    }
61}
62
63impl Next<f64> for FastStochastic {
64    type Output = f64;
65
66    fn next(&mut self, input: f64) -> Self::Output {
67        let min = self.minimum.next(input);
68        let max = self.maximum.next(input);
69
70        if min == max {
71            // When only 1 input was given, than min and max are the same,
72            // therefore it makes sense to return 50
73            50.0
74        } else {
75            (input - min) / (max - min) * 100.0
76        }
77    }
78}
79
80impl<'a, T: High + Low + Close> Next<&'a T> for FastStochastic {
81    type Output = f64;
82
83    fn next(&mut self, input: &'a T) -> Self::Output {
84        let highest = self.maximum.next(input.high());
85        let lowest = self.minimum.next(input.low());
86        let close = input.close();
87
88        if highest == lowest {
89            // To avoid division by zero, return 50.0
90            50.0
91        } else {
92            (close - lowest) / (highest - lowest) * 100.0
93        }
94    }
95}
96
97impl Reset for FastStochastic {
98    fn reset(&mut self) {
99        self.minimum.reset();
100        self.maximum.reset();
101    }
102}
103
104impl Default for FastStochastic {
105    fn default() -> Self {
106        Self::new(14).unwrap()
107    }
108}
109
110impl fmt::Display for FastStochastic {
111    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
112        write!(f, "FAST_STOCH({})", self.length)
113    }
114}
115
116#[cfg(test)]
117mod tests {
118    use super::*;
119    use crate::test_helper::*;
120    macro_rules! test_indicator {
121        ($i:tt) => {
122            #[test]
123            fn test_indicator() {
124                let bar = Bar::new();
125
126                // ensure Default trait is implemented
127                let mut indicator = $i::default();
128
129                // ensure Next<f64> is implemented
130                let first_output = indicator.next(12.3);
131
132                // ensure next accepts &DataItem as well
133                indicator.next(&bar);
134
135                // ensure Reset is implemented and works correctly
136                indicator.reset();
137                assert_eq!(indicator.next(12.3), first_output);
138
139                // ensure Display is implemented
140                format!("{}", indicator);
141            }
142        };
143    }
144    test_indicator!(FastStochastic);
145
146    #[test]
147    fn test_new() {
148        assert!(FastStochastic::new(0).is_err());
149        assert!(FastStochastic::new(1).is_ok());
150    }
151
152    #[test]
153    fn test_next_with_f64() {
154        let mut stoch = FastStochastic::new(3).unwrap();
155        assert_eq!(stoch.next(0.0), 50.0);
156        assert_eq!(stoch.next(200.0), 100.0);
157        assert_eq!(stoch.next(100.0), 50.0);
158        assert_eq!(stoch.next(120.0), 20.0);
159        assert_eq!(stoch.next(115.0), 75.0);
160    }
161
162    #[test]
163    fn test_next_with_bars() {
164        let test_data = vec![
165            // high, low , close, expected
166            (20.0, 20.0, 20.0, 50.0), // min = 20, max = 20
167            (30.0, 10.0, 25.0, 75.0), // min = 10, max = 30
168            (40.0, 20.0, 16.0, 20.0), // min = 10, max = 40
169            (35.0, 15.0, 19.0, 30.0), // min = 10, max = 40
170            (30.0, 20.0, 25.0, 40.0), // min = 15, max = 40
171            (35.0, 25.0, 30.0, 75.0), // min = 15, max = 35
172        ];
173
174        let mut stoch = FastStochastic::new(3).unwrap();
175
176        for (high, low, close, expected) in test_data {
177            let input_bar = Bar::new().high(high).low(low).close(close);
178            assert_eq!(stoch.next(&input_bar), expected);
179        }
180    }
181
182    #[test]
183    fn test_reset() {
184        let mut indicator = FastStochastic::new(10).unwrap();
185        assert_eq!(indicator.next(10.0), 50.0);
186        assert_eq!(indicator.next(210.0), 100.0);
187        assert_eq!(indicator.next(10.0), 0.0);
188        assert_eq!(indicator.next(60.0), 25.0);
189
190        indicator.reset();
191        assert_eq!(indicator.next(10.0), 50.0);
192        assert_eq!(indicator.next(20.0), 100.0);
193        assert_eq!(indicator.next(12.5), 25.0);
194    }
195
196    #[test]
197    fn test_default() {
198        FastStochastic::default();
199    }
200
201    #[test]
202    fn test_display() {
203        let indicator = FastStochastic::new(21).unwrap();
204        assert_eq!(format!("{}", indicator), "FAST_STOCH(21)");
205    }
206}