cxmr_ta_core/indicators/
fast_stochastic.rs

1use std::fmt;
2
3use crate::errors::*;
4use crate::indicators::{Maximum, Minimum};
5use crate::{Calculate, 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 ta::indicators::FastStochastic;
32/// use ta::{Calculate, Next};
33///
34/// let mut stoch = FastStochastic::new(5).unwrap();
35/// assert_eq!(stoch.calc(20.0), 50.0);
36/// assert_eq!(stoch.calc(30.0), 100.0);
37/// assert_eq!(stoch.calc(40.0), 100.0);
38/// assert_eq!(stoch.calc(35.0), 75.0);
39/// assert_eq!(stoch.calc(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 Calculate for FastStochastic {
64    fn calc(&mut self, input: f64) -> f64 {
65        let min = self.minimum.calc(input);
66        let max = self.maximum.calc(input);
67
68        if min == max {
69            // When only 1 input was given, than min and max are the same,
70            // therefore it makes sense to return 50
71            50.0
72        } else {
73            (input - min) / (max - min) * 100.0
74        }
75    }
76}
77
78impl<T: High + Low + Close> Next<T> for FastStochastic {
79    fn next(&mut self, input: &T) -> f64 {
80        let highest = self.maximum.calc(input.high());
81        let lowest = self.minimum.calc(input.low());
82        let close = input.close();
83
84        if highest == lowest {
85            // To avoid division by zero, return 50.0
86            50.0
87        } else {
88            (close - lowest) / (highest - lowest) * 100.0
89        }
90    }
91}
92
93impl Reset for FastStochastic {
94    fn reset(&mut self) {
95        self.minimum.reset();
96        self.maximum.reset();
97    }
98}
99
100impl Default for FastStochastic {
101    fn default() -> Self {
102        Self::new(14).unwrap()
103    }
104}
105
106impl fmt::Display for FastStochastic {
107    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
108        write!(f, "FAST_STOCH({})", self.length)
109    }
110}
111
112#[cfg(test)]
113mod tests {
114    use super::*;
115    use crate::test_helper::*;
116
117    test_indicator!(FastStochastic);
118
119    #[test]
120    fn test_new() {
121        assert!(FastStochastic::new(0).is_err());
122        assert!(FastStochastic::new(1).is_ok());
123    }
124
125    #[test]
126    fn test_next_with_f64() {
127        let mut stoch = FastStochastic::new(3).unwrap();
128        assert_eq!(stoch.calc(0.0), 50.0);
129        assert_eq!(stoch.calc(200.0), 100.0);
130        assert_eq!(stoch.calc(100.0), 50.0);
131        assert_eq!(stoch.calc(120.0), 20.0);
132        assert_eq!(stoch.calc(115.0), 75.0);
133    }
134
135    #[test]
136    fn test_next_with_bars() {
137        let test_data = vec![
138            // high, low , close, expected
139            (20.0, 20.0, 20.0, 50.0), // min = 20, max = 20
140            (30.0, 10.0, 25.0, 75.0), // min = 10, max = 30
141            (40.0, 20.0, 16.0, 20.0), // min = 10, max = 40
142            (35.0, 15.0, 19.0, 30.0), // min = 10, max = 40
143            (30.0, 20.0, 25.0, 40.0), // min = 15, max = 40
144            (35.0, 25.0, 30.0, 75.0), // min = 15, max = 35
145        ];
146
147        let mut stoch = FastStochastic::new(3).unwrap();
148
149        for (high, low, close, expected) in test_data {
150            let input_bar = Bar::new().high(high).low(low).close(close);
151            assert_eq!(stoch.next(&input_bar), expected);
152        }
153    }
154
155    #[test]
156    fn test_reset() {
157        let mut indicator = FastStochastic::new(10).unwrap();
158        assert_eq!(indicator.calc(10.0), 50.0);
159        assert_eq!(indicator.calc(210.0), 100.0);
160        assert_eq!(indicator.calc(10.0), 0.0);
161        assert_eq!(indicator.calc(60.0), 25.0);
162
163        indicator.reset();
164        assert_eq!(indicator.calc(10.0), 50.0);
165        assert_eq!(indicator.calc(20.0), 100.0);
166        assert_eq!(indicator.calc(12.5), 25.0);
167    }
168
169    #[test]
170    fn test_default() {
171        FastStochastic::default();
172    }
173
174    #[test]
175    fn test_display() {
176        let indicator = FastStochastic::new(21).unwrap();
177        assert_eq!(format!("{}", indicator), "FAST_STOCH(21)");
178    }
179}