finance_query/indicators/
stochastic.rs1use std::collections::VecDeque;
4
5use super::{IndicatorError, Result, sma::sma_raw};
6use serde::{Deserialize, Serialize};
7
8#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
10pub struct StochasticResult {
11 pub k: Vec<Option<f64>>,
13 pub d: Vec<Option<f64>>,
15}
16
17pub fn stochastic(
44 highs: &[f64],
45 lows: &[f64],
46 closes: &[f64],
47 k_period: usize,
48 k_slow: usize,
49 d_period: usize,
50) -> Result<StochasticResult> {
51 if k_period == 0 || k_slow == 0 || d_period == 0 {
52 return Err(IndicatorError::InvalidPeriod(
53 "Periods must be greater than 0".to_string(),
54 ));
55 }
56 let len = highs.len();
57 if lows.len() != len || closes.len() != len {
58 return Err(IndicatorError::InvalidPeriod(
59 "Data lengths must match".to_string(),
60 ));
61 }
62 if len < k_period {
63 return Err(IndicatorError::InsufficientData {
64 need: k_period,
65 got: len,
66 });
67 }
68
69 let mut raw_k = vec![None; len];
71 let mut raw_k_for_sma = vec![0.0; len];
72 {
73 let mut max_deque: VecDeque<usize> = VecDeque::new(); let mut min_deque: VecDeque<usize> = VecDeque::new(); for i in 0..len {
77 while max_deque.front().is_some_and(|&j| j + k_period <= i) {
79 max_deque.pop_front();
80 }
81 while min_deque.front().is_some_and(|&j| j + k_period <= i) {
82 min_deque.pop_front();
83 }
84 while max_deque.back().is_some_and(|&j| highs[j] <= highs[i]) {
86 max_deque.pop_back();
87 }
88 while min_deque.back().is_some_and(|&j| lows[j] >= lows[i]) {
90 min_deque.pop_back();
91 }
92 max_deque.push_back(i);
93 min_deque.push_back(i);
94
95 if i + 1 >= k_period {
96 let highest = highs[*max_deque.front().unwrap()];
97 let lowest = lows[*min_deque.front().unwrap()];
98 let k = if (highest - lowest).abs() < f64::EPSILON {
99 50.0 } else {
101 ((closes[i] - lowest) / (highest - lowest)) * 100.0
102 };
103 raw_k[i] = Some(k);
104 raw_k_for_sma[i] = k;
105 }
106 }
107 }
108
109 let raw_k_valid_start = k_period - 1;
112 let slow_dense: Vec<f64>;
113 let (slow_k, slow_k_valid_start) = if k_slow == 1 {
114 slow_dense = raw_k_for_sma[raw_k_valid_start..].to_vec();
115 (raw_k.clone(), raw_k_valid_start)
116 } else {
117 let raw_k_slice = &raw_k_for_sma[raw_k_valid_start..];
118 slow_dense = sma_raw(raw_k_slice, k_slow); let slow_valid_start = raw_k_valid_start + k_slow - 1;
120
121 let mut slow_k = vec![None; len];
122 for (j, &val) in slow_dense.iter().enumerate() {
123 let idx = j + slow_valid_start;
124 if idx < len {
125 slow_k[idx] = Some(val);
126 }
127 }
128 (slow_k, slow_valid_start)
129 };
130
131 let d_raw = sma_raw(&slow_dense, d_period);
133 let d_off = slow_k_valid_start + d_period - 1;
134 let mut d_values = vec![None; len];
135 for (j, &val) in d_raw.iter().enumerate() {
136 let idx = j + d_off;
137 if idx < len {
138 d_values[idx] = Some(val);
139 }
140 }
141
142 Ok(StochasticResult {
143 k: slow_k,
144 d: d_values,
145 })
146}
147
148#[cfg(test)]
149mod tests {
150 use super::*;
151
152 #[test]
153 fn test_stochastic_no_k_slow() {
154 let highs = vec![10.0, 11.0, 12.0, 13.0, 14.0];
155 let lows = vec![8.0, 9.0, 10.0, 11.0, 12.0];
156 let closes = vec![9.0, 10.0, 11.0, 12.0, 13.0];
157 let result = stochastic(&highs, &lows, &closes, 3, 1, 2).unwrap();
158
159 assert_eq!(result.k.len(), 5);
160 assert_eq!(result.d.len(), 5);
161
162 assert!(result.k[0].is_none());
164 assert!(result.k[1].is_none());
165 assert!(result.k[2].is_some());
166
167 assert!(result.d[0].is_none());
169 assert!(result.d[1].is_none());
170 assert!(result.d[2].is_none());
171 assert!(result.d[3].is_some());
172 }
173
174 #[test]
175 fn test_stochastic_with_k_slow() {
176 let highs = vec![10.0; 10];
177 let lows = vec![8.0; 10];
178 let closes = vec![9.0; 10];
179 let result = stochastic(&highs, &lows, &closes, 3, 3, 3).unwrap();
181 assert!(result.k[3].is_none());
183 assert!(result.k[4].is_some());
184 assert!(result.d[5].is_none());
185 assert!(result.d[6].is_some());
186 }
187
188 #[test]
189 fn test_stochastic_k_slow_produces_different_k_than_no_slow() {
190 let closes: Vec<f64> = (0..20)
193 .map(|i| if i % 2 == 0 { 10.0 } else { 20.0 })
194 .collect();
195 let highs: Vec<f64> = closes.iter().map(|&c| c + 0.5).collect();
196 let lows: Vec<f64> = closes.iter().map(|&c| c - 0.5).collect();
197
198 let fast = stochastic(&highs, &lows, &closes, 5, 1, 3).unwrap();
200 let slow = stochastic(&highs, &lows, &closes, 5, 3, 3).unwrap();
202
203 let idx = 10;
205 assert!(fast.k[idx].is_some());
206 assert!(slow.k[idx].is_some());
207 assert_ne!(fast.k[idx], slow.k[idx]);
209 }
210}