finlib_ta/indicators/
fast_stochastic.rs

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