egui_charts/studies/builtin/
ulcer_index.rs1use super::over_finite_bars;
28use crate::model::Bar;
29use crate::studies::palette;
30use crate::studies::stepper::{rolling_max, rolling_mean};
31use crate::studies::stepper::{update_window, usable_behind};
32use crate::studies::{Indicator, IndicatorValue, IndicatorValues};
33use egui::Color32;
34
35#[derive(Clone)]
37pub struct UlcerIndex {
38 period: usize,
39 values: IndicatorValues,
40 color: Color32,
41 visible: bool,
42}
43
44impl UlcerIndex {
45 pub fn new(period: usize) -> Self {
47 Self {
48 period: period.max(1),
49 values: IndicatorValues::new(),
50 color: palette::BLUE,
51 visible: true,
52 }
53 }
54
55 fn compute(&self, data: &[Bar]) -> IndicatorValues {
57 let highest = rolling_max(data.iter().map(|b| b.close), self.period);
58 let squares: Vec<f64> = data
59 .iter()
60 .zip(highest)
61 .enumerate()
62 .map(|(i, (bar, highest))| {
63 if i + 1 < self.period {
64 return f64::NAN;
65 }
66 let drawdown = 100.0 * (bar.close - highest) / highest;
67 drawdown * drawdown
68 })
69 .collect();
70 rolling_mean(&squares, self.period)
71 .into_iter()
72 .map(|mean| IndicatorValue::Single(mean.sqrt()))
73 .collect()
74 }
75}
76
77impl Default for UlcerIndex {
78 fn default() -> Self {
79 Self::new(14)
80 }
81}
82
83impl Indicator for UlcerIndex {
84 fn source(&self) -> Option<crate::studies::StudySource> {
85 Some(crate::studies::StudySource::Price(
86 crate::model::PriceSource::Close,
87 ))
88 }
89
90 crate::studies::inputs::study_inputs!(self;
91 period: length("Length", 1, usize::MAX)
92 ; {});
93
94 fn name(&self) -> &str {
95 "Ulcer Index"
96 }
97
98 fn desc(&self) -> &str {
99 "Ulcer Index - Depth and duration of drawdowns from the recent high"
100 }
101
102 fn calculate(&mut self, data: &[Bar]) {
103 self.update(data, 0);
104 }
105
106 fn update(&mut self, data: &[Bar], from: usize) {
107 let mut values = std::mem::take(&mut self.values);
108 update_window(
109 &mut values,
110 data,
111 from,
112 usable_behind((self.period - 1).saturating_mul(2), |b| b.close.is_finite()),
113 |data| over_finite_bars(data, |b| b.close.is_finite(), |data| self.compute(data)),
114 );
115 self.values = values;
116 }
117
118 fn is_incremental(&self) -> bool {
119 true
120 }
121
122 fn values(&self) -> &IndicatorValues {
123 &self.values
124 }
125
126 fn colors(&self) -> Vec<Color32> {
127 vec![self.color]
128 }
129
130 fn set_colors(&mut self, colors: Vec<Color32>) {
131 if let Some(&color) = colors.first() {
132 self.color = color;
133 }
134 }
135
136 fn is_overlay(&self) -> bool {
137 false
138 }
139
140 fn is_visible(&self) -> bool {
141 self.visible
142 }
143
144 fn set_visible(&mut self, visible: bool) {
145 self.visible = visible;
146 }
147
148 fn clone_box(&self) -> Box<dyn Indicator> {
149 Box::new(self.clone())
150 }
151
152 fn line_names(&self) -> Vec<String> {
153 vec![format!("Ulcer Index({})", self.period)]
154 }
155}