Skip to main content

egui_charts/studies/builtin/
double_stochastic.rs

1use 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/// Double Stochastic
10/// Applies the stochastic formula twice for smoother signals
11/// More responsive to overbought/oversold conditions
12#[derive(Clone)]
13pub struct DoubleStochastic {
14    /// Period for stochastic calculation
15    period: usize,
16    /// Smoothing period
17    smooth: usize,
18    values: IndicatorValues,
19    color: Color32,
20    visible: bool,
21}
22
23impl DoubleStochastic {
24    /// Create with `period`, `smooth` in bars, each clamped to at least one.
25    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    /// Set the plot color.
36    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    /// The values over `data`, every bar of which has finite inputs.
50    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        // First stochastic
61        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        // Smooth the first stochastic
84        let smooth_stoch1 = helpers::sma(&stoch1, self.smooth);
85
86        // Second stochastic on the smoothed values
87        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        // Final smoothing
123        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}