egui_charts/studies/builtin/
double_stochastic.rs1use super::over_finite_bars;
2use crate::model::Bar;
3use crate::studies::helpers;
4use crate::studies::palette;
5use crate::studies::stepper::{update_window, usable_behind};
6use crate::studies::{Indicator, IndicatorValue, IndicatorValues};
7use egui::Color32;
8
9#[derive(Clone)]
13pub struct DoubleStochastic {
14 period: usize,
16 smooth: usize,
18 values: IndicatorValues,
19 color: Color32,
20 visible: bool,
21}
22
23impl DoubleStochastic {
24 pub fn new(period: usize, smooth: usize) -> Self {
26 Self {
27 period: period.max(1),
28 smooth: smooth.max(1),
29 values: IndicatorValues::new(),
30 color: palette::PURPLE,
31 visible: true,
32 }
33 }
34
35 pub fn with_color(mut self, color: Color32) -> Self {
37 self.color = color;
38 self
39 }
40}
41
42impl Default for DoubleStochastic {
43 fn default() -> Self {
44 Self::new(10, 3)
45 }
46}
47
48impl DoubleStochastic {
49 fn compute(&self, data: &[Bar]) -> IndicatorValues {
51 let mut values = IndicatorValues::new();
52 let min_period = self.period.saturating_add(self.smooth.saturating_mul(2));
53 if data.len() < min_period {
54 for _ in 0..data.len() {
55 values.push(IndicatorValue::None);
56 }
57 return values;
58 }
59
60 let mut stoch1: Vec<f64> = Vec::with_capacity(data.len());
62
63 for i in 0..data.len() {
64 if i < self.period - 1 {
65 stoch1.push(f64::NAN);
66 } else {
67 let window = &data[i + 1 - self.period..=i];
68 let high = window
69 .iter()
70 .map(|b| b.high)
71 .fold(f64::NEG_INFINITY, f64::max);
72 let low = window.iter().map(|b| b.low).fold(f64::INFINITY, f64::min);
73
74 let k = if high != low {
75 (data[i].close - low) / (high - low) * 100.0
76 } else {
77 50.0
78 };
79 stoch1.push(k);
80 }
81 }
82
83 let smooth_stoch1 = helpers::sma(&stoch1, self.smooth);
85
86 let mut stoch2: Vec<f64> = Vec::with_capacity(data.len());
88
89 for i in 0..data.len() {
90 let start = if i >= self.period - 1 {
91 i + 1 - self.period
92 } else {
93 0
94 };
95
96 if i < self.period + self.smooth - 1 {
97 stoch2.push(f64::NAN);
98 } else {
99 let window = &smooth_stoch1[start..=i];
100 let valid_values: Vec<f64> =
101 window.iter().filter(|v| v.is_finite()).cloned().collect();
102
103 if valid_values.len() >= self.period {
104 let high = valid_values
105 .iter()
106 .fold(f64::NEG_INFINITY, |a, &b| a.max(b));
107 let low = valid_values.iter().fold(f64::INFINITY, |a, &b| a.min(b));
108
109 let current = smooth_stoch1[i];
110 let k = if high != low && current.is_finite() {
111 (current - low) / (high - low) * 100.0
112 } else {
113 50.0
114 };
115 stoch2.push(k);
116 } else {
117 stoch2.push(f64::NAN);
118 }
119 }
120 }
121
122 let ds = helpers::sma(&stoch2, self.smooth);
124
125 for i in 0..data.len() {
126 if i < min_period - 1 || ds[i].is_nan() {
127 values.push(IndicatorValue::None);
128 } else {
129 values.push(IndicatorValue::Single(ds[i]));
130 }
131 }
132
133 values
134 }
135}
136
137impl Indicator for DoubleStochastic {
138 crate::studies::inputs::study_inputs!(self;
139 period: length("Period", 1, usize::MAX),
140 smooth: length("Smooth", 1, usize::MAX)
141 ; {});
142
143 fn name(&self) -> &str {
144 "DS"
145 }
146
147 fn desc(&self) -> &str {
148 "Double Stochastic - Double-smoothed stochastic oscillator"
149 }
150
151 fn calculate(&mut self, data: &[Bar]) {
152 self.update(data, 0);
153 }
154
155 fn update(&mut self, data: &[Bar], from: usize) {
156 let mut values = std::mem::take(&mut self.values);
157 update_window(
158 &mut values,
159 data,
160 from,
161 usable_behind(
162 self.period.saturating_add(self.smooth).saturating_mul(2),
163 |b| b.high.is_finite() && b.low.is_finite() && b.close.is_finite(),
164 ),
165 |data| {
166 over_finite_bars(
167 data,
168 |b| b.high.is_finite() && b.low.is_finite() && b.close.is_finite(),
169 |data| self.compute(data),
170 )
171 },
172 );
173 self.values = values;
174 }
175
176 fn is_incremental(&self) -> bool {
177 true
178 }
179
180 fn values(&self) -> &IndicatorValues {
181 &self.values
182 }
183
184 fn colors(&self) -> Vec<Color32> {
185 vec![self.color]
186 }
187
188 fn set_colors(&mut self, colors: Vec<Color32>) {
189 if !colors.is_empty() {
190 self.color = colors[0];
191 }
192 }
193
194 fn is_overlay(&self) -> bool {
195 false
196 }
197
198 fn is_visible(&self) -> bool {
199 self.visible
200 }
201
202 fn set_visible(&mut self, visible: bool) {
203 self.visible = visible;
204 }
205
206 fn clone_box(&self) -> Box<dyn Indicator> {
207 Box::new(self.clone())
208 }
209
210 fn line_names(&self) -> Vec<String> {
211 vec![format!("DS({},{})", self.period, self.smooth)]
212 }
213}