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)]
41pub struct FibExtension {
42 swing: SwingTracker,
43}
44
45impl FibExtension {
46 #[must_use]
48 pub const fn new() -> Self {
49 Self {
50 swing: SwingTracker::new(SWING_THRESHOLD, 2),
51 }
52 }
53
54 fn level(start: f64, end: f64, e: f64) -> f64 {
57 start + e * (end - start)
58 }
59
60 fn levels(&self) -> Option<FibExtensionOutput> {
61 let pivots = self.swing.pivots();
62 let [start, end] = [pivots.first()?.price, pivots.get(1)?.price];
63 Some(FibExtensionOutput {
64 level_1272: Self::level(start, end, RATIOS[0]),
65 level_1414: Self::level(start, end, RATIOS[1]),
66 level_1618: Self::level(start, end, RATIOS[2]),
67 level_2000: Self::level(start, end, RATIOS[3]),
68 level_2618: Self::level(start, end, RATIOS[4]),
69 })
70 }
71}
72
73impl Default for FibExtension {
74 fn default() -> Self {
75 Self::new()
76 }
77}
78
79impl Indicator for FibExtension {
80 type Input = Candle;
81 type Output = FibExtensionOutput;
82
83 fn update(&mut self, candle: Candle) -> Option<FibExtensionOutput> {
84 self.swing.update(candle);
85 self.levels()
86 }
87
88 fn reset(&mut self) {
89 self.swing.reset();
90 }
91
92 fn warmup_period(&self) -> usize {
93 2
94 }
95
96 fn is_ready(&self) -> bool {
97 self.swing.pivots().len() >= 2
98 }
99
100 fn name(&self) -> &'static str {
101 "FibExtension"
102 }
103}
104
105#[cfg(test)]
106mod tests {
107 use super::*;
108 use crate::indicators::pattern_swing::candles_for_pivots;
109 use crate::traits::BatchExt;
110 use approx::assert_relative_eq;
111
112 #[test]
113 fn accessors_and_metadata() {
114 let indicator = FibExtension::new();
115 assert_eq!(indicator.name(), "FibExtension");
116 assert_eq!(indicator.warmup_period(), 2);
117 assert!(!indicator.is_ready());
118 assert!(!FibExtension::default().is_ready());
119 }
120
121 #[test]
122 fn no_output_before_two_pivots() {
123 let mut indicator = FibExtension::new();
124 let outputs: Vec<_> = candles_for_pivots(&[120.0])
125 .into_iter()
126 .map(|c| indicator.update(c))
127 .collect();
128 assert!(outputs.iter().all(Option::is_none));
129 }
130
131 #[test]
132 fn extension_levels_of_a_down_leg() {
133 let mut indicator = FibExtension::new();
135 let mut last = None;
136 for candle in candles_for_pivots(&[200.0, 100.0]) {
137 last = indicator.update(candle);
138 }
139 let v = last.unwrap();
140 assert!(indicator.is_ready());
141 assert_relative_eq!(v.level_1272, 200.0 - 127.2);
143 assert_relative_eq!(v.level_1414, 200.0 - 141.4);
144 assert_relative_eq!(v.level_1618, 200.0 - 161.8);
145 assert_relative_eq!(v.level_2000, 0.0);
146 assert_relative_eq!(v.level_2618, 200.0 - 261.8);
147 }
148
149 #[test]
150 fn reset_clears_state() {
151 let mut indicator = FibExtension::new();
152 for candle in candles_for_pivots(&[200.0, 100.0]) {
153 let _ = indicator.update(candle);
154 }
155 indicator.reset();
156 assert!(!indicator.is_ready());
157 let c = Candle::new(99.5, 100.0, 99.5, 99.5, 1.0, 0).unwrap();
158 assert!(indicator.update(c).is_none());
159 }
160
161 #[test]
162 fn batch_equals_streaming() {
163 let candles = candles_for_pivots(&[200.0, 100.0, 150.0]);
164 let mut a = FibExtension::new();
165 let mut b = FibExtension::new();
166 assert_eq!(
167 a.batch(&candles),
168 candles.iter().map(|x| b.update(*x)).collect::<Vec<_>>()
169 );
170 }
171}