1use super::swings::{Swing, Swings};
36use crate::model::Bar;
37use crate::studies::palette;
38use crate::studies::stepper::Stepper;
39use crate::studies::{Indicator, IndicatorValue, IndicatorValues, StudyCell, StudyTable};
40use chrono::{DateTime, Utc};
41use egui::Color32;
42use std::collections::VecDeque;
43
44#[derive(Clone)]
46pub struct ElliottWave {
47 left: usize,
48 right: usize,
49 history: usize,
50 values: IndicatorValues,
51 stepper: Stepper<State>,
52 table: StudyTable,
53 colors: [Color32; 3],
54 visible: bool,
55}
56
57#[derive(Clone)]
59struct Construction {
60 anchors: Vec<Swing>,
61 confirmed: Option<DateTime<Utc>>,
62}
63
64impl Construction {
65 fn bullish(&self) -> bool {
66 !self.anchors[0].high
67 }
68
69 fn wave(&self) -> usize {
71 self.anchors.len() - 1
72 }
73}
74
75#[derive(Clone, Default)]
76struct State {
77 swings: Swings,
78 developing: Option<Construction>,
79 confirmed: VecDeque<Construction>,
80}
81
82fn construct(pivots: &VecDeque<Swing>) -> Option<Construction> {
84 (3..=5).rev().find_map(|waves| {
85 let anchors: Vec<_> = pivots
86 .range(pivots.len().checked_sub(waves + 1)?..)
87 .copied()
88 .collect();
89 let sign = if anchors[0].high { -1.0 } else { 1.0 };
91 let p = |n: usize| sign * anchors[n].price;
92 let wave = |n: usize| p(n) - p(n - 1);
93 let valid = p(2) > p(0)
94 && p(3) > p(1)
95 && (waves < 4 || p(4) > p(1))
96 && (waves < 5 || wave(3) >= wave(1) || wave(3) >= wave(5));
97 valid.then_some(Construction {
98 anchors,
99 confirmed: None,
100 })
101 })
102}
103
104impl ElliottWave {
105 pub fn new(left: usize, right: usize, history: usize) -> Self {
108 Self {
109 left: left.max(1),
110 right: right.max(1),
111 history: history.clamp(1, 100),
112 values: IndicatorValues::new(),
113 stepper: Stepper::default(),
114 table: StudyTable {
115 title: "Elliott Wave".into(),
116 columns: ["Impulse", "Confirmed", "Anchors"]
117 .map(String::from)
118 .to_vec(),
119 rows: Vec::new(),
120 },
121 colors: [palette::ACCENT, palette::GREEN, palette::RED],
122 visible: true,
123 }
124 }
125}
126
127impl Default for ElliottWave {
129 fn default() -> Self {
130 Self::new(5, 5, 5)
131 }
132}
133
134impl Indicator for ElliottWave {
135 crate::studies::inputs::study_inputs!(self;
136 left: length("Pivot bars left", 1, usize::MAX),
137 right: length("Pivot bars right", 1, usize::MAX),
138 history: length("Confirmed impulses listed", 1, 100)
139 ; {});
140
141 fn name(&self) -> &str {
142 "Elliott Wave"
143 }
144
145 fn desc(&self) -> &str {
146 "Elliott Wave - Five-wave impulses in confirmed swing pivots, developing and confirmed"
147 }
148
149 fn calculate(&mut self, data: &[Bar]) {
150 self.update(data, 0);
151 }
152
153 fn update(&mut self, data: &[Bar], from: usize) {
154 let mut stepper = std::mem::take(&mut self.stepper);
155 let mut values = std::mem::take(&mut self.values);
156 stepper.run(data, from, &mut values, |state, bar| {
157 if ![bar.high, bar.low, bar.close].iter().all(|v| v.is_finite()) {
158 state.swings.skip();
159 return IndicatorValue::None;
160 }
161 let mut row = [f64::NAN; 4];
162 if state.swings.push(bar, self.left, self.right, 6) {
163 state.developing = construct(&state.swings.pivots);
164 if let Some(mut construction) = state.developing.take() {
165 let sign = if construction.bullish() { 1.0 } else { -1.0 };
166 let last = construction.anchors[construction.wave()].price;
167 row[3] = sign * construction.wave() as f64;
168 if construction.wave() == 5 {
169 row[if construction.bullish() { 1 } else { 2 }] = last;
170 construction.confirmed = Some(bar.time);
171 state.confirmed.push_front(construction);
172 state.confirmed.truncate(self.history);
173 while state.swings.pivots.len() > 1 {
175 state.swings.pivots.pop_front();
176 }
177 } else {
178 row[0] = last;
179 state.developing = Some(construction);
180 }
181 }
182 } else if let Some(construction) = &state.developing {
183 let wave_1_end = construction.anchors[1].price;
185 if (construction.bullish() && bar.close < wave_1_end)
186 || (!construction.bullish() && bar.close > wave_1_end)
187 {
188 state.developing = None;
189 }
190 }
191 IndicatorValue::Multiple(row.to_vec())
192 });
193 let state = stepper.state();
194 let time = |time: DateTime<Utc>| time.format("%Y-%m-%d %H:%M").to_string();
195 let describe = |construction: &Construction| {
196 let anchors = construction
197 .anchors
198 .iter()
199 .enumerate()
200 .map(|(n, anchor)| format!("{n}:{:.2}", anchor.price))
201 .collect::<Vec<_>>()
202 .join(" ");
203 let direction = if construction.bullish() {
204 "Bullish"
205 } else {
206 "Bearish"
207 };
208 let confirmed = construction.confirmed.map_or("-".into(), time);
209 vec![
210 StudyCell::Text(format!("{direction}, wave {}", construction.wave())),
211 StudyCell::Text(confirmed),
212 StudyCell::Text(anchors),
213 ]
214 };
215 self.table.rows = state
216 .developing
217 .iter()
218 .chain(&state.confirmed)
219 .map(describe)
220 .collect();
221 if self.table.rows.is_empty() {
222 let text = |text: &str| StudyCell::Text(text.into());
223 self.table
224 .rows
225 .push(vec![text("No impulse"), text("-"), text("-")]);
226 }
227 self.stepper = stepper;
228 self.values = values;
229 }
230
231 fn is_incremental(&self) -> bool {
232 true
233 }
234
235 fn table(&self) -> Option<&StudyTable> {
236 Some(&self.table)
237 }
238
239 fn plot_offset(&self, line: usize) -> isize {
241 if line < 3 {
242 -(self.right.min(isize::MAX as usize) as isize)
243 } else {
244 0
245 }
246 }
247
248 fn plot_color(&self, line: usize, bar: usize) -> Option<Color32> {
249 (line == 0).then(|| {
250 let wave = self.values.value(3, bar)?;
251 Some(if wave > 0.0 {
252 self.colors[1]
253 } else {
254 self.colors[2]
255 })
256 })?
257 }
258
259 fn plot_text(&self, line: usize, bar: usize) -> Option<String> {
260 self.values.value(line, bar)?;
261 let wave = self.values.value(3, bar)?;
262 (line < 3).then(|| format!("{}", wave.abs()))
263 }
264
265 fn plot_style(&self, line: usize) -> crate::studies::PlotStyle {
266 crate::studies::PlotStyle {
267 color: self.colors[line.min(2)],
268 width: 1.0,
269 pattern: crate::model::LineStyle::Solid,
270 plot_type: match line {
271 0 => crate::studies::PlotType::Circles,
272 1 => crate::studies::PlotType::TriangleUp,
273 _ => crate::studies::PlotType::TriangleDown,
274 },
275 visible: line < 3,
276 }
277 }
278
279 fn values(&self) -> &IndicatorValues {
280 &self.values
281 }
282
283 fn colors(&self) -> Vec<Color32> {
284 self.colors.to_vec()
285 }
286
287 fn set_colors(&mut self, colors: Vec<Color32>) {
288 for (old, new) in self.colors.iter_mut().zip(colors) {
289 *old = new;
290 }
291 }
292
293 fn is_visible(&self) -> bool {
294 self.visible
295 }
296
297 fn set_visible(&mut self, visible: bool) {
298 self.visible = visible;
299 }
300
301 fn clone_box(&self) -> Box<dyn Indicator> {
302 Box::new(self.clone())
303 }
304
305 fn line_names(&self) -> Vec<String> {
306 [
307 "Developing Anchor",
308 "Confirmed Bullish Impulse",
309 "Confirmed Bearish Impulse",
310 "Wave",
311 ]
312 .map(String::from)
313 .to_vec()
314 }
315}
316
317#[cfg(test)]
318mod tests {
319 use super::*;
320
321 const IMPULSE: [f64; 13] = [
322 105.0, 100.0, 105.0, 110.0, 107.0, 104.0, 115.0, 125.0, 120.0, 115.0, 122.0, 130.0, 128.0,
323 ];
324
325 fn run(closes: &[f64]) -> (Vec<Bar>, ElliottWave) {
326 let hlc: Vec<_> = closes.iter().map(|&c| (c, c, c)).collect();
327 let bars = super::super::hlc_bars(&hlc);
328 let mut study = ElliottWave::new(1, 1, 5);
329 study.calculate(&bars);
330 (bars, study)
331 }
332
333 fn column(study: &ElliottWave, line: usize, len: usize) -> Vec<Option<f64>> {
334 (0..len).map(|at| study.values().value(line, at)).collect()
335 }
336
337 #[test]
342 fn impulses_are_developing_then_confirmed_without_changes() {
343 let none = None;
344 for sign in [1.0, -1.0] {
345 let closes = IMPULSE.map(|c| sign * c);
346 let (bars, study) = run(&closes);
347 super::super::assert_replay_matches_batch(&study, &bars);
348 let anchor = |price: f64| Some(sign * price);
349 assert_eq!(
350 column(&study, 0, 13),
351 [
352 none,
353 none,
354 none,
355 none,
356 none,
357 none,
358 none,
359 none,
360 anchor(125.0),
361 none,
362 anchor(115.0),
363 none,
364 none
365 ]
366 );
367 let confirmed = column(&study, if sign > 0.0 { 1 } else { 2 }, 13);
368 assert_eq!(confirmed[..12], [none; 12]);
369 assert_eq!(confirmed[12], anchor(130.0));
370 assert_eq!(
371 column(&study, if sign > 0.0 { 2 } else { 1 }, 13),
372 [none; 13]
373 );
374 assert_eq!(study.plot_text(0, 8).as_deref(), Some("3"));
375 assert_eq!(
376 study
377 .plot_text(if sign > 0.0 { 1 } else { 2 }, 12)
378 .as_deref(),
379 Some("5")
380 );
381 assert_eq!(study.plot_offset(0), -1);
382 let rows = &study.table().unwrap().rows;
383 assert_eq!(rows.len(), 1);
384 let StudyCell::Text(anchors) = &rows[0][2] else {
385 panic!()
386 };
387 let expected: Vec<_> = [100.0, 110.0, 104.0, 125.0, 115.0, 130.0]
388 .iter()
389 .enumerate()
390 .map(|(n, p)| format!("{n}:{:.2}", sign * p))
391 .collect();
392 assert_eq!(anchors, &expected.join(" "));
394 assert_eq!(rows[0][1], StudyCell::Text("1970-01-13 00:00".into()));
395 }
396 }
397
398 #[test]
402 fn invalid_sequences_are_not_confirmed() {
403 let (_, study) = run(&[
404 105.0, 100.0, 105.0, 110.0, 107.0, 104.0, 108.0, 112.0, 111.5, 111.0, 120.0, 130.0,
405 128.0,
406 ]);
407 assert_eq!(column(&study, 1, 13), [None; 13]);
408 assert_eq!(study.values().value(0, 12), Some(130.0));
409 assert_eq!(study.values().value(3, 12), Some(3.0));
410 let (_, study) = run(&[
412 105.0, 100.0, 105.0, 110.0, 107.0, 104.0, 115.0, 125.0, 120.0, 108.0,
413 ]);
414 assert_eq!(study.values().value(0, 8), Some(125.0));
415 let rows = &study.table().unwrap().rows;
416 assert_eq!(rows.len(), 1);
417 assert_eq!(rows[0][0], StudyCell::Text("No impulse".into()));
418 }
419}