wickra_core/indicators/
fibonacci_pivots.rs1use crate::ohlcv::Candle;
4use crate::traits::Indicator;
5
6#[derive(Debug, Clone, Copy, PartialEq)]
9pub struct FibonacciPivotsOutput {
10 pub pp: f64,
12 pub r1: f64,
14 pub r2: f64,
16 pub r3: f64,
18 pub s1: f64,
20 pub s2: f64,
22 pub s3: f64,
24}
25
26#[derive(Debug, Clone, Default)]
52pub struct FibonacciPivots {
53 ready: bool,
54}
55
56impl FibonacciPivots {
57 pub const fn new() -> Self {
59 Self { ready: false }
60 }
61}
62
63const FIB1: f64 = 0.382;
64const FIB2: f64 = 0.618;
65const FIB3: f64 = 1.000;
66
67impl Indicator for FibonacciPivots {
68 type Input = Candle;
69 type Output = FibonacciPivotsOutput;
70
71 fn update(&mut self, candle: Candle) -> Option<FibonacciPivotsOutput> {
72 let (h, l, c) = (candle.high, candle.low, candle.close);
73 let pp = (h + l + c) / 3.0;
74 let range = h - l;
75 let out = FibonacciPivotsOutput {
76 pp,
77 r1: pp + FIB1 * range,
78 r2: pp + FIB2 * range,
79 r3: pp + FIB3 * range,
80 s1: pp - FIB1 * range,
81 s2: pp - FIB2 * range,
82 s3: pp - FIB3 * range,
83 };
84 self.ready = true;
85 Some(out)
86 }
87
88 fn reset(&mut self) {
89 self.ready = false;
90 }
91
92 fn warmup_period(&self) -> usize {
93 1
94 }
95
96 fn is_ready(&self) -> bool {
97 self.ready
98 }
99
100 fn name(&self) -> &'static str {
101 "FibonacciPivots"
102 }
103}
104
105#[cfg(test)]
106mod tests {
107 use super::*;
108 use crate::traits::BatchExt;
109
110 fn c(h: f64, l: f64, close: f64, ts: i64) -> Candle {
111 Candle::new(close, h, l, close, 1.0, ts).unwrap()
112 }
113
114 #[test]
115 fn formula_reference_values() {
116 let levels = FibonacciPivots::new()
118 .update(c(110.0, 90.0, 100.0, 0))
119 .unwrap();
120 assert!((levels.pp - 100.0).abs() < 1e-12);
121 assert!((levels.r1 - (100.0 + 0.382 * 20.0)).abs() < 1e-12);
122 assert!((levels.r2 - (100.0 + 0.618 * 20.0)).abs() < 1e-12);
123 assert!((levels.r3 - (100.0 + 20.0)).abs() < 1e-12);
124 assert!((levels.s1 - (100.0 - 0.382 * 20.0)).abs() < 1e-12);
125 assert!((levels.s2 - (100.0 - 0.618 * 20.0)).abs() < 1e-12);
126 assert!((levels.s3 - (100.0 - 20.0)).abs() < 1e-12);
127 }
128
129 #[test]
130 fn resistances_strictly_above_pp_supports_strictly_below() {
131 let levels = FibonacciPivots::new()
132 .update(c(120.0, 80.0, 110.0, 0))
133 .unwrap();
134 assert!(levels.r3 > levels.r2);
135 assert!(levels.r2 > levels.r1);
136 assert!(levels.r1 > levels.pp);
137 assert!(levels.pp > levels.s1);
138 assert!(levels.s1 > levels.s2);
139 assert!(levels.s2 > levels.s3);
140 }
141
142 #[test]
143 fn constant_series_collapses_levels() {
144 let levels = FibonacciPivots::new()
145 .update(c(50.0, 50.0, 50.0, 0))
146 .unwrap();
147 assert_eq!(levels.pp, 50.0);
148 assert_eq!(levels.r1, 50.0);
149 assert_eq!(levels.s3, 50.0);
150 }
151
152 #[test]
153 fn warmup_and_ready() {
154 let mut p = FibonacciPivots::new();
155 assert!(!p.is_ready());
156 assert_eq!(p.warmup_period(), 1);
157 p.update(c(11.0, 9.0, 10.0, 0));
158 assert!(p.is_ready());
159 }
160
161 #[test]
162 fn reset_clears_state() {
163 let mut p = FibonacciPivots::new();
164 p.update(c(11.0, 9.0, 10.0, 0));
165 p.reset();
166 assert!(!p.is_ready());
167 }
168
169 #[test]
170 fn batch_equals_streaming() {
171 let candles: Vec<Candle> = (0_i32..40)
172 .map(|i| {
173 c(
174 f64::from(i) + 2.0,
175 f64::from(i),
176 f64::from(i) + 1.0,
177 i.into(),
178 )
179 })
180 .collect();
181 let mut a = FibonacciPivots::new();
182 let mut b = FibonacciPivots::new();
183 assert_eq!(
184 a.batch(&candles),
185 candles.iter().map(|x| b.update(*x)).collect::<Vec<_>>()
186 );
187 }
188
189 #[test]
190 fn accessors_and_metadata() {
191 let p = FibonacciPivots::new();
192 assert_eq!(p.warmup_period(), 1);
193 assert_eq!(p.name(), "FibonacciPivots");
194 }
195}