1use super::{Indicator, IndicatorValue, IndicatorValues, StudyData, StudyDataRequest};
4
5use crate::data::MetricSeries;
6use crate::model::Bar;
7use crate::scales::StudyFormat;
8use crate::studies::palette;
9use egui::Color32;
10
11#[derive(Clone)]
18pub struct MetricStudy {
19 metric: MetricSeries,
20 values: IndicatorValues,
21 color: Color32,
22 visible: bool,
23}
24
25impl MetricStudy {
26 pub fn new(metric: MetricSeries) -> Self {
28 Self {
29 metric,
30 values: IndicatorValues::new(),
31 color: palette::ACCENT,
32 visible: true,
33 }
34 }
35}
36
37impl Indicator for MetricStudy {
38 fn name(&self) -> &str {
39 &self.metric.name
40 }
41 fn desc(&self) -> &str {
42 &self.metric.unit
43 }
44 fn is_overlay(&self) -> bool {
45 false
46 }
47 fn value_format(&self) -> StudyFormat {
48 self.metric.format
49 }
50 fn line_names(&self) -> Vec<String> {
51 vec![if self.metric.unit.is_empty() {
52 self.metric.name.clone()
53 } else {
54 format!("{} ({})", self.metric.name, self.metric.unit)
55 }]
56 }
57 fn plot_style(&self, _: usize) -> super::PlotStyle {
58 super::PlotStyle {
59 color: self.color,
60 width: 1.0,
61 pattern: crate::model::LineStyle::Solid,
62 plot_type: super::PlotType::StepLine,
63 visible: true,
64 }
65 }
66 fn data_requests(&self) -> Vec<StudyDataRequest> {
67 vec![StudyDataRequest {
68 symbol: String::new(),
69 timeframe: None,
70 metric: Some(self.metric.id.clone()),
71 }]
72 }
73 fn calculate(&mut self, data: &[Bar]) {
74 self.update_data(data, 0, &[]);
75 }
76 fn update_data(&mut self, data: &[Bar], _: usize, sources: &[StudyData<'_>]) {
77 let request = &self.data_requests()[0];
78 let source = sources.iter().find(|source| &source.request == request);
79 self.values = data
80 .iter()
81 .map(|bar| {
82 source
83 .and_then(|source| source.bar_at(bar.time).map(|i| source.bars[i].close))
84 .map_or(IndicatorValue::None, IndicatorValue::Single)
85 })
86 .collect();
87 }
88 fn values(&self) -> &IndicatorValues {
89 &self.values
90 }
91 fn colors(&self) -> Vec<Color32> {
92 vec![self.color]
93 }
94 fn set_colors(&mut self, colors: Vec<Color32>) {
95 if let Some(&color) = colors.first() {
96 self.color = color;
97 }
98 }
99 fn is_visible(&self) -> bool {
100 self.visible
101 }
102 fn set_visible(&mut self, visible: bool) {
103 self.visible = visible;
104 }
105 fn clone_box(&self) -> Box<dyn Indicator> {
106 Box::new(self.clone())
107 }
108}
109
110#[cfg(test)]
111mod tests {
112 use super::*;
113 use crate::data::MetricPoint;
114 use crate::model::{BarClock, Timeframe};
115 use chrono::DateTime;
116
117 #[test]
120 fn irregular_metric_points_hold_until_the_next_publication() {
121 let time = |seconds| DateTime::from_timestamp(seconds, 0).unwrap();
122 let points = [(30, 10.0), (150, 20.0), (301, 5.0)];
123 let mut bars: Vec<Bar> = points
124 .into_iter()
125 .map(|(t, value)| {
126 MetricPoint {
127 time: time(t),
128 value,
129 }
130 .into()
131 })
132 .collect();
133 let chart: Vec<_> = [0, 30, 60, 120, 150, 180, 240, 300, 360]
134 .into_iter()
135 .map(|t| Bar::new(time(t), 1.0, 1.0, 1.0, 1.0, 1.0))
136 .collect();
137 let mut study = MetricStudy::new(MetricSeries {
138 id: "oi".into(),
139 name: "Open interest".into(),
140 unit: "contracts".into(),
141 format: StudyFormat::Volume,
142 });
143 let request = study.data_requests().pop().unwrap();
144 for (edit, expected) in [
146 (
147 None,
148 [
149 None,
150 Some(10.0),
151 Some(10.0),
152 Some(10.0),
153 Some(20.0),
154 Some(20.0),
155 Some(20.0),
156 Some(20.0),
157 Some(5.0),
158 ],
159 ),
160 (
161 Some(12.0),
162 [
163 None,
164 Some(12.0),
165 Some(12.0),
166 Some(12.0),
167 Some(20.0),
168 Some(20.0),
169 Some(20.0),
170 Some(20.0),
171 Some(5.0),
172 ],
173 ),
174 ] {
175 if let Some(value) = edit {
176 bars[0] = MetricPoint {
177 time: time(30),
178 value,
179 }
180 .into();
181 }
182 let source = StudyData {
183 request: request.clone(),
184 bars: &bars,
185 clock: BarClock::new(Timeframe::Ticks(1.try_into().unwrap())),
186 instrument: None,
187 timeframe: Timeframe::Min1,
188 };
189 study.update_data(&chart, 0, &[source]);
190 for (i, want) in expected.into_iter().enumerate() {
191 assert_eq!(
192 study.values().value(0, i),
193 want,
194 "bar {i}, correction {edit:?}"
195 );
196 }
197 }
198 study.calculate(&chart);
199 assert!(
200 study.values().columns().is_empty(),
201 "missing histories stay unavailable"
202 );
203 }
204}