wickra_core/indicators/
fib_extension.rs1use crate::indicators::pattern_swing::{SwingTracker, SWING_THRESHOLD};
4use crate::ohlcv::Candle;
5use crate::traits::Indicator;
6
7const RATIOS: [f64; 5] = [1.272, 1.414, 1.618, 2.0, 2.618];
11
12#[derive(Debug, Clone, Copy, PartialEq)]
17pub struct FibExtensionOutput {
18 pub level_1272: f64,
20 pub level_1414: f64,
22 pub level_1618: f64,
24 pub level_2000: f64,
26 pub level_2618: f64,
28}
29
30#[derive(Debug, Clone)]
56pub struct FibExtension {
57 swing: SwingTracker,
58}
59
60impl FibExtension {
61 #[must_use]
63 pub const fn new() -> Self {
64 Self {
65 swing: SwingTracker::new(SWING_THRESHOLD, 2),
66 }
67 }
68
69 fn level(start: f64, end: f64, e: f64) -> f64 {
72 start + e * (end - start)
73 }
74
75 fn levels(&self) -> Option<FibExtensionOutput> {
76 let pivots = self.swing.pivots();
77 let [start, end] = [pivots.first()?.price, pivots.get(1)?.price];
78 Some(FibExtensionOutput {
79 level_1272: Self::level(start, end, RATIOS[0]),
80 level_1414: Self::level(start, end, RATIOS[1]),
81 level_1618: Self::level(start, end, RATIOS[2]),
82 level_2000: Self::level(start, end, RATIOS[3]),
83 level_2618: Self::level(start, end, RATIOS[4]),
84 })
85 }
86}
87
88impl Default for FibExtension {
89 fn default() -> Self {
90 Self::new()
91 }
92}
93
94impl Indicator for FibExtension {
95 type Input = Candle;
96 type Output = FibExtensionOutput;
97
98 fn update(&mut self, candle: Candle) -> Option<FibExtensionOutput> {
99 self.swing.update(candle);
100 self.levels()
101 }
102
103 fn reset(&mut self) {
104 self.swing.reset();
105 }
106
107 fn warmup_period(&self) -> usize {
108 2
109 }
110
111 fn is_ready(&self) -> bool {
112 self.swing.pivots().len() >= 2
113 }
114
115 fn name(&self) -> &'static str {
116 "FibExtension"
117 }
118}
119
120#[cfg(test)]
121mod tests {
122 use super::*;
123 use crate::indicators::pattern_swing::candles_for_pivots;
124 use crate::traits::BatchExt;
125 use approx::assert_relative_eq;
126
127 #[test]
128 fn accessors_and_metadata() {
129 let indicator = FibExtension::new();
130 assert_eq!(indicator.name(), "FibExtension");
131 assert_eq!(indicator.warmup_period(), 2);
132 assert!(!indicator.is_ready());
133 assert!(!FibExtension::default().is_ready());
134 }
135
136 #[test]
137 fn no_output_before_two_pivots() {
138 let mut indicator = FibExtension::new();
139 let outputs: Vec<_> = candles_for_pivots(&[120.0])
140 .into_iter()
141 .map(|c| indicator.update(c))
142 .collect();
143 assert!(outputs.iter().all(Option::is_none));
144 }
145
146 #[test]
147 fn extension_levels_of_a_down_leg() {
148 let mut indicator = FibExtension::new();
150 let mut last = None;
151 for candle in candles_for_pivots(&[200.0, 100.0]) {
152 last = indicator.update(candle);
153 }
154 let v = last.unwrap();
155 assert!(indicator.is_ready());
156 assert_relative_eq!(v.level_1272, 200.0 - 127.2);
158 assert_relative_eq!(v.level_1414, 200.0 - 141.4);
159 assert_relative_eq!(v.level_1618, 200.0 - 161.8);
160 assert_relative_eq!(v.level_2000, 0.0);
161 assert_relative_eq!(v.level_2618, 200.0 - 261.8);
162 }
163
164 #[test]
165 fn reset_clears_state() {
166 let mut indicator = FibExtension::new();
167 for candle in candles_for_pivots(&[200.0, 100.0]) {
168 let _ = indicator.update(candle);
169 }
170 indicator.reset();
171 assert!(!indicator.is_ready());
172 let c = Candle::new(99.5, 100.0, 99.5, 99.5, 1.0, 0).unwrap();
173 assert!(indicator.update(c).is_none());
174 }
175
176 #[test]
177 fn batch_equals_streaming() {
178 let candles = candles_for_pivots(&[200.0, 100.0, 150.0]);
179 let mut a = FibExtension::new();
180 let mut b = FibExtension::new();
181 assert_eq!(
182 a.batch(&candles),
183 candles.iter().map(|x| b.update(*x)).collect::<Vec<_>>()
184 );
185 }
186}