indexes_rs/v2/stochastic/
main.rs1use crate::v2::stochastic::types::StochasticSignal;
2
3use super::types::{OHLCData, StochasticError, StochasticResult};
4use std::collections::VecDeque;
5
6pub struct StochasticOscillator {
16 period: usize, k_smooth: usize, d_period: usize, highs: VecDeque<f64>, lows: VecDeque<f64>, closes: VecDeque<f64>, k_values: VecDeque<f64>, raw_k_values: VecDeque<f64>, }
25
26impl StochasticOscillator {
27 pub fn new(period: usize, k_smooth: usize, d_period: usize) -> Result<Self, StochasticError> {
34 if period == 0 {
35 return Err(StochasticError::InvalidPeriod);
36 }
37 if k_smooth == 0 || d_period == 0 {
38 return Err(StochasticError::InvalidSmoothingPeriod);
39 }
40
41 Ok(Self {
42 period,
43 k_smooth,
44 d_period,
45 highs: VecDeque::with_capacity(period),
46 lows: VecDeque::with_capacity(period),
47 closes: VecDeque::with_capacity(k_smooth),
48 k_values: VecDeque::with_capacity(d_period),
49 raw_k_values: VecDeque::with_capacity(k_smooth),
50 })
51 }
52
53 pub fn default() -> Self {
55 Self::new(14, 3, 3).unwrap()
56 }
57
58 pub fn update(&mut self, data: OHLCData) -> Option<StochasticResult> {
60 if !self.validate_ohlc(&data) {
62 return None;
63 }
64
65 self.add_to_windows(data.high, data.low, data.close);
67
68 if self.highs.len() >= self.period {
70 self.calculate()
71 } else {
72 None
73 }
74 }
75
76 pub fn update_hlc(&mut self, high: f64, low: f64, close: f64) -> Option<StochasticResult> {
78 self.update(OHLCData::new(high, low, close))
79 }
80
81 pub fn value(&self) -> Option<StochasticResult> {
83 if self.k_values.is_empty() {
84 return None;
85 }
86
87 let k = *self.k_values.back()?;
88 let d = if self.k_values.len() >= self.d_period {
89 self.k_values.iter().rev().take(self.d_period).sum::<f64>() / self.d_period as f64
90 } else {
91 self.k_values.iter().sum::<f64>() / self.k_values.len() as f64
92 };
93
94 Some(StochasticResult { k, d })
95 }
96
97 pub fn reset(&mut self) {
99 self.highs.clear();
100 self.lows.clear();
101 self.closes.clear();
102 self.k_values.clear();
103 self.raw_k_values.clear();
104 }
105
106 pub fn is_ready(&self) -> bool {
108 self.highs.len() >= self.period && !self.k_values.is_empty()
109 }
110
111 pub fn period(&self) -> usize {
113 self.period
114 }
115
116 pub fn calculate_batch(period: usize, k_smooth: usize, d_period: usize, data: &[OHLCData]) -> Result<Vec<Option<StochasticResult>>, StochasticError> {
118 let mut stoch = Self::new(period, k_smooth, d_period)?;
119 let mut results = Vec::with_capacity(data.len());
120
121 for ohlc in data {
122 results.push(stoch.update(*ohlc));
123 }
124
125 Ok(results)
126 }
127
128 pub fn fast(period: usize) -> Result<Self, StochasticError> {
130 Self::new(period, 1, 3)
131 }
132
133 pub fn slow(period: usize) -> Result<Self, StochasticError> {
135 Self::new(period, 3, 3)
136 }
137
138 pub fn full(period: usize, k_smooth: usize, d_period: usize) -> Result<Self, StochasticError> {
140 Self::new(period, k_smooth, d_period)
141 }
142
143 fn validate_ohlc(&self, data: &OHLCData) -> bool {
146 if data.high.is_nan() || data.low.is_nan() || data.close.is_nan() {
148 return false;
149 }
150 if data.high.is_infinite() || data.low.is_infinite() || data.close.is_infinite() {
151 return false;
152 }
153 if data.high < data.low {
155 return false;
156 }
157 let tolerance = 0.0001;
159 if data.close > data.high + tolerance || data.close < data.low - tolerance {
160 return false;
161 }
162 true
163 }
164
165 fn add_to_windows(&mut self, high: f64, low: f64, close: f64) {
166 if self.highs.len() >= self.period {
168 self.highs.pop_front();
169 }
170 self.highs.push_back(high);
171
172 if self.lows.len() >= self.period {
174 self.lows.pop_front();
175 }
176 self.lows.push_back(low);
177
178 if self.closes.len() >= self.k_smooth {
180 self.closes.pop_front();
181 }
182 self.closes.push_back(close);
183 }
184
185 fn calculate(&mut self) -> Option<StochasticResult> {
186 let highest_high = self.highs.iter().fold(f64::MIN, |a, &b| a.max(b));
188 let lowest_low = self.lows.iter().fold(f64::MAX, |a, &b| a.min(b));
189
190 let range = highest_high - lowest_low;
192 let raw_k = if range > 0.0 {
193 let current_close = *self.closes.back()?;
194 ((current_close - lowest_low) / range) * 100.0
195 } else {
196 50.0 };
198
199 if self.raw_k_values.len() >= self.k_smooth {
201 self.raw_k_values.pop_front();
202 }
203 self.raw_k_values.push_back(raw_k);
204
205 if self.raw_k_values.len() >= self.k_smooth {
207 let smoothed_k = self.raw_k_values.iter().sum::<f64>() / self.k_smooth as f64;
208
209 if self.k_values.len() >= self.d_period {
211 self.k_values.pop_front();
212 }
213 self.k_values.push_back(smoothed_k);
214
215 let d = if self.k_values.len() >= self.d_period {
217 self.k_values.iter().sum::<f64>() / self.d_period as f64
218 } else {
219 self.k_values.iter().sum::<f64>() / self.k_values.len() as f64
220 };
221
222 Some(StochasticResult { k: smoothed_k, d })
223 } else {
224 None
225 }
226 }
227
228 pub fn signal(&self) -> Option<StochasticSignal> {
230 let result = self.value()?;
231
232 if result.k > 80.0 && result.d > 80.0 {
233 Some(StochasticSignal::Overbought)
234 } else if result.k < 20.0 && result.d < 20.0 {
235 Some(StochasticSignal::Oversold)
236 } else if result.k > result.d {
237 Some(StochasticSignal::Bullish)
238 } else if result.k < result.d {
239 Some(StochasticSignal::Bearish)
240 } else {
241 Some(StochasticSignal::Neutral)
242 }
243 }
244}