egui_charts/studies/builtin/
median.rs1use super::clamp_param;
26use super::smoothing::Smoothing;
27use crate::model::Bar;
28use crate::studies::palette;
29use crate::studies::stepper::Stepper;
30use crate::studies::{ATR, Indicator, IndicatorValue, IndicatorValues};
31use egui::Color32;
32
33#[derive(Clone)]
35pub struct Median {
36 length: usize,
37 atr_length: usize,
38 multiplier: f64,
39 values: IndicatorValues,
40 atr: ATR,
41 stepper: Stepper<(usize, std::collections::VecDeque<f64>, Smoothing)>,
42 colors: [Color32; 6],
44 visible: bool,
45}
46
47impl Median {
48 pub fn new(length: usize, atr_length: usize, multiplier: f64) -> Self {
51 Self {
52 length: length.max(1),
53 atr_length: atr_length.max(1),
54 multiplier: clamp_param(multiplier, 0.0, f64::MAX),
55 values: IndicatorValues::new(),
56 atr: ATR::new(atr_length),
57 stepper: Stepper::default(),
58 colors: [
59 palette::RED,
60 Color32::from_rgb(0, 230, 118),
61 Color32::from_rgb(224, 64, 251),
62 palette::BLUE,
63 Color32::from_rgb(0, 230, 118),
64 Color32::from_rgb(224, 64, 251),
65 ],
66 visible: true,
67 }
68 }
69}
70
71impl Default for Median {
73 fn default() -> Self {
74 Self::new(3, 14, 2.0)
75 }
76}
77
78impl Indicator for Median {
79 fn line_style(&self, line: usize) -> crate::studies::IndicatorLineStyle {
80 crate::studies::IndicatorLineStyle {
81 width: if line == 0 { 3.0 } else { 1.0 },
82 ..Default::default()
83 }
84 }
85
86 crate::studies::inputs::study_inputs!(self;
87 length: length("Median length", 1, usize::MAX),
88 atr_length: length("ATR length", 1, usize::MAX),
89 multiplier: number("ATR multiplier", 0.0, f64::MAX)
90 ; {});
91
92 fn fills(&self) -> Vec<crate::studies::StudyFill> {
93 let alpha = |c: Color32| Color32::from_rgba_unmultiplied(c.r(), c.g(), c.b(), 230);
94 vec![crate::studies::StudyFill {
95 first: crate::studies::FillBoundary::Plot(0),
96 second: crate::studies::FillBoundary::Plot(3),
97 color: alpha(self.colors[4]),
98 inverted_color: Some(alpha(self.colors[5])),
99 }]
100 }
101
102 fn name(&self) -> &str {
103 "Median"
104 }
105
106 fn desc(&self) -> &str {
107 "Median - Rolling median of the bar midpoint with its EMA and ATR bands"
108 }
109
110 fn calculate(&mut self, data: &[Bar]) {
111 self.update(data, 0);
112 }
113
114 fn update(&mut self, data: &[Bar], from: usize) {
115 if from == 0 {
116 self.atr = ATR::new(self.atr_length);
117 }
118 self.atr.update(data, from);
119 self.stepper
120 .run(data, from, &mut self.values, |state, bar| {
121 let index = state.0;
122 state.0 += 1;
123 if ![bar.high, bar.low, bar.close].iter().all(|x| x.is_finite()) {
124 return IndicatorValue::None;
125 }
126 state.1.push_back((bar.high + bar.low) / 2.0);
127 if state.1.len() > self.length {
128 state.1.pop_front();
129 }
130 let median = if state.1.len() < self.length {
131 f64::NAN
132 } else {
133 let mut window: Vec<_> = state.1.iter().copied().collect();
134 *window
135 .select_nth_unstable_by(self.length.div_ceil(2) - 1, f64::total_cmp)
136 .1
137 };
138 let ema = state
139 .2
140 .next(median, self.length, 2.0 / (self.length as f64 + 1.0));
141 let band = self
142 .atr
143 .values()
144 .value(0, index)
145 .map_or(f64::NAN, |atr| self.multiplier * atr);
146 IndicatorValue::Multiple(vec![median, median + band, median - band, ema])
147 });
148 }
149
150 fn is_incremental(&self) -> bool {
151 true
152 }
153
154 fn values(&self) -> &IndicatorValues {
155 &self.values
156 }
157
158 fn colors(&self) -> Vec<Color32> {
159 self.colors.to_vec()
160 }
161
162 fn set_colors(&mut self, colors: Vec<Color32>) {
163 for (old, new) in self.colors.iter_mut().zip(colors) {
164 *old = new;
165 }
166 }
167
168 fn is_visible(&self) -> bool {
169 self.visible
170 }
171
172 fn set_visible(&mut self, visible: bool) {
173 self.visible = visible;
174 }
175
176 fn clone_box(&self) -> Box<dyn Indicator> {
177 Box::new(self.clone())
178 }
179
180 fn line_names(&self) -> Vec<String> {
181 vec![
182 "Median".into(),
183 "Upper Band".into(),
184 "Lower Band".into(),
185 "Median EMA".into(),
186 ]
187 }
188}