finance_query/indicators/
stochastic.rs1use super::{IndicatorError, Result, sma::sma};
4use serde::{Deserialize, Serialize};
5
6#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
8pub struct StochasticResult {
9 pub k: Vec<Option<f64>>,
11 pub d: Vec<Option<f64>>,
13}
14
15pub fn stochastic(
42 highs: &[f64],
43 lows: &[f64],
44 closes: &[f64],
45 k_period: usize,
46 k_slow: usize,
47 d_period: usize,
48) -> Result<StochasticResult> {
49 if k_period == 0 || k_slow == 0 || d_period == 0 {
50 return Err(IndicatorError::InvalidPeriod(
51 "Periods must be greater than 0".to_string(),
52 ));
53 }
54 let len = highs.len();
55 if lows.len() != len || closes.len() != len {
56 return Err(IndicatorError::InvalidPeriod(
57 "Data lengths must match".to_string(),
58 ));
59 }
60 if len < k_period {
61 return Err(IndicatorError::InsufficientData {
62 need: k_period,
63 got: len,
64 });
65 }
66
67 let mut raw_k = vec![None; len];
69 let mut raw_k_for_sma = vec![0.0; len];
70
71 for i in (k_period - 1)..len {
72 let start = i + 1 - k_period;
73 let highest = highs[start..=i]
74 .iter()
75 .fold(f64::NEG_INFINITY, |a, &b| a.max(b));
76 let lowest = lows[start..=i].iter().fold(f64::INFINITY, |a, &b| a.min(b));
77 let k = if (highest - lowest).abs() < f64::EPSILON {
78 50.0 } else {
80 ((closes[i] - lowest) / (highest - lowest)) * 100.0
81 };
82 raw_k[i] = Some(k);
83 raw_k_for_sma[i] = k;
84 }
85
86 let raw_k_valid_start = k_period - 1;
88 let (slow_k, slow_k_valid_start) = if k_slow == 1 {
89 (raw_k.clone(), raw_k_valid_start)
91 } else {
92 let raw_k_slice = &raw_k_for_sma[raw_k_valid_start..];
93 let smoothed = sma(raw_k_slice, k_slow);
94 let slow_valid_start = raw_k_valid_start + k_slow - 1;
95
96 let mut slow_k = vec![None; len];
97 for (j, val) in smoothed.into_iter().enumerate() {
98 let idx = j + raw_k_valid_start;
99 if idx < len {
100 slow_k[idx] = val;
101 }
102 }
103 (slow_k, slow_valid_start)
104 };
105
106 let slow_k_values: Vec<f64> = slow_k.iter().map(|v| v.unwrap_or(0.0)).collect();
108 let slow_k_slice = &slow_k_values[slow_k_valid_start..];
109 let d_smoothed = sma(slow_k_slice, d_period);
110
111 let mut d_values = vec![None; len];
112 for (j, val) in d_smoothed.into_iter().enumerate() {
113 let idx = j + slow_k_valid_start;
114 if idx < len {
115 d_values[idx] = val;
116 }
117 }
118
119 Ok(StochasticResult {
120 k: slow_k,
121 d: d_values,
122 })
123}
124
125#[cfg(test)]
126mod tests {
127 use super::*;
128
129 #[test]
130 fn test_stochastic_no_k_slow() {
131 let highs = vec![10.0, 11.0, 12.0, 13.0, 14.0];
132 let lows = vec![8.0, 9.0, 10.0, 11.0, 12.0];
133 let closes = vec![9.0, 10.0, 11.0, 12.0, 13.0];
134 let result = stochastic(&highs, &lows, &closes, 3, 1, 2).unwrap();
135
136 assert_eq!(result.k.len(), 5);
137 assert_eq!(result.d.len(), 5);
138
139 assert!(result.k[0].is_none());
141 assert!(result.k[1].is_none());
142 assert!(result.k[2].is_some());
143
144 assert!(result.d[0].is_none());
146 assert!(result.d[1].is_none());
147 assert!(result.d[2].is_none());
148 assert!(result.d[3].is_some());
149 }
150
151 #[test]
152 fn test_stochastic_with_k_slow() {
153 let highs = vec![10.0; 10];
154 let lows = vec![8.0; 10];
155 let closes = vec![9.0; 10];
156 let result = stochastic(&highs, &lows, &closes, 3, 3, 3).unwrap();
158 assert!(result.k[3].is_none());
160 assert!(result.k[4].is_some());
161 assert!(result.d[5].is_none());
162 assert!(result.d[6].is_some());
163 }
164
165 #[test]
166 fn test_stochastic_k_slow_produces_different_k_than_no_slow() {
167 let closes: Vec<f64> = (0..20)
170 .map(|i| if i % 2 == 0 { 10.0 } else { 20.0 })
171 .collect();
172 let highs: Vec<f64> = closes.iter().map(|&c| c + 0.5).collect();
173 let lows: Vec<f64> = closes.iter().map(|&c| c - 0.5).collect();
174
175 let fast = stochastic(&highs, &lows, &closes, 5, 1, 3).unwrap();
177 let slow = stochastic(&highs, &lows, &closes, 5, 3, 3).unwrap();
179
180 let idx = 10;
182 assert!(fast.k[idx].is_some());
183 assert!(slow.k[idx].is_some());
184 assert_ne!(fast.k[idx], slow.k[idx]);
186 }
187}