finance_query/indicators/
fibonacci_retracement.rs1use std::collections::VecDeque;
7
8use super::{IndicatorError, Result};
9use serde::{Deserialize, Serialize};
10
11#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)]
13#[non_exhaustive]
14pub struct FibonacciLevels {
15 pub swing_high: f64,
17 pub swing_low: f64,
19 pub level_23_6: f64,
21 pub level_38_2: f64,
23 pub level_50: f64,
25 pub level_61_8: f64,
27 pub level_78_6: f64,
29}
30
31pub fn fibonacci_retracement(
57 highs: &[f64],
58 lows: &[f64],
59 period: usize,
60) -> Result<Vec<Option<FibonacciLevels>>> {
61 if period == 0 {
62 return Err(IndicatorError::InvalidPeriod(
63 "Period must be greater than 0".to_string(),
64 ));
65 }
66 let len = highs.len();
67 if lows.len() != len {
68 return Err(IndicatorError::InvalidPeriod(
69 "highs and lows must have the same length".to_string(),
70 ));
71 }
72 if len < period {
73 return Err(IndicatorError::InsufficientData {
74 need: period,
75 got: len,
76 });
77 }
78
79 let mut result = vec![None; len];
80
81 let mut max_deque: VecDeque<usize> = VecDeque::new();
83 let mut min_deque: VecDeque<usize> = VecDeque::new();
84
85 for i in 0..len {
86 while max_deque.front().is_some_and(|&j| j + period <= i) {
87 max_deque.pop_front();
88 }
89 while min_deque.front().is_some_and(|&j| j + period <= i) {
90 min_deque.pop_front();
91 }
92 while max_deque.back().is_some_and(|&j| highs[j] <= highs[i]) {
93 max_deque.pop_back();
94 }
95 while min_deque.back().is_some_and(|&j| lows[j] >= lows[i]) {
96 min_deque.pop_back();
97 }
98 max_deque.push_back(i);
99 min_deque.push_back(i);
100
101 if i + 1 >= period {
102 let swing_high = highs[*max_deque.front().unwrap()];
103 let swing_low = lows[*min_deque.front().unwrap()];
104 let range = swing_high - swing_low;
105 result[i] = Some(FibonacciLevels {
106 swing_high,
107 swing_low,
108 level_23_6: swing_high - range * 0.236,
109 level_38_2: swing_high - range * 0.382,
110 level_50: swing_high - range * 0.5,
111 level_61_8: swing_high - range * 0.618,
112 level_78_6: swing_high - range * 0.786,
113 });
114 }
115 }
116
117 Ok(result)
118}
119
120#[cfg(test)]
121mod tests {
122 use super::*;
123
124 #[test]
125 fn test_fibonacci_retracement_basic() {
126 let highs = vec![10.0, 12.0, 11.0, 9.0, 13.0];
127 let lows = vec![8.0, 9.0, 8.5, 7.0, 10.0];
128 let result = fibonacci_retracement(&highs, &lows, 3).unwrap();
129
130 assert!(result[0].is_none());
131 assert!(result[1].is_none());
132 assert!(result[2].is_some());
133
134 let l = result[2].unwrap();
136 assert!((l.swing_high - 12.0).abs() < 1e-9);
137 assert!((l.swing_low - 8.0).abs() < 1e-9);
138 assert!((l.level_50 - 10.0).abs() < 1e-9);
139 assert!((l.level_23_6 - (12.0 - 4.0 * 0.236)).abs() < 1e-9);
140 assert!((l.level_61_8 - (12.0 - 4.0 * 0.618)).abs() < 1e-9);
141 }
142
143 #[test]
144 fn test_fibonacci_levels_ordering() {
145 let highs = vec![10.0, 12.0, 11.0, 9.0, 13.0];
146 let lows = vec![8.0, 9.0, 8.5, 7.0, 10.0];
147 let result = fibonacci_retracement(&highs, &lows, 3).unwrap();
148
149 for l in result.into_iter().flatten() {
150 assert!(l.swing_high >= l.level_23_6);
151 assert!(l.level_23_6 >= l.level_38_2);
152 assert!(l.level_38_2 >= l.level_50);
153 assert!(l.level_50 >= l.level_61_8);
154 assert!(l.level_61_8 >= l.level_78_6);
155 assert!(l.level_78_6 >= l.swing_low);
156 }
157 }
158
159 #[test]
160 fn test_fibonacci_retracement_insufficient_data() {
161 assert!(fibonacci_retracement(&[1.0, 2.0], &[1.0, 2.0], 5).is_err());
162 }
163
164 #[test]
165 fn test_fibonacci_retracement_invalid_period() {
166 assert!(fibonacci_retracement(&[1.0], &[1.0], 0).is_err());
167 }
168
169 #[test]
170 fn test_fibonacci_retracement_mismatched_lengths() {
171 assert!(fibonacci_retracement(&[1.0, 2.0], &[1.0], 1).is_err());
172 }
173}