use std::collections::VecDeque;
use super::{IndicatorError, Result};
use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)]
#[non_exhaustive]
pub struct FibonacciLevels {
pub swing_high: f64,
pub swing_low: f64,
pub level_23_6: f64,
pub level_38_2: f64,
pub level_50: f64,
pub level_61_8: f64,
pub level_78_6: f64,
}
pub fn fibonacci_retracement(
highs: &[f64],
lows: &[f64],
period: usize,
) -> Result<Vec<Option<FibonacciLevels>>> {
if period == 0 {
return Err(IndicatorError::InvalidPeriod(
"Period must be greater than 0".to_string(),
));
}
let len = highs.len();
if lows.len() != len {
return Err(IndicatorError::InvalidPeriod(
"highs and lows must have the same length".to_string(),
));
}
if len < period {
return Err(IndicatorError::InsufficientData {
need: period,
got: len,
});
}
let mut result = vec![None; len];
let mut max_deque: VecDeque<usize> = VecDeque::new();
let mut min_deque: VecDeque<usize> = VecDeque::new();
for i in 0..len {
while max_deque.front().is_some_and(|&j| j + period <= i) {
max_deque.pop_front();
}
while min_deque.front().is_some_and(|&j| j + period <= i) {
min_deque.pop_front();
}
while max_deque.back().is_some_and(|&j| highs[j] <= highs[i]) {
max_deque.pop_back();
}
while min_deque.back().is_some_and(|&j| lows[j] >= lows[i]) {
min_deque.pop_back();
}
max_deque.push_back(i);
min_deque.push_back(i);
if i + 1 >= period {
let swing_high = highs[*max_deque.front().unwrap()];
let swing_low = lows[*min_deque.front().unwrap()];
let range = swing_high - swing_low;
result[i] = Some(FibonacciLevels {
swing_high,
swing_low,
level_23_6: swing_high - range * 0.236,
level_38_2: swing_high - range * 0.382,
level_50: swing_high - range * 0.5,
level_61_8: swing_high - range * 0.618,
level_78_6: swing_high - range * 0.786,
});
}
}
Ok(result)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_fibonacci_retracement_basic() {
let highs = vec![10.0, 12.0, 11.0, 9.0, 13.0];
let lows = vec![8.0, 9.0, 8.5, 7.0, 10.0];
let result = fibonacci_retracement(&highs, &lows, 3).unwrap();
assert!(result[0].is_none());
assert!(result[1].is_none());
assert!(result[2].is_some());
let l = result[2].unwrap();
assert!((l.swing_high - 12.0).abs() < 1e-9);
assert!((l.swing_low - 8.0).abs() < 1e-9);
assert!((l.level_50 - 10.0).abs() < 1e-9);
assert!((l.level_23_6 - (12.0 - 4.0 * 0.236)).abs() < 1e-9);
assert!((l.level_61_8 - (12.0 - 4.0 * 0.618)).abs() < 1e-9);
}
#[test]
fn test_fibonacci_levels_ordering() {
let highs = vec![10.0, 12.0, 11.0, 9.0, 13.0];
let lows = vec![8.0, 9.0, 8.5, 7.0, 10.0];
let result = fibonacci_retracement(&highs, &lows, 3).unwrap();
for l in result.into_iter().flatten() {
assert!(l.swing_high >= l.level_23_6);
assert!(l.level_23_6 >= l.level_38_2);
assert!(l.level_38_2 >= l.level_50);
assert!(l.level_50 >= l.level_61_8);
assert!(l.level_61_8 >= l.level_78_6);
assert!(l.level_78_6 >= l.swing_low);
}
}
#[test]
fn test_fibonacci_retracement_insufficient_data() {
assert!(fibonacci_retracement(&[1.0, 2.0], &[1.0, 2.0], 5).is_err());
}
#[test]
fn test_fibonacci_retracement_invalid_period() {
assert!(fibonacci_retracement(&[1.0], &[1.0], 0).is_err());
}
#[test]
fn test_fibonacci_retracement_mismatched_lengths() {
assert!(fibonacci_retracement(&[1.0, 2.0], &[1.0], 1).is_err());
}
}