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 #[inline]
110 fn update(&mut self, candle: Candle) -> Option<FibRetracementOutput> {
111 self.swing.update(candle);
112 self.levels()
113 }
114
115 fn reset(&mut self) {
116 self.swing.reset();
117 }
118
119 #[inline]
120 fn warmup_period(&self) -> usize {
121 2
122 }
123
124 #[inline]
125 fn is_ready(&self) -> bool {
126 self.swing.pivots().len() >= 2
127 }
128
129 #[inline]
130 fn name(&self) -> &'static str {
131 "FibRetracement"
132 }
133}
134
135#[cfg(test)]
136mod tests {
137 use super::*;
138 use crate::indicators::pattern_swing::candles_for_pivots;
139 use crate::traits::BatchExt;
140 use approx::assert_relative_eq;
141
142 #[test]
143 fn accessors_and_metadata() {
144 let indicator = FibRetracement::new();
145 assert_eq!(indicator.name(), "FibRetracement");
146 assert_eq!(indicator.warmup_period(), 2);
147 assert!(!indicator.is_ready());
148 assert!(!FibRetracement::default().is_ready());
149 }
150
151 #[test]
152 fn no_output_before_two_pivots() {
153 let mut indicator = FibRetracement::new();
154 let candles = candles_for_pivots(&[120.0]);
156 let outputs: Vec<_> = candles.into_iter().map(|c| indicator.update(c)).collect();
157 assert!(outputs.iter().all(Option::is_none));
158 assert!(!indicator.is_ready());
159 }
160
161 #[test]
162 fn retracement_levels_of_a_down_leg() {
163 let mut indicator = FibRetracement::new();
165 let mut last = None;
166 for candle in candles_for_pivots(&[200.0, 100.0]) {
167 last = indicator.update(candle);
168 }
169 let v = last.unwrap();
170 assert!(indicator.is_ready());
171 assert_relative_eq!(v.level_0, 100.0);
173 assert_relative_eq!(v.level_1000, 200.0);
174 assert_relative_eq!(v.level_618, 161.8);
176 assert_relative_eq!(v.level_500, 150.0);
177 assert_relative_eq!(v.level_382, 138.2);
178 assert_relative_eq!(v.level_236, 123.6);
179 assert_relative_eq!(v.level_786, 178.6);
180 }
181
182 #[test]
183 fn levels_refresh_on_a_new_leg() {
184 let mut indicator = FibRetracement::new();
187 let mut last = None;
188 for candle in candles_for_pivots(&[200.0, 100.0, 130.0, 90.0]) {
189 last = indicator.update(candle);
190 }
191 let v = last.unwrap();
192 assert_relative_eq!(v.level_0, 90.0);
193 assert_relative_eq!(v.level_1000, 130.0);
194 assert_relative_eq!(v.level_618, 90.0 + 0.618 * 40.0);
195 }
196
197 #[test]
198 fn reset_clears_state() {
199 let mut indicator = FibRetracement::new();
200 for candle in candles_for_pivots(&[200.0, 100.0]) {
201 let _ = indicator.update(candle);
202 }
203 assert!(indicator.is_ready());
204 indicator.reset();
205 assert!(!indicator.is_ready());
206 let c = Candle::new(99.5, 100.0, 99.5, 99.5, 1.0, 0).unwrap();
207 assert!(indicator.update(c).is_none());
208 }
209
210 #[test]
211 fn batch_equals_streaming() {
212 let candles = candles_for_pivots(&[200.0, 100.0, 150.0]);
213 let mut a = FibRetracement::new();
214 let mut b = FibRetracement::new();
215 assert_eq!(
216 a.batch(&candles),
217 candles.iter().map(|x| b.update(*x)).collect::<Vec<_>>()
218 );
219 }
220}