egui_charts/studies/builtin/
williams_fractals.rs1use super::over_finite_bars;
43use crate::model::Bar;
44use crate::studies::stepper::{rolling_max, update_window, usable_behind};
45use crate::studies::{Indicator, IndicatorValues};
46use egui::Color32;
47
48#[derive(Clone)]
50pub struct WilliamsFractals {
51 periods: usize,
52 values: IndicatorValues,
53 up_color: Color32,
54 down_color: Color32,
55 visible: bool,
56}
57
58impl WilliamsFractals {
59 pub fn new(periods: usize) -> Self {
61 Self {
62 periods: periods.max(2),
63 values: IndicatorValues::new(),
64 up_color: crate::studies::palette::DEEP_TEAL,
65 down_color: crate::studies::palette::BRIGHT_RED,
66 visible: true,
67 }
68 }
69
70 fn compute(&self, data: &[Bar]) -> IndicatorValues {
72 let mut values = IndicatorValues::missing(data.len());
73 if !data.is_empty() {
74 values.set_value(1, 0, f64::NAN);
76 }
77 for (line, sign) in [(0, 1.0), (1, -1.0)] {
78 let x: Vec<f64> = data
80 .iter()
81 .map(|b| sign * if line == 0 { b.high } else { b.low })
82 .collect();
83 let highest = rolling_max(x.iter().copied(), self.periods);
85 for p in 0..x.len() {
86 let after = p.checked_add(self.periods).and_then(|end| highest.get(end));
87 if !after.is_some_and(|&after| after < x[p]) {
88 continue;
89 }
90 let before = (0..=4).any(|k: usize| {
91 p.checked_sub(k + self.periods).is_some()
92 && x[p - k..p].iter().all(|&v| v <= x[p])
93 && highest[p - k - 1] < x[p]
94 });
95 if before {
96 values.set_value(line, p, sign * x[p]);
97 }
98 }
99 }
100 values
101 }
102}
103
104impl Default for WilliamsFractals {
106 fn default() -> Self {
107 Self::new(2)
108 }
109}
110
111impl Indicator for WilliamsFractals {
112 crate::studies::inputs::study_inputs!(self;
113 periods: length("Periods", 2, usize::MAX)
114 ; {});
115
116 fn name(&self) -> &str {
117 "Williams Fractals"
118 }
119
120 fn desc(&self) -> &str {
121 "Williams Fractals - Swing highs and lows confirmed by the bars on each side"
122 }
123
124 fn calculate(&mut self, data: &[Bar]) {
125 self.update(data, 0);
126 }
127
128 fn update(&mut self, data: &[Bar], from: usize) {
129 let from = usable_behind(self.periods, |b| b.high.is_finite() && b.low.is_finite())(
131 data,
132 from.min(self.values.len()).min(data.len()),
133 );
134 let mut values = std::mem::take(&mut self.values);
135 update_window(
136 &mut values,
137 data,
138 from,
139 usable_behind(self.periods.saturating_add(4), |b| {
140 b.high.is_finite() && b.low.is_finite()
141 }),
142 |data| {
143 over_finite_bars(
144 data,
145 |b| b.high.is_finite() && b.low.is_finite(),
146 |data| self.compute(data),
147 )
148 },
149 );
150 self.values = values;
151 }
152
153 fn is_incremental(&self) -> bool {
154 true
155 }
156
157 fn plot_style(&self, line: usize) -> crate::studies::PlotStyle {
158 crate::studies::PlotStyle {
159 color: if line == 0 {
160 self.up_color
161 } else {
162 self.down_color
163 },
164 width: 2.0,
165 pattern: crate::model::LineStyle::Solid,
166 plot_type: if line == 0 {
167 crate::studies::PlotType::TriangleUp
168 } else {
169 crate::studies::PlotType::TriangleDown
170 },
171 visible: true,
172 }
173 }
174
175 fn values(&self) -> &IndicatorValues {
176 &self.values
177 }
178
179 fn colors(&self) -> Vec<Color32> {
180 vec![self.up_color, self.down_color]
181 }
182
183 fn set_colors(&mut self, colors: Vec<Color32>) {
184 if let Some(&color) = colors.first() {
185 self.up_color = color;
186 }
187 if let Some(&color) = colors.get(1) {
188 self.down_color = color;
189 }
190 }
191
192 fn is_visible(&self) -> bool {
193 self.visible
194 }
195
196 fn set_visible(&mut self, visible: bool) {
197 self.visible = visible;
198 }
199
200 fn clone_box(&self) -> Box<dyn Indicator> {
201 Box::new(self.clone())
202 }
203
204 fn line_names(&self) -> Vec<String> {
205 vec!["Up Fractal".to_string(), "Down Fractal".to_string()]
206 }
207}