egui_charts/studies/builtin/
visible_average_price.rs1use crate::model::{Bar, PriceSource};
4use crate::studies::palette;
5use crate::studies::{Indicator, IndicatorValues};
6use egui::Color32;
7use std::ops::Range;
8
9#[derive(Clone)]
11pub struct VisibleAveragePrice {
12 source: PriceSource,
13 range: Range<usize>,
14 values: IndicatorValues,
15 mean: Option<f64>,
16 color: Color32,
17 visible: bool,
18}
19
20impl VisibleAveragePrice {
21 pub fn new() -> Self {
23 Self {
24 source: PriceSource::Close,
25 range: 0..usize::MAX,
26 values: IndicatorValues::new(),
27 mean: None,
28 color: palette::GRAY,
29 visible: true,
30 }
31 }
32}
33
34impl Default for VisibleAveragePrice {
35 fn default() -> Self {
36 Self::new()
37 }
38}
39
40impl Indicator for VisibleAveragePrice {
41 fn source(&self) -> Option<crate::studies::StudySource> {
42 Some(crate::studies::StudySource::Price(self.source))
43 }
44
45 crate::studies::inputs::study_inputs!(self;
46 source: choice("Source", PriceSource::all())
47 ; {});
48
49 fn name(&self) -> &str {
50 "Visible Average Price"
51 }
52 fn desc(&self) -> &str {
53 "Arithmetic mean of the visible bars' price source"
54 }
55 fn set_visible_range(&mut self, range: Range<usize>) -> bool {
56 let changed = self.range != range;
57 self.range = range;
58 changed
59 }
60 fn calculate(&mut self, data: &[Bar]) {
61 self.values = IndicatorValues::missing(data.len());
62 let end = self.range.end.min(data.len());
63 let start = self.range.start.min(end);
64 let price = |bar: &Bar| self.source.compute(bar.open, bar.high, bar.low, bar.close);
65 let (mut mean, mut count) = (0.0, 0.0);
66 for value in data[start..end].iter().map(price).filter(|v| v.is_finite()) {
67 count += 1.0;
68 mean = mean * ((count - 1.0) / count) + value / count;
69 }
70 self.mean = (count != 0.0).then_some(mean);
71 if count != 0.0 {
72 for (i, bar) in data.iter().enumerate() {
73 if price(bar).is_finite() {
74 self.values.set_value(0, i, mean);
75 }
76 }
77 }
78 }
79 fn update(&mut self, data: &[Bar], from: usize) {
80 let start = from.min(self.values.len()).min(data.len());
81 if start < self.range.end || from == 0 {
82 self.calculate(data);
84 return;
85 }
86 self.values.truncate(start);
87 for bar in &data[start..] {
88 let price = self.source.compute(bar.open, bar.high, bar.low, bar.close);
89 self.values.push_lines(&[if price.is_finite() {
90 self.mean.unwrap_or(f64::NAN)
91 } else {
92 f64::NAN
93 }]);
94 }
95 }
96 fn values(&self) -> &IndicatorValues {
97 &self.values
98 }
99 fn colors(&self) -> Vec<Color32> {
100 vec![self.color]
101 }
102 fn set_colors(&mut self, colors: Vec<Color32>) {
103 if let Some(color) = colors.first() {
104 self.color = *color;
105 }
106 }
107 fn is_visible(&self) -> bool {
108 self.visible
109 }
110 fn set_visible(&mut self, visible: bool) {
111 self.visible = visible;
112 }
113 fn clone_box(&self) -> Box<dyn Indicator> {
114 Box::new(self.clone())
115 }
116 fn line_names(&self) -> Vec<String> {
117 vec!["Avg Price".into()]
118 }
119}
120
121#[cfg(test)]
122mod tests {
123 use crate::model::Bar;
124 use crate::studies::*;
125 use chrono::{TimeZone, Utc};
126
127 #[test]
129 fn visible_average_follows_the_visible_range() {
130 let bars: Vec<_> = [2.0, 4.0, 12.0, 20.0]
131 .into_iter()
132 .enumerate()
133 .map(|(i, c)| Bar::new(Utc.timestamp_opt(i as i64, 0).unwrap(), c, c, c, c, 1.0))
134 .collect();
135 let mut study = ConfiguredStudy::new(
136 IndicatorFactory::new()
137 .create("Visible Average Price")
138 .expect("Visible Average Price study"),
139 );
140 for (range, expected) in [(0..2, Some(3.0)), (1..4, Some(12.0)), (4..99, None)] {
141 study.set_visible_range(range.clone());
142 study.calculate(&bars);
143 for i in 0..bars.len() {
144 assert_eq!(study.values().value(0, i), expected);
145 }
146 }
147 #[cfg(feature = "widget")]
148 {
149 use crate::model::LogicalRange;
150 use crate::widget::{Chart, test_frames::frame};
151 let bars: Vec<_> = (0..100)
152 .map(|i| {
153 let c = i as f64;
154 Bar::new(Utc.timestamp_opt(i, 0).unwrap(), c, c, c, c, 1.0)
155 })
156 .collect();
157 let mut chart = Chart::new(crate::model::BarData::from_bars(bars));
158 use crate::studies::{InputValue, StudySource};
159 let source = chart.add_indicator(Box::new(SMA::new(1)));
160 let average = chart.add_indicator(Box::new(study));
161 chart
162 .set_indicator_source(
163 average,
164 StudySource::Study {
165 id: source.0,
166 plot: 0,
167 },
168 )
169 .unwrap();
170 let consumer = chart.add_indicator(Box::new(SMA::new(1)));
171 chart
172 .set_indicator_source(
173 consumer,
174 StudySource::Study {
175 id: average.0,
176 plot: 0,
177 },
178 )
179 .unwrap();
180 assert!(
181 chart
182 .indicators()
183 .find(|(id, _)| *id == average)
184 .unwrap()
185 .1
186 .inputs()
187 .iter()
188 .any(|input| input.id == "source"
189 && matches!(input.value, InputValue::Source(_)))
190 );
191 let ctx = egui::Context::default();
192 frame(&ctx, &mut chart, 0.0, Vec::new());
193 for (time, left, expected) in [(1.0, 10.5, 20.0), (2.0, 50.5, 60.0)] {
195 assert!(chart.set_visible_logical_range(LogicalRange::new(left, left + 19.0)));
196 frame(&ctx, &mut chart, time, Vec::new());
197 for id in [average, consumer] {
198 let study = chart
199 .indicators()
200 .find(|(candidate, _)| *candidate == id)
201 .unwrap()
202 .1;
203 assert!((study.values().value(0, 0).unwrap() - expected).abs() < 1e-10);
204 }
205 }
206 }
207 }
208}