Skip to main content

egui_charts/studies/builtin/
vidya.rs

1use super::smoothing::Smoothing;
2use crate::model::Bar;
3use crate::studies::palette;
4use crate::studies::stepper::Stepper;
5use crate::studies::{Indicator, IndicatorValue, IndicatorValues};
6use egui::Color32;
7
8/// VIDYA - Variable Index Dynamic Avg
9/// Adaptive moving avg that adjusts based on volatility
10/// Uses Chande Momentum Oscillator for volatility measurement
11#[derive(Clone)]
12pub struct VIDYA {
13    /// Period for calculation
14    period: usize,
15    values: IndicatorValues,
16    stepper: Stepper<(std::collections::VecDeque<f64>, Option<f64>)>,
17    color: Color32,
18    visible: bool,
19}
20
21impl VIDYA {
22    /// Create with a lookback of `period` bars, clamped to at least 1.
23    pub fn new(period: usize) -> Self {
24        Self {
25            period: period.max(1),
26            values: IndicatorValues::new(),
27            stepper: Stepper::default(),
28            color: palette::BLUE,
29            visible: true,
30        }
31    }
32
33    /// Set the plot color.
34    pub fn with_color(mut self, color: Color32) -> Self {
35        self.color = color;
36        self
37    }
38}
39
40impl Default for VIDYA {
41    fn default() -> Self {
42        Self::new(14)
43    }
44}
45
46impl Indicator for VIDYA {
47    fn source(&self) -> Option<crate::studies::StudySource> {
48        Some(crate::studies::StudySource::Price(
49            crate::model::PriceSource::Close,
50        ))
51    }
52
53    crate::studies::inputs::study_inputs!(self;
54        period: length("Period", 1, usize::MAX)
55    ; {});
56
57    fn name(&self) -> &str {
58        "VIDYA"
59    }
60
61    fn desc(&self) -> &str {
62        "Variable Index Dynamic Avg - Volatility-adjusted MA"
63    }
64
65    fn calculate(&mut self, data: &[Bar]) {
66        self.update(data, 0);
67    }
68
69    fn update(&mut self, data: &[Bar], from: usize) {
70        self.stepper
71            .run(data, from, &mut self.values, |state, bar| {
72                if !bar.close.is_finite() {
73                    return IndicatorValue::None;
74                }
75                let previous = state.1.get_or_insert(bar.close);
76                state.0.push_back(bar.close);
77                if state.0.len() <= self.period {
78                    return IndicatorValue::None;
79                }
80                while state.0.len() - 1 > self.period {
81                    state.0.pop_front();
82                }
83                let (mut up, mut down) = (0.0, 0.0);
84                for i in 1..state.0.len() {
85                    let change = state.0[i] - state.0[i - 1];
86                    if change > 0.0 {
87                        up += change;
88                    } else {
89                        down += (-change).abs();
90                    }
91                }
92                let vi = if up + down != 0.0 {
93                    (up - down).abs() / (up + down)
94                } else {
95                    0.0
96                };
97                let sc = 2.0 / (self.period as f64 + 1.0);
98                *previous = sc * vi * bar.close + (1.0 - sc * vi) * *previous;
99                IndicatorValue::Single(*previous)
100            });
101    }
102
103    fn is_incremental(&self) -> bool {
104        true
105    }
106
107    fn values(&self) -> &IndicatorValues {
108        &self.values
109    }
110
111    fn colors(&self) -> Vec<Color32> {
112        vec![self.color]
113    }
114
115    fn set_colors(&mut self, colors: Vec<Color32>) {
116        if !colors.is_empty() {
117            self.color = colors[0];
118        }
119    }
120
121    fn is_overlay(&self) -> bool {
122        true
123    }
124
125    fn is_visible(&self) -> bool {
126        self.visible
127    }
128
129    fn set_visible(&mut self, visible: bool) {
130        self.visible = visible;
131    }
132
133    fn clone_box(&self) -> Box<dyn Indicator> {
134        Box::new(self.clone())
135    }
136
137    fn line_names(&self) -> Vec<String> {
138        vec![format!("VIDYA({})", self.period)]
139    }
140}
141
142/// Zero Lag EMA
143/// Reduces lag in EMA by using error correction
144#[derive(Clone)]
145pub struct ZLEMA {
146    period: usize,
147    values: IndicatorValues,
148    stepper: Stepper<(std::collections::VecDeque<f64>, usize, Smoothing)>,
149    color: Color32,
150    visible: bool,
151}
152
153impl ZLEMA {
154    /// Create with a lookback of `period` bars, clamped to at least 1.
155    pub fn new(period: usize) -> Self {
156        Self {
157            period: period.max(1),
158            values: IndicatorValues::new(),
159            stepper: Stepper::default(),
160            color: palette::BLUE,
161            visible: true,
162        }
163    }
164
165    /// Set the plot color.
166    pub fn with_color(mut self, color: Color32) -> Self {
167        self.color = color;
168        self
169    }
170}
171
172impl Default for ZLEMA {
173    fn default() -> Self {
174        Self::new(14)
175    }
176}
177
178impl Indicator for ZLEMA {
179    fn source(&self) -> Option<crate::studies::StudySource> {
180        Some(crate::studies::StudySource::Price(
181            crate::model::PriceSource::Close,
182        ))
183    }
184
185    crate::studies::inputs::study_inputs!(self;
186        period: length("Period", 1, usize::MAX)
187    ; {});
188
189    fn name(&self) -> &str {
190        "ZLEMA"
191    }
192
193    fn desc(&self) -> &str {
194        "Zero Lag EMA - Reduced lag moving avg"
195    }
196
197    fn calculate(&mut self, data: &[Bar]) {
198        self.update(data, 0);
199    }
200
201    fn update(&mut self, data: &[Bar], from: usize) {
202        self.stepper
203            .run(data, from, &mut self.values, |state, bar| {
204                if !bar.close.is_finite() {
205                    return IndicatorValue::None;
206                }
207                let lag = (self.period - 1) / 2;
208                state.0.push_back(bar.close);
209                let delagged = if state.0.len() > lag {
210                    bar.close + (bar.close - state.0[state.0.len() - 1 - lag])
211                } else {
212                    bar.close
213                };
214                if state.0.len() > lag {
215                    state.0.pop_front();
216                }
217                let ema = state.2.next(delagged, 1, 2.0 / (self.period as f64 + 1.0));
218                state.1 += 1;
219                if state.1 < self.period {
220                    IndicatorValue::None
221                } else {
222                    IndicatorValue::Single(ema)
223                }
224            });
225    }
226
227    fn is_incremental(&self) -> bool {
228        true
229    }
230
231    fn values(&self) -> &IndicatorValues {
232        &self.values
233    }
234
235    fn colors(&self) -> Vec<Color32> {
236        vec![self.color]
237    }
238
239    fn set_colors(&mut self, colors: Vec<Color32>) {
240        if !colors.is_empty() {
241            self.color = colors[0];
242        }
243    }
244
245    fn is_overlay(&self) -> bool {
246        true
247    }
248
249    fn is_visible(&self) -> bool {
250        self.visible
251    }
252
253    fn set_visible(&mut self, visible: bool) {
254        self.visible = visible;
255    }
256
257    fn clone_box(&self) -> Box<dyn Indicator> {
258        Box::new(self.clone())
259    }
260
261    fn line_names(&self) -> Vec<String> {
262        vec![format!("ZLEMA({})", self.period)]
263    }
264}