egui_charts/studies/builtin/
stochastic_rsi.rs1use crate::model::Bar;
27use crate::studies::palette;
28use crate::studies::stepper::Stepper;
29use crate::studies::{Indicator, IndicatorValue, IndicatorValues};
30use egui::Color32;
31
32#[derive(Clone)]
34pub struct StochasticRSI {
35 rsi_period: usize,
36 stoch_period: usize,
37 smooth_k: usize,
38 smooth_d: usize,
39 values: IndicatorValues,
40 stepper: Stepper<State>,
41 k_color: Color32,
42 d_color: Color32,
43 visible: bool,
44}
45
46#[derive(Clone, Default)]
47struct State {
48 previous: Option<f64>,
49 count: usize,
50 gain: f64,
51 loss: f64,
52 rsi: std::collections::VecDeque<f64>,
53 k: std::collections::VecDeque<f64>,
54 d: std::collections::VecDeque<f64>,
55}
56
57impl StochasticRSI {
58 pub fn new(rsi_period: usize, stoch_period: usize, smooth_k: usize, smooth_d: usize) -> Self {
66 Self {
67 rsi_period: rsi_period.max(1),
68 stoch_period: stoch_period.max(1),
69 smooth_k: smooth_k.max(1),
70 smooth_d: smooth_d.max(1),
71 values: IndicatorValues::new(),
72 stepper: Stepper::default(),
73 k_color: palette::BLUE,
74 d_color: palette::ORANGE,
75 visible: true,
76 }
77 }
78
79 pub fn default_params() -> Self {
81 Self::new(14, 14, 3, 3)
82 }
83
84 pub fn with_colors(mut self, k_color: Color32, d_color: Color32) -> Self {
86 self.k_color = k_color;
87 self.d_color = d_color;
88 self
89 }
90}
91
92impl Default for StochasticRSI {
94 fn default() -> Self {
95 Self::new(14, 14, 3, 3)
96 }
97}
98
99impl Indicator for StochasticRSI {
100 fn source(&self) -> Option<crate::studies::StudySource> {
101 Some(crate::studies::StudySource::Price(
102 crate::model::PriceSource::Close,
103 ))
104 }
105
106 crate::studies::inputs::study_inputs!(self;
107 rsi_period: length("RSI period", 1, usize::MAX),
108 stoch_period: length("Stoch period", 1, usize::MAX),
109 smooth_k: length("Smooth k", 1, usize::MAX),
110 smooth_d: length("Smooth d", 1, usize::MAX)
111 ; {});
112
113 fn name(&self) -> &str {
114 "StochRSI"
115 }
116
117 fn desc(&self) -> &str {
118 "Stochastic RSI - Stochastic oscillator of RSI"
119 }
120
121 fn calculate(&mut self, data: &[Bar]) {
122 self.update(data, 0);
123 }
124
125 fn update(&mut self, data: &[Bar], from: usize) {
126 self.stepper
127 .run(data, from, &mut self.values, |state, bar| {
128 if !bar.close.is_finite() {
129 return IndicatorValue::None;
130 }
131 let Some(previous) = state.previous.replace(bar.close) else {
132 return IndicatorValue::None;
133 };
134 let change = bar.close - previous;
135 state.count += 1;
136 let gain = change.max(0.0);
137 let loss = (-change).max(0.0);
138 if state.count <= self.rsi_period {
139 state.gain += gain;
140 state.loss += loss;
141 if state.count < self.rsi_period {
142 return IndicatorValue::None;
143 }
144 state.gain /= self.rsi_period as f64;
145 state.loss /= self.rsi_period as f64;
146 } else {
147 state.gain =
148 (state.gain * (self.rsi_period - 1) as f64 + gain) / self.rsi_period as f64;
149 state.loss =
150 (state.loss * (self.rsi_period - 1) as f64 + loss) / self.rsi_period as f64;
151 }
152 let rsi = super::rsi::from_averages(state.gain, state.loss);
153 state.rsi.push_back(rsi);
154 if state.rsi.len() > self.stoch_period {
155 state.rsi.pop_front();
156 }
157 if state.rsi.len() < self.stoch_period {
158 return IndicatorValue::None;
159 }
160 let low = state.rsi.iter().copied().fold(f64::INFINITY, f64::min);
161 let high = state.rsi.iter().copied().fold(f64::NEG_INFINITY, f64::max);
162 let stoch = if (high - low).abs() < 1e-10 {
163 0.5
164 } else {
165 (rsi - low) / (high - low)
166 };
167 state.k.push_back(stoch);
168 if state.k.len() > self.smooth_k {
169 state.k.pop_front();
170 }
171 if state.k.len() < self.smooth_k {
172 return IndicatorValue::None;
173 }
174 let k = state.k.iter().sum::<f64>() / self.smooth_k as f64;
175 state.d.push_back(k);
176 if state.d.len() > self.smooth_d {
177 state.d.pop_front();
178 }
179 if state.d.len() < self.smooth_d {
180 return IndicatorValue::Multiple(vec![k * 100.0, f64::NAN]);
181 }
182 let d = state.d.iter().sum::<f64>() / self.smooth_d as f64;
183 if k.is_nan() || d.is_nan() {
184 IndicatorValue::None
185 } else {
186 IndicatorValue::Multiple(vec![k * 100.0, d * 100.0])
187 }
188 });
189 }
190
191 fn is_incremental(&self) -> bool {
192 true
193 }
194
195 fn values(&self) -> &IndicatorValues {
196 &self.values
197 }
198
199 fn colors(&self) -> Vec<Color32> {
200 vec![self.k_color, self.d_color]
201 }
202
203 fn set_colors(&mut self, colors: Vec<Color32>) {
204 if !colors.is_empty() {
205 self.k_color = colors[0];
206 }
207 if colors.len() > 1 {
208 self.d_color = colors[1];
209 }
210 }
211
212 fn is_overlay(&self) -> bool {
213 false }
215
216 fn line_count(&self) -> usize {
217 2 }
219
220 fn is_visible(&self) -> bool {
221 self.visible
222 }
223
224 fn set_visible(&mut self, visible: bool) {
225 self.visible = visible;
226 }
227
228 fn clone_box(&self) -> Box<dyn Indicator> {
229 Box::new(self.clone())
230 }
231
232 fn line_names(&self) -> Vec<String> {
233 vec![
234 format!(
235 "%K({}, {}, {})",
236 self.rsi_period, self.stoch_period, self.smooth_k
237 ),
238 format!("%D({})", self.smooth_d),
239 ]
240 }
241}