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)]
65pub struct FibRetracement {
66 swing: SwingTracker,
67}
68
69impl FibRetracement {
70 #[must_use]
72 pub const fn new() -> Self {
73 Self {
74 swing: SwingTracker::new(SWING_THRESHOLD, 2),
75 }
76 }
77
78 fn level(start: f64, end: f64, r: f64) -> f64 {
81 end + r * (start - end)
82 }
83
84 fn levels(&self) -> Option<FibRetracementOutput> {
85 let pivots = self.swing.pivots();
86 let [start, end] = [pivots.first()?.price, pivots.get(1)?.price];
87 Some(FibRetracementOutput {
88 level_0: Self::level(start, end, RATIOS[0]),
89 level_236: Self::level(start, end, RATIOS[1]),
90 level_382: Self::level(start, end, RATIOS[2]),
91 level_500: Self::level(start, end, RATIOS[3]),
92 level_618: Self::level(start, end, RATIOS[4]),
93 level_786: Self::level(start, end, RATIOS[5]),
94 level_1000: Self::level(start, end, RATIOS[6]),
95 })
96 }
97}
98
99impl Default for FibRetracement {
100 fn default() -> Self {
101 Self::new()
102 }
103}
104
105impl Indicator for FibRetracement {
106 type Input = Candle;
107 type Output = FibRetracementOutput;
108
109 fn update(&mut self, candle: Candle) -> Option<FibRetracementOutput> {
110 self.swing.update(candle);
111 self.levels()
112 }
113
114 fn reset(&mut self) {
115 self.swing.reset();
116 }
117
118 fn warmup_period(&self) -> usize {
119 2
120 }
121
122 fn is_ready(&self) -> bool {
123 self.swing.pivots().len() >= 2
124 }
125
126 fn name(&self) -> &'static str {
127 "FibRetracement"
128 }
129}
130
131#[cfg(test)]
132mod tests {
133 use super::*;
134 use crate::indicators::pattern_swing::candles_for_pivots;
135 use crate::traits::BatchExt;
136 use approx::assert_relative_eq;
137
138 #[test]
139 fn accessors_and_metadata() {
140 let indicator = FibRetracement::new();
141 assert_eq!(indicator.name(), "FibRetracement");
142 assert_eq!(indicator.warmup_period(), 2);
143 assert!(!indicator.is_ready());
144 assert!(!FibRetracement::default().is_ready());
145 }
146
147 #[test]
148 fn no_output_before_two_pivots() {
149 let mut indicator = FibRetracement::new();
150 let candles = candles_for_pivots(&[120.0]);
152 let outputs: Vec<_> = candles.into_iter().map(|c| indicator.update(c)).collect();
153 assert!(outputs.iter().all(Option::is_none));
154 assert!(!indicator.is_ready());
155 }
156
157 #[test]
158 fn retracement_levels_of_a_down_leg() {
159 let mut indicator = FibRetracement::new();
161 let mut last = None;
162 for candle in candles_for_pivots(&[200.0, 100.0]) {
163 last = indicator.update(candle);
164 }
165 let v = last.unwrap();
166 assert!(indicator.is_ready());
167 assert_relative_eq!(v.level_0, 100.0);
169 assert_relative_eq!(v.level_1000, 200.0);
170 assert_relative_eq!(v.level_618, 161.8);
172 assert_relative_eq!(v.level_500, 150.0);
173 assert_relative_eq!(v.level_382, 138.2);
174 assert_relative_eq!(v.level_236, 123.6);
175 assert_relative_eq!(v.level_786, 178.6);
176 }
177
178 #[test]
179 fn levels_refresh_on_a_new_leg() {
180 let mut indicator = FibRetracement::new();
183 let mut last = None;
184 for candle in candles_for_pivots(&[200.0, 100.0, 130.0, 90.0]) {
185 last = indicator.update(candle);
186 }
187 let v = last.unwrap();
188 assert_relative_eq!(v.level_0, 90.0);
189 assert_relative_eq!(v.level_1000, 130.0);
190 assert_relative_eq!(v.level_618, 90.0 + 0.618 * 40.0);
191 }
192
193 #[test]
194 fn reset_clears_state() {
195 let mut indicator = FibRetracement::new();
196 for candle in candles_for_pivots(&[200.0, 100.0]) {
197 let _ = indicator.update(candle);
198 }
199 assert!(indicator.is_ready());
200 indicator.reset();
201 assert!(!indicator.is_ready());
202 let c = Candle::new(99.5, 100.0, 99.5, 99.5, 1.0, 0).unwrap();
203 assert!(indicator.update(c).is_none());
204 }
205
206 #[test]
207 fn batch_equals_streaming() {
208 let candles = candles_for_pivots(&[200.0, 100.0, 150.0]);
209 let mut a = FibRetracement::new();
210 let mut b = FibRetracement::new();
211 assert_eq!(
212 a.batch(&candles),
213 candles.iter().map(|x| b.update(*x)).collect::<Vec<_>>()
214 );
215 }
216}