finance_query/indicators/
pivot_points.rs1use super::{IndicatorError, Result};
9use serde::{Deserialize, Serialize};
10
11#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)]
13#[non_exhaustive]
14pub struct PivotPoints {
15 pub pivot: f64,
17 pub r1: f64,
19 pub r2: f64,
21 pub r3: f64,
23 pub s1: f64,
25 pub s2: f64,
27 pub s3: f64,
29}
30
31fn validate(highs: &[f64], lows: &[f64], closes: &[f64]) -> Result<()> {
32 if highs.len() != lows.len() || highs.len() != closes.len() {
33 return Err(IndicatorError::InvalidPeriod(
34 "highs, lows, and closes must have the same length".to_string(),
35 ));
36 }
37 if highs.len() < 2 {
38 return Err(IndicatorError::InsufficientData {
39 need: 2,
40 got: highs.len(),
41 });
42 }
43 Ok(())
44}
45
46pub fn pivot_points(
72 highs: &[f64],
73 lows: &[f64],
74 closes: &[f64],
75) -> Result<Vec<Option<PivotPoints>>> {
76 validate(highs, lows, closes)?;
77 let mut result = vec![None; highs.len()];
78 for i in 1..highs.len() {
79 let (h, l, c) = (highs[i - 1], lows[i - 1], closes[i - 1]);
80 let pivot = (h + l + c) / 3.0;
81 let range = h - l;
82 result[i] = Some(PivotPoints {
83 pivot,
84 r1: 2.0 * pivot - l,
85 s1: 2.0 * pivot - h,
86 r2: pivot + range,
87 s2: pivot - range,
88 r3: h + 2.0 * (pivot - l),
89 s3: l - 2.0 * (h - pivot),
90 });
91 }
92 Ok(result)
93}
94
95pub fn fibonacci_pivot_points(
120 highs: &[f64],
121 lows: &[f64],
122 closes: &[f64],
123) -> Result<Vec<Option<PivotPoints>>> {
124 validate(highs, lows, closes)?;
125 let mut result = vec![None; highs.len()];
126 for i in 1..highs.len() {
127 let (h, l, c) = (highs[i - 1], lows[i - 1], closes[i - 1]);
128 let pivot = (h + l + c) / 3.0;
129 let range = h - l;
130 result[i] = Some(PivotPoints {
131 pivot,
132 r1: pivot + 0.382 * range,
133 r2: pivot + 0.618 * range,
134 r3: pivot + 1.000 * range,
135 s1: pivot - 0.382 * range,
136 s2: pivot - 0.618 * range,
137 s3: pivot - 1.000 * range,
138 });
139 }
140 Ok(result)
141}
142
143#[cfg(test)]
144mod tests {
145 use super::*;
146
147 #[test]
148 fn test_pivot_points_basic() {
149 let highs = vec![12.0, 15.0];
151 let lows = vec![8.0, 9.0];
152 let closes = vec![10.0, 13.0];
153 let result = pivot_points(&highs, &lows, &closes).unwrap();
154
155 assert!(result[0].is_none());
156 let p = result[1].unwrap();
157 assert!((p.pivot - 10.0).abs() < 1e-9);
158 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);
163 assert!(p.s3 < p.s2);
164 }
165
166 #[test]
167 fn test_fibonacci_pivot_points_basic() {
168 let highs = vec![12.0, 15.0];
169 let lows = vec![8.0, 9.0];
170 let closes = vec![10.0, 13.0];
171 let result = fibonacci_pivot_points(&highs, &lows, &closes).unwrap();
172
173 assert!(result[0].is_none());
174 let p = result[1].unwrap();
175 assert!((p.pivot - 10.0).abs() < 1e-9);
176 assert!((p.r1 - (10.0 + 0.382 * 4.0)).abs() < 1e-9);
178 assert!((p.s1 - (10.0 - 0.382 * 4.0)).abs() < 1e-9);
179 assert!((p.r3 - 14.0).abs() < 1e-9);
180 assert!((p.s3 - 6.0).abs() < 1e-9);
181 }
182
183 #[test]
184 fn test_pivot_points_insufficient_data() {
185 assert!(pivot_points(&[1.0], &[1.0], &[1.0]).is_err());
186 }
187
188 #[test]
189 fn test_pivot_points_mismatched_lengths() {
190 assert!(pivot_points(&[1.0, 2.0], &[1.0], &[1.0, 2.0]).is_err());
191 }
192}