finance_query/indicators/
obv.rs1use super::{IndicatorError, Result};
4
5pub fn obv(closes: &[f64], volumes: &[f64]) -> Result<Vec<Option<f64>>> {
31 if closes.len() < 2 {
32 return Err(IndicatorError::InsufficientData {
33 need: 2,
34 got: closes.len(),
35 });
36 }
37
38 if closes.len() != volumes.len() {
39 return Err(IndicatorError::InvalidPeriod(
40 "Closes and volumes must have the same length".to_string(),
41 ));
42 }
43
44 let mut result = Vec::with_capacity(closes.len());
45 let mut obv_value = 0.0;
46
47 result.push(Some(obv_value)); for i in 1..closes.len() {
50 if closes[i] > closes[i - 1] {
51 obv_value += volumes[i];
52 } else if closes[i] < closes[i - 1] {
53 obv_value -= volumes[i];
54 }
55 result.push(Some(obv_value));
57 }
58
59 Ok(result)
60}
61
62#[cfg(test)]
63mod tests {
64 use super::*;
65
66 #[test]
67 fn test_obv_basic() {
68 let closes = vec![100.0, 102.0, 101.0, 103.0, 105.0];
69 let volumes = vec![1000.0, 1200.0, 900.0, 1500.0, 2000.0];
70
71 let result = obv(&closes, &volumes).unwrap();
72
73 assert_eq!(result.len(), 5);
74 assert_eq!(result[0], Some(0.0)); assert_eq!(result[1], Some(1200.0)); assert_eq!(result[2], Some(300.0)); assert_eq!(result[3], Some(1800.0)); assert_eq!(result[4], Some(3800.0)); }
80
81 #[test]
82 fn test_obv_insufficient_data() {
83 let closes = vec![100.0];
84 let volumes = vec![1000.0];
85
86 let result = obv(&closes, &volumes);
87 assert!(result.is_err());
88 }
89
90 #[test]
91 fn test_obv_mismatched_lengths() {
92 let closes = vec![100.0, 102.0, 101.0];
93 let volumes = vec![1000.0, 1200.0];
94
95 let result = obv(&closes, &volumes);
96 assert!(result.is_err());
97 }
98}