nautilus_indicators/momentum/
stochastics.rs1use std::fmt::Display;
17
18use arraydeque::{ArrayDeque, Wrapping};
19use nautilus_model::data::Bar;
20
21use crate::indicator::Indicator;
22
23const MAX_PERIOD: usize = 1_024;
24
25#[repr(C)]
26#[derive(Debug)]
27#[cfg_attr(
28 feature = "python",
29 pyo3::pyclass(module = "posei_trader.core.nautilus_pyo3.indicators")
30)]
31pub struct Stochastics {
32 pub period_k: usize,
33 pub period_d: usize,
34 pub value_k: f64,
35 pub value_d: f64,
36 pub initialized: bool,
37 has_inputs: bool,
38 highs: ArrayDeque<f64, MAX_PERIOD, Wrapping>,
39 lows: ArrayDeque<f64, MAX_PERIOD, Wrapping>,
40 c_sub_1: ArrayDeque<f64, MAX_PERIOD, Wrapping>,
41 h_sub_l: ArrayDeque<f64, MAX_PERIOD, Wrapping>,
42}
43
44impl Display for Stochastics {
45 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
46 write!(f, "{}({},{})", self.name(), self.period_k, self.period_d,)
47 }
48}
49
50impl Indicator for Stochastics {
51 fn name(&self) -> String {
52 stringify!(Stochastics).to_string()
53 }
54
55 fn has_inputs(&self) -> bool {
56 self.has_inputs
57 }
58
59 fn initialized(&self) -> bool {
60 self.initialized
61 }
62
63 fn handle_bar(&mut self, bar: &Bar) {
64 self.update_raw((&bar.high).into(), (&bar.low).into(), (&bar.close).into());
65 }
66
67 fn reset(&mut self) {
68 self.highs.clear();
69 self.lows.clear();
70 self.c_sub_1.clear();
71 self.h_sub_l.clear();
72 self.value_k = 0.0;
73 self.value_d = 0.0;
74 self.has_inputs = false;
75 self.initialized = false;
76 }
77}
78
79impl Stochastics {
80 #[must_use]
87 pub fn new(period_k: usize, period_d: usize) -> Self {
88 assert!(
89 period_k > 0 && period_k <= MAX_PERIOD,
90 "Stochastics: period_k {period_k} exceeds bounds (1..={MAX_PERIOD})"
91 );
92 assert!(
93 period_d > 0 && period_d <= MAX_PERIOD,
94 "Stochastics: period_d {period_d} exceeds bounds (1..={MAX_PERIOD})"
95 );
96
97 Self {
98 period_k,
99 period_d,
100 has_inputs: false,
101 initialized: false,
102 value_k: 0.0,
103 value_d: 0.0,
104 highs: ArrayDeque::new(),
105 lows: ArrayDeque::new(),
106 h_sub_l: ArrayDeque::new(),
107 c_sub_1: ArrayDeque::new(),
108 }
109 }
110
111 pub fn update_raw(&mut self, high: f64, low: f64, close: f64) {
112 if !self.has_inputs {
113 self.has_inputs = true;
114 }
115
116 if self.highs.len() == self.period_k {
117 self.highs.pop_front();
118 self.lows.pop_front();
119 }
120 let _ = self.highs.push_back(high);
121 let _ = self.lows.push_back(low);
122
123 if !self.initialized
124 && self.highs.len() == self.period_k
125 && self.lows.len() == self.period_k
126 {
127 self.initialized = true;
128 }
129
130 let k_max_high = self.highs.iter().copied().fold(f64::NEG_INFINITY, f64::max);
131 let k_min_low = self.lows.iter().copied().fold(f64::INFINITY, f64::min);
132
133 if self.c_sub_1.len() == self.period_d {
134 self.c_sub_1.pop_front();
135 self.h_sub_l.pop_front();
136 }
137 let _ = self.c_sub_1.push_back(close - k_min_low);
138 let _ = self.h_sub_l.push_back(k_max_high - k_min_low);
139
140 if k_max_high == k_min_low {
141 return;
142 }
143
144 self.value_k = 100.0 * ((close - k_min_low) / (k_max_high - k_min_low));
145 self.value_d =
146 100.0 * (self.c_sub_1.iter().sum::<f64>() / self.h_sub_l.iter().sum::<f64>());
147 }
148}
149
150#[cfg(test)]
154mod tests {
155 use nautilus_model::data::Bar;
156 use rstest::rstest;
157
158 use crate::{
159 indicator::Indicator,
160 momentum::stochastics::Stochastics,
161 stubs::{bar_ethusdt_binance_minute_bid, stochastics_10},
162 };
163
164 #[rstest]
165 fn test_stochastics_initialized(stochastics_10: Stochastics) {
166 let display_str = format!("{stochastics_10}");
167 assert_eq!(display_str, "Stochastics(10,10)");
168 assert_eq!(stochastics_10.period_d, 10);
169 assert_eq!(stochastics_10.period_k, 10);
170 assert!(!stochastics_10.initialized);
171 assert!(!stochastics_10.has_inputs);
172 }
173
174 #[rstest]
175 fn test_value_with_one_input(mut stochastics_10: Stochastics) {
176 stochastics_10.update_raw(1.0, 1.0, 1.0);
177 assert_eq!(stochastics_10.value_d, 0.0);
178 assert_eq!(stochastics_10.value_k, 0.0);
179 }
180
181 #[rstest]
182 fn test_value_with_three_inputs(mut stochastics_10: Stochastics) {
183 stochastics_10.update_raw(1.0, 1.0, 1.0);
184 stochastics_10.update_raw(2.0, 2.0, 2.0);
185 stochastics_10.update_raw(3.0, 3.0, 3.0);
186 assert_eq!(stochastics_10.value_d, 100.0);
187 assert_eq!(stochastics_10.value_k, 100.0);
188 }
189
190 #[rstest]
191 fn test_value_with_ten_inputs(mut stochastics_10: Stochastics) {
192 let high_values = [
193 1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0, 10.0, 11.0, 12.0, 13.0, 14.0, 15.0,
194 ];
195 let low_values = [
196 0.9, 1.9, 2.9, 3.9, 4.9, 5.9, 6.9, 7.9, 8.9, 9.9, 10.1, 10.2, 10.3, 11.1, 11.4,
197 ];
198 let close_values = [
199 1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0, 10.0, 11.0, 12.0, 13.0, 14.0, 15.0,
200 ];
201
202 for i in 0..15 {
203 stochastics_10.update_raw(high_values[i], low_values[i], close_values[i]);
204 }
205
206 assert!(stochastics_10.initialized());
207 assert_eq!(stochastics_10.value_d, 100.0);
208 assert_eq!(stochastics_10.value_k, 100.0);
209 }
210
211 #[rstest]
212 fn test_initialized_with_required_input(mut stochastics_10: Stochastics) {
213 for i in 1..10 {
214 stochastics_10.update_raw(f64::from(i), f64::from(i), f64::from(i));
215 }
216 assert!(!stochastics_10.initialized);
217 stochastics_10.update_raw(10.0, 12.0, 14.0);
218 assert!(stochastics_10.initialized);
219 }
220
221 #[rstest]
222 fn test_handle_bar(mut stochastics_10: Stochastics, bar_ethusdt_binance_minute_bid: Bar) {
223 stochastics_10.handle_bar(&bar_ethusdt_binance_minute_bid);
224 assert_eq!(stochastics_10.value_d, 49.090_909_090_909_09);
225 assert_eq!(stochastics_10.value_k, 49.090_909_090_909_09);
226 assert!(stochastics_10.has_inputs);
227 assert!(!stochastics_10.initialized);
228 }
229
230 #[rstest]
231 fn test_reset(mut stochastics_10: Stochastics) {
232 stochastics_10.update_raw(1.0, 1.0, 1.0);
233 assert_eq!(stochastics_10.c_sub_1.len(), 1);
234 assert_eq!(stochastics_10.h_sub_l.len(), 1);
235
236 stochastics_10.reset();
237 assert_eq!(stochastics_10.value_d, 0.0);
238 assert_eq!(stochastics_10.value_k, 0.0);
239 assert_eq!(stochastics_10.h_sub_l.len(), 0);
240 assert_eq!(stochastics_10.c_sub_1.len(), 0);
241 assert!(!stochastics_10.has_inputs);
242 assert!(!stochastics_10.initialized);
243 }
244}