wickra_core/indicators/
fib_retracement.rs1use crate::indicators::pattern_swing::{SwingTracker, SWING_THRESHOLD};
4use crate::ohlcv::Candle;
5use crate::traits::Indicator;
6
7const RATIOS: [f64; 7] = [0.0, 0.236, 0.382, 0.5, 0.618, 0.786, 1.0];
11
12#[derive(Debug, Clone, Copy, PartialEq)]
18pub struct FibRetracementOutput {
19 pub level_0: f64,
21 pub level_236: f64,
23 pub level_382: f64,
25 pub level_500: f64,
27 pub level_618: f64,
29 pub level_786: f64,
31 pub level_1000: f64,
33}
34
35#[derive(Debug, Clone)]
50pub struct FibRetracement {
51 swing: SwingTracker,
52}
53
54impl FibRetracement {
55 #[must_use]
57 pub const fn new() -> Self {
58 Self {
59 swing: SwingTracker::new(SWING_THRESHOLD, 2),
60 }
61 }
62
63 fn level(start: f64, end: f64, r: f64) -> f64 {
66 end + r * (start - end)
67 }
68
69 fn levels(&self) -> Option<FibRetracementOutput> {
70 let pivots = self.swing.pivots();
71 let [start, end] = [pivots.first()?.price, pivots.get(1)?.price];
72 Some(FibRetracementOutput {
73 level_0: Self::level(start, end, RATIOS[0]),
74 level_236: Self::level(start, end, RATIOS[1]),
75 level_382: Self::level(start, end, RATIOS[2]),
76 level_500: Self::level(start, end, RATIOS[3]),
77 level_618: Self::level(start, end, RATIOS[4]),
78 level_786: Self::level(start, end, RATIOS[5]),
79 level_1000: Self::level(start, end, RATIOS[6]),
80 })
81 }
82}
83
84impl Default for FibRetracement {
85 fn default() -> Self {
86 Self::new()
87 }
88}
89
90impl Indicator for FibRetracement {
91 type Input = Candle;
92 type Output = FibRetracementOutput;
93
94 fn update(&mut self, candle: Candle) -> Option<FibRetracementOutput> {
95 self.swing.update(candle);
96 self.levels()
97 }
98
99 fn reset(&mut self) {
100 self.swing.reset();
101 }
102
103 fn warmup_period(&self) -> usize {
104 2
105 }
106
107 fn is_ready(&self) -> bool {
108 self.swing.pivots().len() >= 2
109 }
110
111 fn name(&self) -> &'static str {
112 "FibRetracement"
113 }
114}
115
116#[cfg(test)]
117mod tests {
118 use super::*;
119 use crate::indicators::pattern_swing::candles_for_pivots;
120 use crate::traits::BatchExt;
121 use approx::assert_relative_eq;
122
123 #[test]
124 fn accessors_and_metadata() {
125 let indicator = FibRetracement::new();
126 assert_eq!(indicator.name(), "FibRetracement");
127 assert_eq!(indicator.warmup_period(), 2);
128 assert!(!indicator.is_ready());
129 assert!(!FibRetracement::default().is_ready());
130 }
131
132 #[test]
133 fn no_output_before_two_pivots() {
134 let mut indicator = FibRetracement::new();
135 let candles = candles_for_pivots(&[120.0]);
137 let outputs: Vec<_> = candles.into_iter().map(|c| indicator.update(c)).collect();
138 assert!(outputs.iter().all(Option::is_none));
139 assert!(!indicator.is_ready());
140 }
141
142 #[test]
143 fn retracement_levels_of_a_down_leg() {
144 let mut indicator = FibRetracement::new();
146 let mut last = None;
147 for candle in candles_for_pivots(&[200.0, 100.0]) {
148 last = indicator.update(candle);
149 }
150 let v = last.unwrap();
151 assert!(indicator.is_ready());
152 assert_relative_eq!(v.level_0, 100.0);
154 assert_relative_eq!(v.level_1000, 200.0);
155 assert_relative_eq!(v.level_618, 161.8);
157 assert_relative_eq!(v.level_500, 150.0);
158 assert_relative_eq!(v.level_382, 138.2);
159 assert_relative_eq!(v.level_236, 123.6);
160 assert_relative_eq!(v.level_786, 178.6);
161 }
162
163 #[test]
164 fn levels_refresh_on_a_new_leg() {
165 let mut indicator = FibRetracement::new();
168 let mut last = None;
169 for candle in candles_for_pivots(&[200.0, 100.0, 130.0, 90.0]) {
170 last = indicator.update(candle);
171 }
172 let v = last.unwrap();
173 assert_relative_eq!(v.level_0, 90.0);
174 assert_relative_eq!(v.level_1000, 130.0);
175 assert_relative_eq!(v.level_618, 90.0 + 0.618 * 40.0);
176 }
177
178 #[test]
179 fn reset_clears_state() {
180 let mut indicator = FibRetracement::new();
181 for candle in candles_for_pivots(&[200.0, 100.0]) {
182 let _ = indicator.update(candle);
183 }
184 assert!(indicator.is_ready());
185 indicator.reset();
186 assert!(!indicator.is_ready());
187 let c = Candle::new(99.5, 100.0, 99.5, 99.5, 1.0, 0).unwrap();
188 assert!(indicator.update(c).is_none());
189 }
190
191 #[test]
192 fn batch_equals_streaming() {
193 let candles = candles_for_pivots(&[200.0, 100.0, 150.0]);
194 let mut a = FibRetracement::new();
195 let mut b = FibRetracement::new();
196 assert_eq!(
197 a.batch(&candles),
198 candles.iter().map(|x| b.update(*x)).collect::<Vec<_>>()
199 );
200 }
201}