use super::{IndicatorError, Result};
use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)]
#[non_exhaustive]
pub struct PivotPoints {
pub pivot: f64,
pub r1: f64,
pub r2: f64,
pub r3: f64,
pub s1: f64,
pub s2: f64,
pub s3: f64,
}
fn validate(highs: &[f64], lows: &[f64], closes: &[f64]) -> Result<()> {
if highs.len() != lows.len() || highs.len() != closes.len() {
return Err(IndicatorError::InvalidPeriod(
"highs, lows, and closes must have the same length".to_string(),
));
}
if highs.len() < 2 {
return Err(IndicatorError::InsufficientData {
need: 2,
got: highs.len(),
});
}
Ok(())
}
pub fn pivot_points(
highs: &[f64],
lows: &[f64],
closes: &[f64],
) -> Result<Vec<Option<PivotPoints>>> {
validate(highs, lows, closes)?;
let mut result = vec![None; highs.len()];
for i in 1..highs.len() {
let (h, l, c) = (highs[i - 1], lows[i - 1], closes[i - 1]);
let pivot = (h + l + c) / 3.0;
let range = h - l;
result[i] = Some(PivotPoints {
pivot,
r1: 2.0 * pivot - l,
s1: 2.0 * pivot - h,
r2: pivot + range,
s2: pivot - range,
r3: h + 2.0 * (pivot - l),
s3: l - 2.0 * (h - pivot),
});
}
Ok(result)
}
pub fn fibonacci_pivot_points(
highs: &[f64],
lows: &[f64],
closes: &[f64],
) -> Result<Vec<Option<PivotPoints>>> {
validate(highs, lows, closes)?;
let mut result = vec![None; highs.len()];
for i in 1..highs.len() {
let (h, l, c) = (highs[i - 1], lows[i - 1], closes[i - 1]);
let pivot = (h + l + c) / 3.0;
let range = h - l;
result[i] = Some(PivotPoints {
pivot,
r1: pivot + 0.382 * range,
r2: pivot + 0.618 * range,
r3: pivot + 1.000 * range,
s1: pivot - 0.382 * range,
s2: pivot - 0.618 * range,
s3: pivot - 1.000 * range,
});
}
Ok(result)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_pivot_points_basic() {
let highs = vec![12.0, 15.0];
let lows = vec![8.0, 9.0];
let closes = vec![10.0, 13.0];
let result = pivot_points(&highs, &lows, &closes).unwrap();
assert!(result[0].is_none());
let p = result[1].unwrap();
assert!((p.pivot - 10.0).abs() < 1e-9);
assert!((p.r1 - 12.0).abs() < 1e-9); assert!((p.s1 - 8.0).abs() < 1e-9); assert!((p.r2 - 14.0).abs() < 1e-9); assert!((p.s2 - 6.0).abs() < 1e-9); assert!(p.r3 > p.r2);
assert!(p.s3 < p.s2);
}
#[test]
fn test_fibonacci_pivot_points_basic() {
let highs = vec![12.0, 15.0];
let lows = vec![8.0, 9.0];
let closes = vec![10.0, 13.0];
let result = fibonacci_pivot_points(&highs, &lows, &closes).unwrap();
assert!(result[0].is_none());
let p = result[1].unwrap();
assert!((p.pivot - 10.0).abs() < 1e-9);
assert!((p.r1 - (10.0 + 0.382 * 4.0)).abs() < 1e-9);
assert!((p.s1 - (10.0 - 0.382 * 4.0)).abs() < 1e-9);
assert!((p.r3 - 14.0).abs() < 1e-9);
assert!((p.s3 - 6.0).abs() < 1e-9);
}
#[test]
fn test_pivot_points_insufficient_data() {
assert!(pivot_points(&[1.0], &[1.0], &[1.0]).is_err());
}
#[test]
fn test_pivot_points_mismatched_lengths() {
assert!(pivot_points(&[1.0, 2.0], &[1.0], &[1.0, 2.0]).is_err());
}
}