egui_charts/studies/builtin/
stochastic.rs1use super::over_finite_bars;
25use crate::model::Bar;
26use crate::studies::helpers;
27use crate::studies::palette;
28use crate::studies::stepper::{rolling_max, rolling_min, update_window, usable_behind};
29use crate::studies::{Indicator, IndicatorValue, IndicatorValues};
30use egui::Color32;
31
32#[derive(Clone)]
34pub struct Stochastic {
35 k_period: usize,
36 k_smooth: usize,
37 d_period: usize,
38 values: IndicatorValues,
39 k_color: Color32,
40 d_color: Color32,
41 visible: bool,
42}
43
44impl Stochastic {
45 pub fn new(k_period: usize, k_smooth: usize, d_period: usize) -> Self {
52 Self {
53 k_period: k_period.max(1),
54 k_smooth: k_smooth.max(1),
55 d_period: d_period.max(1),
56 values: IndicatorValues::new(),
57 k_color: palette::BLUE,
58 d_color: palette::ORANGE,
59 visible: true,
60 }
61 }
62
63 pub fn default_params() -> Self {
65 Self::new(14, 3, 3)
66 }
67
68 pub fn with_colors(mut self, k_color: Color32, d_color: Color32) -> Self {
70 self.k_color = k_color;
71 self.d_color = d_color;
72 self
73 }
74}
75
76impl Default for Stochastic {
78 fn default() -> Self {
79 Self::new(14, 3, 3)
80 }
81}
82
83impl Stochastic {
84 fn compute(&self, data: &[Bar]) -> IndicatorValues {
86 let mut values = IndicatorValues::new();
87 let min_period = self.k_period.saturating_add(self.k_smooth) - 1;
88 if data.len() < min_period {
89 for _ in 0..data.len() {
90 values.push(IndicatorValue::None);
91 }
92 return values;
93 }
94
95 let highest = rolling_max(data.iter().map(|b| b.high), self.k_period);
97 let lowest = rolling_min(data.iter().map(|b| b.low), self.k_period);
98 let mut raw_k = Vec::with_capacity(data.len());
99 for i in 0..data.len() {
100 if i < self.k_period - 1 {
101 raw_k.push(f64::NAN);
102 } else {
103 let (highest, lowest) = (highest[i], lowest[i]);
104 let range = highest - lowest;
105
106 if range == 0.0 {
107 raw_k.push(50.0); } else {
109 let k = 100.0 * (data[i].close - lowest) / range;
110 raw_k.push(k.clamp(0.0, 100.0));
111 }
112 }
113 }
114
115 let smooth_k = helpers::sma(&raw_k, self.k_smooth);
116 let d_values = helpers::sma(&smooth_k, self.d_period);
117
118 for i in 0..data.len() {
120 if smooth_k[i].is_nan() {
121 values.push(IndicatorValue::None);
122 } else {
123 values.push_lines(&[smooth_k[i], d_values[i]]);
124 }
125 }
126
127 values
128 }
129}
130
131impl Indicator for Stochastic {
132 crate::studies::inputs::study_inputs!(self;
133 k_period: length("K period", 1, usize::MAX),
134 k_smooth: length("K smooth", 1, usize::MAX),
135 d_period: length("D period", 1, usize::MAX)
136 ; {});
137
138 fn name(&self) -> &str {
139 "Stochastic"
140 }
141
142 fn precision(&self) -> Option<usize> {
143 Some(2)
144 }
145
146 fn desc(&self) -> &str {
147 "Stochastic Oscillator - Momentum oscillator (0-100)"
148 }
149
150 fn calculate(&mut self, data: &[Bar]) {
151 self.update(data, 0);
152 }
153
154 fn update(&mut self, data: &[Bar], from: usize) {
155 let mut values = std::mem::take(&mut self.values);
156 let behind = (self.k_period - 1)
157 .saturating_add(self.k_smooth - 1)
158 .saturating_add(self.d_period - 1);
159 let usable = |b: &Bar| b.high.is_finite() && b.low.is_finite() && b.close.is_finite();
160 update_window(
161 &mut values,
162 data,
163 from,
164 usable_behind(behind, usable),
165 |data| over_finite_bars(data, usable, |data| self.compute(data)),
166 );
167 self.values = values;
168 }
169
170 fn is_incremental(&self) -> bool {
171 true
172 }
173
174 fn values(&self) -> &IndicatorValues {
175 &self.values
176 }
177
178 fn colors(&self) -> Vec<Color32> {
179 vec![self.k_color, self.d_color]
180 }
181
182 fn set_colors(&mut self, colors: Vec<Color32>) {
183 if !colors.is_empty() {
184 self.k_color = colors[0];
185 }
186 if colors.len() > 1 {
187 self.d_color = colors[1];
188 }
189 }
190
191 fn pane(&self) -> crate::studies::StudyPane {
192 crate::studies::StudyPane {
193 height: None,
194 range: Some((0.0, 100.0)),
195 levels: vec![
196 (80.0, palette::MUTED, "Overbought"),
197 (20.0, palette::MUTED, "Oversold"),
198 ],
199 fill_between_levels: false,
200 }
201 }
202
203 fn is_overlay(&self) -> bool {
204 false }
206
207 fn line_count(&self) -> usize {
208 2 }
210
211 fn is_visible(&self) -> bool {
212 self.visible
213 }
214
215 fn set_visible(&mut self, visible: bool) {
216 self.visible = visible;
217 }
218
219 fn clone_box(&self) -> Box<dyn Indicator> {
220 Box::new(self.clone())
221 }
222
223 fn line_names(&self) -> Vec<String> {
224 vec![
225 format!("%K({},{},{})", self.k_period, self.k_smooth, self.d_period),
226 "%D".to_string(),
227 ]
228 }
229}