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 #[inline]
72 fn update(&mut self, candle: Candle) -> Option<FibonacciPivotsOutput> {
73 let (h, l, c) = (candle.high, candle.low, candle.close);
74 let pp = (h + l + c) / 3.0;
75 let range = h - l;
76 let out = FibonacciPivotsOutput {
77 pp,
78 r1: pp + FIB1 * range,
79 r2: pp + FIB2 * range,
80 r3: pp + FIB3 * range,
81 s1: pp - FIB1 * range,
82 s2: pp - FIB2 * range,
83 s3: pp - FIB3 * range,
84 };
85 self.ready = true;
86 Some(out)
87 }
88
89 fn reset(&mut self) {
90 self.ready = false;
91 }
92
93 #[inline]
94 fn warmup_period(&self) -> usize {
95 1
96 }
97
98 #[inline]
99 fn is_ready(&self) -> bool {
100 self.ready
101 }
102
103 #[inline]
104 fn name(&self) -> &'static str {
105 "FibonacciPivots"
106 }
107}
108
109#[cfg(test)]
110mod tests {
111 use super::*;
112 use crate::traits::BatchExt;
113
114 fn c(h: f64, l: f64, close: f64, ts: i64) -> Candle {
115 Candle::new(close, h, l, close, 1.0, ts).unwrap()
116 }
117
118 #[test]
119 fn formula_reference_values() {
120 let levels = FibonacciPivots::new()
122 .update(c(110.0, 90.0, 100.0, 0))
123 .unwrap();
124 assert!((levels.pp - 100.0).abs() < 1e-12);
125 assert!((levels.r1 - (100.0 + 0.382 * 20.0)).abs() < 1e-12);
126 assert!((levels.r2 - (100.0 + 0.618 * 20.0)).abs() < 1e-12);
127 assert!((levels.r3 - (100.0 + 20.0)).abs() < 1e-12);
128 assert!((levels.s1 - (100.0 - 0.382 * 20.0)).abs() < 1e-12);
129 assert!((levels.s2 - (100.0 - 0.618 * 20.0)).abs() < 1e-12);
130 assert!((levels.s3 - (100.0 - 20.0)).abs() < 1e-12);
131 }
132
133 #[test]
134 fn resistances_strictly_above_pp_supports_strictly_below() {
135 let levels = FibonacciPivots::new()
136 .update(c(120.0, 80.0, 110.0, 0))
137 .unwrap();
138 assert!(levels.r3 > levels.r2);
139 assert!(levels.r2 > levels.r1);
140 assert!(levels.r1 > levels.pp);
141 assert!(levels.pp > levels.s1);
142 assert!(levels.s1 > levels.s2);
143 assert!(levels.s2 > levels.s3);
144 }
145
146 #[test]
147 fn constant_series_collapses_levels() {
148 let levels = FibonacciPivots::new()
149 .update(c(50.0, 50.0, 50.0, 0))
150 .unwrap();
151 assert_eq!(levels.pp, 50.0);
152 assert_eq!(levels.r1, 50.0);
153 assert_eq!(levels.s3, 50.0);
154 }
155
156 #[test]
157 fn warmup_and_ready() {
158 let mut p = FibonacciPivots::new();
159 assert!(!p.is_ready());
160 assert_eq!(p.warmup_period(), 1);
161 p.update(c(11.0, 9.0, 10.0, 0));
162 assert!(p.is_ready());
163 }
164
165 #[test]
166 fn reset_clears_state() {
167 let mut p = FibonacciPivots::new();
168 p.update(c(11.0, 9.0, 10.0, 0));
169 p.reset();
170 assert!(!p.is_ready());
171 }
172
173 #[test]
174 fn batch_equals_streaming() {
175 let candles: Vec<Candle> = (0_i32..40)
176 .map(|i| {
177 c(
178 f64::from(i) + 2.0,
179 f64::from(i),
180 f64::from(i) + 1.0,
181 i.into(),
182 )
183 })
184 .collect();
185 let mut a = FibonacciPivots::new();
186 let mut b = FibonacciPivots::new();
187 assert_eq!(
188 a.batch(&candles),
189 candles.iter().map(|x| b.update(*x)).collect::<Vec<_>>()
190 );
191 }
192
193 #[test]
194 fn accessors_and_metadata() {
195 let p = FibonacciPivots::new();
196 assert_eq!(p.warmup_period(), 1);
197 assert_eq!(p.name(), "FibonacciPivots");
198 }
199}