egui_charts/studies/builtin/
price_channel.rs1use super::over_finite_bars;
2use crate::model::Bar;
3use crate::studies::palette;
4use crate::studies::stepper::{update_window, usable_behind};
5use crate::studies::{Indicator, IndicatorValue, IndicatorValues};
6use egui::Color32;
7
8#[derive(Clone)]
12pub struct PriceChannel {
13 period: usize,
15 offset: i32,
16 values: IndicatorValues,
17 colors: Vec<Color32>,
19 visible: bool,
20}
21
22impl PriceChannel {
23 pub fn new(period: usize) -> Self {
25 Self {
26 period: period.max(1),
27 offset: 0,
28 values: IndicatorValues::new(),
29 colors: vec![
30 Color32::from_rgb(245, 0, 87), Color32::from_rgb(245, 0, 87), palette::LIGHT_BLUE, ],
34 visible: true,
35 }
36 }
37
38 pub fn with_colors(mut self, colors: Vec<Color32>) -> Self {
40 self.colors = colors;
41 self
42 }
43
44 pub fn with_offset(mut self, offset: i32) -> Self {
46 self.offset = offset;
47 self
48 }
49}
50
51impl Default for PriceChannel {
52 fn default() -> Self {
53 Self::new(20)
54 }
55}
56
57impl PriceChannel {
58 fn compute(&self, data: &[Bar]) -> IndicatorValues {
60 let mut values = IndicatorValues::new();
61 if data.len() < self.period {
62 for _ in 0..data.len() {
63 values.push(IndicatorValue::None);
64 }
65 return values;
66 }
67
68 for i in 0..data.len() {
69 if i < self.period - 1 {
70 values.push(IndicatorValue::None);
71 } else {
72 let window = &data[i + 1 - self.period..=i];
73 let highest = window
74 .iter()
75 .map(|b| b.high)
76 .fold(f64::NEG_INFINITY, f64::max);
77 let lowest = window.iter().map(|b| b.low).fold(f64::INFINITY, f64::min);
78 let middle = (highest + lowest) / 2.0;
79
80 values.push_lines(&[highest, lowest, middle]);
81 }
82 }
83
84 values
85 }
86}
87
88impl Indicator for PriceChannel {
89 crate::studies::inputs::study_inputs!(self;
90 period: length("Period", 1, usize::MAX),
91 offset: integer("Offset", i32::MIN, i32::MAX)
92 ; {});
93
94 fn plot_offset(&self, _line: usize) -> isize {
95 self.offset as isize
96 }
97
98 fn name(&self) -> &str {
99 "Price Channel"
100 }
101
102 fn desc(&self) -> &str {
103 "Price Channel - Highest high / Lowest low bands"
104 }
105
106 fn calculate(&mut self, data: &[Bar]) {
107 self.update(data, 0);
108 }
109
110 fn update(&mut self, data: &[Bar], from: usize) {
111 let mut values = std::mem::take(&mut self.values);
112 update_window(
113 &mut values,
114 data,
115 from,
116 usable_behind(self.period - 1, |b| b.high.is_finite() && b.low.is_finite()),
117 |data| {
118 over_finite_bars(
119 data,
120 |b| b.high.is_finite() && b.low.is_finite(),
121 |data| self.compute(data),
122 )
123 },
124 );
125 self.values = values;
126 }
127
128 fn is_incremental(&self) -> bool {
129 true
130 }
131
132 fn values(&self) -> &IndicatorValues {
133 &self.values
134 }
135
136 fn colors(&self) -> Vec<Color32> {
137 self.colors.clone()
138 }
139
140 fn set_colors(&mut self, colors: Vec<Color32>) {
141 if colors.len() >= 3 {
142 self.colors = colors;
143 }
144 }
145
146 fn is_overlay(&self) -> bool {
147 true }
149
150 fn is_visible(&self) -> bool {
151 self.visible
152 }
153
154 fn set_visible(&mut self, visible: bool) {
155 self.visible = visible;
156 }
157
158 fn clone_box(&self) -> Box<dyn Indicator> {
159 Box::new(self.clone())
160 }
161
162 fn line_names(&self) -> Vec<String> {
163 vec![
164 format!("Upper({})", self.period),
165 format!("Lower({})", self.period),
166 "Middle".to_string(),
167 ]
168 }
169}
170
171#[cfg(test)]
172mod tests {
173 use super::*;
174 use crate::studies::test_support::make_range_bar as make_bar;
175
176 #[test]
177 fn test_price_channel() {
178 let mut pc = PriceChannel::new(5);
179
180 let data = vec![
181 make_bar(102.0, 98.0, 100.0),
182 make_bar(104.0, 99.0, 103.0),
183 make_bar(106.0, 101.0, 105.0),
184 make_bar(108.0, 103.0, 107.0),
185 make_bar(110.0, 105.0, 109.0),
186 ];
187
188 pc.calculate(&data);
189
190 assert_eq!(pc.values.len(), 5);
191
192 let rows = pc.values.to_rows();
194 let IndicatorValue::Multiple(v) = rows.last().unwrap() else {
195 panic!("no value: {:?}", pc.values.to_rows().last().unwrap());
196 };
197 assert_eq!(v.len(), 3);
198 assert!((v[0] - 110.0).abs() < 0.01); assert!((v[1] - 98.0).abs() < 0.01); assert!((v[2] - 104.0).abs() < 0.01); }
202}