wickra_core/indicators/
fib_projection.rs1use crate::indicators::pattern_swing::{SwingTracker, SWING_THRESHOLD};
4use crate::ohlcv::Candle;
5use crate::traits::Indicator;
6
7const RATIOS: [f64; 4] = [0.618, 1.0, 1.618, 2.618];
10
11#[derive(Debug, Clone, Copy, PartialEq)]
13pub struct FibProjectionOutput {
14 pub level_618: f64,
16 pub level_1000: f64,
18 pub level_1618: f64,
20 pub level_2618: f64,
22}
23
24#[derive(Debug, Clone)]
50pub struct FibProjection {
51 swing: SwingTracker,
52}
53
54impl FibProjection {
55 #[must_use]
57 pub const fn new() -> Self {
58 Self {
59 swing: SwingTracker::new(SWING_THRESHOLD, 3),
60 }
61 }
62
63 fn levels(&self) -> Option<FibProjectionOutput> {
64 let pivots = self.swing.pivots();
65 let [a, b, c] = [
66 pivots.first()?.price,
67 pivots.get(1)?.price,
68 pivots.get(2)?.price,
69 ];
70 let project = |p: f64| c + p * (b - a);
71 Some(FibProjectionOutput {
72 level_618: project(RATIOS[0]),
73 level_1000: project(RATIOS[1]),
74 level_1618: project(RATIOS[2]),
75 level_2618: project(RATIOS[3]),
76 })
77 }
78}
79
80impl Default for FibProjection {
81 fn default() -> Self {
82 Self::new()
83 }
84}
85
86impl Indicator for FibProjection {
87 type Input = Candle;
88 type Output = FibProjectionOutput;
89
90 #[inline]
91 fn update(&mut self, candle: Candle) -> Option<FibProjectionOutput> {
92 self.swing.update(candle);
93 self.levels()
94 }
95
96 fn reset(&mut self) {
97 self.swing.reset();
98 }
99
100 #[inline]
101 fn warmup_period(&self) -> usize {
102 3
103 }
104
105 #[inline]
106 fn is_ready(&self) -> bool {
107 self.swing.pivots().len() >= 3
108 }
109
110 #[inline]
111 fn name(&self) -> &'static str {
112 "FibProjection"
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 = FibProjection::new();
126 assert_eq!(indicator.name(), "FibProjection");
127 assert_eq!(indicator.warmup_period(), 3);
128 assert!(!indicator.is_ready());
129 assert!(!FibProjection::default().is_ready());
130 }
131
132 #[test]
133 fn no_output_before_three_pivots() {
134 let mut indicator = FibProjection::new();
135 let outputs: Vec<_> = candles_for_pivots(&[200.0, 100.0])
136 .into_iter()
137 .map(|c| indicator.update(c))
138 .collect();
139 assert!(outputs.iter().all(Option::is_none));
140 assert!(!indicator.is_ready());
141 }
142
143 #[test]
144 fn measured_move_from_three_pivots() {
145 let mut indicator = FibProjection::new();
148 let mut last = None;
149 for candle in candles_for_pivots(&[200.0, 160.0, 190.0]) {
150 last = indicator.update(candle);
151 }
152 let v = last.unwrap();
153 assert!(indicator.is_ready());
154 let (a, b, c) = (200.0, 160.0, 190.0);
155 assert_relative_eq!(v.level_618, c + 0.618 * (b - a));
156 assert_relative_eq!(v.level_1000, c + (b - a));
157 assert_relative_eq!(v.level_1618, c + 1.618 * (b - a));
158 assert_relative_eq!(v.level_2618, c + 2.618 * (b - a));
159 }
160
161 #[test]
162 fn reset_clears_state() {
163 let mut indicator = FibProjection::new();
164 for candle in candles_for_pivots(&[200.0, 160.0, 190.0]) {
165 let _ = indicator.update(candle);
166 }
167 assert!(indicator.is_ready());
168 indicator.reset();
169 assert!(!indicator.is_ready());
170 let c = Candle::new(99.5, 100.0, 99.5, 99.5, 1.0, 0).unwrap();
171 assert!(indicator.update(c).is_none());
172 }
173
174 #[test]
175 fn batch_equals_streaming() {
176 let candles = candles_for_pivots(&[200.0, 160.0, 190.0, 150.0]);
177 let mut a = FibProjection::new();
178 let mut b = FibProjection::new();
179 assert_eq!(
180 a.batch(&candles),
181 candles.iter().map(|x| b.update(*x)).collect::<Vec<_>>()
182 );
183 }
184}