egui_charts/studies/builtin/
projected_pivot_points.rs1use super::pivot_points::{PivotLabelSide, PivotMethod, PivotPoints, PivotTimeframe};
41use crate::model::{Bar, Instrument, Timeframe};
42use crate::studies::{Indicator, IndicatorValues, StudyDataRequest, StudyLabel, StudyProjection};
43use egui::Color32;
44
45#[derive(Clone)]
47pub struct ProjectedPivotPoints {
48 pivots: PivotPoints,
49 projections: Vec<StudyProjection>,
50}
51
52impl ProjectedPivotPoints {
53 pub fn new() -> Self {
55 Self {
56 pivots: PivotPoints::new(),
57 projections: Vec::new(),
58 }
59 }
60
61 pub fn with_method(mut self, method: PivotMethod) -> Self {
63 self.pivots = self.pivots.with_method(method);
64 self
65 }
66
67 pub fn with_timeframe(mut self, timeframe: PivotTimeframe) -> Self {
69 self.pivots = self.pivots.with_timeframe(timeframe);
70 self
71 }
72
73 pub fn with_pivots_back(mut self, pivots_back: usize) -> Self {
75 self.pivots = self.pivots.with_pivots_back(pivots_back);
76 self
77 }
78
79 pub fn with_labels(mut self, names: bool, prices: bool) -> Self {
82 self.pivots = self.pivots.with_labels(names, prices);
83 self
84 }
85
86 pub fn with_label_side(mut self, side: PivotLabelSide) -> Self {
88 self.pivots = self.pivots.with_label_side(side);
89 self
90 }
91
92 pub fn with_daily_bars(mut self, daily: bool) -> Self {
95 self.pivots = self.pivots.with_daily_bars(daily);
96 self
97 }
98
99 fn project(&mut self, data: &[Bar]) {
100 self.projections.clear();
101 if let Some((method, period, start)) = self.pivots.latest_period() {
102 let last = data.len() - 1;
103 let width = data.len() - start;
104 let levels = PivotPoints::levels(method, period, period.close);
105 self.projections.extend(
106 levels
107 .into_iter()
108 .enumerate()
109 .filter(|(_, price)| price.is_finite())
110 .map(|(plot, price)| StudyProjection {
111 bars: last as f64..(last + width) as f64,
112 top: price,
113 bottom: price,
114 plot,
115 }),
116 );
117 self.pivots.label_projection(last..last + width, &levels);
118 }
119 }
120}
121
122impl Default for ProjectedPivotPoints {
124 fn default() -> Self {
125 Self::new()
126 }
127}
128
129impl Indicator for ProjectedPivotPoints {
130 fn inputs(&self) -> Vec<crate::studies::StudyInput> {
131 self.pivots.inputs()
132 }
133
134 fn set_input(
135 &mut self,
136 id: &str,
137 value: crate::studies::InputValue,
138 ) -> Result<(), crate::studies::StudyError> {
139 self.pivots.set_input(id, value)?;
140 self.projections.clear();
141 Ok(())
142 }
143
144 fn name(&self) -> &str {
145 "Projected Pivot Points"
146 }
147
148 fn desc(&self) -> &str {
149 "Projected Pivot Points - Next period's pivot levels from the period so far"
150 }
151
152 fn calculate(&mut self, data: &[Bar]) {
153 self.update(data, 0);
154 }
155
156 fn update(&mut self, data: &[Bar], from: usize) {
157 self.pivots.update(data, from);
158 self.project(data);
159 }
160
161 fn data_requests(&self) -> Vec<StudyDataRequest> {
162 self.pivots.data_requests()
163 }
164
165 fn update_data(
166 &mut self,
167 data: &[Bar],
168 from: usize,
169 sources: &[crate::studies::StudyData<'_>],
170 ) {
171 self.pivots.update_data(data, from, sources);
172 self.project(data);
173 }
174
175 fn is_incremental(&self) -> bool {
176 true
177 }
178
179 fn set_timeframe(&mut self, timeframe: Timeframe) {
180 self.pivots.set_timeframe(timeframe);
181 }
182
183 fn set_instrument(&mut self, instrument: Option<&Instrument>) -> bool {
184 self.pivots.set_instrument(instrument)
185 }
186
187 fn plot_color(&self, line: usize, bar: usize) -> Option<Color32> {
188 self.pivots.plot_color(line, bar)
189 }
190
191 fn projections(&self) -> &[StudyProjection] {
192 &self.projections
193 }
194
195 fn labels(&self) -> &[StudyLabel] {
196 self.pivots.labels()
197 }
198
199 fn set_label_precision(&mut self, precision: Option<usize>) {
200 self.pivots.set_label_precision(precision);
201 }
202
203 fn values(&self) -> &IndicatorValues {
204 self.pivots.values()
205 }
206
207 fn colors(&self) -> Vec<Color32> {
208 self.pivots.colors()
209 }
210
211 fn set_colors(&mut self, colors: Vec<Color32>) {
212 self.pivots.set_colors(colors);
213 }
214
215 fn is_overlay(&self) -> bool {
216 true
217 }
218
219 fn is_visible(&self) -> bool {
220 self.pivots.is_visible()
221 }
222
223 fn set_visible(&mut self, visible: bool) {
224 self.pivots.set_visible(visible);
225 }
226
227 fn clone_box(&self) -> Box<dyn Indicator> {
228 Box::new(self.clone())
229 }
230
231 fn line_names(&self) -> Vec<String> {
232 self.pivots.line_names()
233 }
234}
235
236#[cfg(test)]
237mod tests {
238 use super::*;
239 use chrono::{TimeZone, Utc};
240
241 #[test]
246 fn projects_from_the_period_so_far_beside_the_confirmed_levels() {
247 let hour = |day, h| Utc.with_ymd_and_hms(2026, 3, day, h, 0, 0).unwrap();
248 let data: Vec<Bar> = [
249 (hour(2, 9), 10.0, 8.0, 9.0),
250 (hour(2, 10), 12.0, 9.0, 11.0),
251 (hour(3, 9), 11.0, 9.0, 10.0),
252 (hour(3, 10), 14.0, 10.0, 12.0),
253 (hour(3, 11), 13.0, 10.0, 13.0),
254 ]
255 .into_iter()
256 .map(|(time, high, low, close)| Bar::new(time, close, high, low, close, 1.0))
257 .collect();
258 let mut pivots = ProjectedPivotPoints::new().with_timeframe(PivotTimeframe::Daily);
259 pivots.calculate(&data);
260 let projected: Vec<_> = pivots.projections()[..3]
261 .iter()
262 .map(|p| (p.bars.clone(), p.top, p.plot))
263 .collect();
264 assert_eq!(
265 projected,
266 [
267 (4.0..7.0, 12.0, 0),
268 (4.0..7.0, 15.0, 1),
269 (4.0..7.0, 10.0, 2)
270 ]
271 );
272 assert!((pivots.values().value(0, 4).unwrap() - 31.0 / 3.0).abs() < 1e-12);
273 let mut more = data.clone();
275 more.push(Bar::new(hour(3, 12), 13.0, 15.0, 12.0, 13.0, 1.0));
276 pivots.update(&more, 5);
277 assert_eq!(pivots.projections()[0].bars, 5.0..9.0);
278 assert!((pivots.projections()[0].top - (15.0 + 9.0 + 13.0) / 3.0).abs() < 1e-12);
279 }
280}