finance_query/indicators/
cmo.rs1use super::{IndicatorError, Result};
4
5pub fn cmo(data: &[f64], period: usize) -> Result<Vec<Option<f64>>> {
24 if period == 0 {
25 return Err(IndicatorError::InvalidPeriod(
26 "Period must be greater than 0".to_string(),
27 ));
28 }
29 if data.len() < period + 1 {
30 return Err(IndicatorError::InsufficientData {
31 need: period + 1,
32 got: data.len(),
33 });
34 }
35
36 let mut result = vec![None; data.len()];
37
38 let mut changes = Vec::with_capacity(data.len());
40 changes.push(0.0); for i in 1..data.len() {
42 changes.push(data[i] - data[i - 1]);
43 }
44
45 let mut gains_sum = 0.0;
47 let mut losses_sum = 0.0;
48
49 for &change in changes.iter().skip(1).take(period) {
50 if change > 0.0 {
51 gains_sum += change;
52 } else {
53 losses_sum += -change;
54 }
55 }
56
57 let total = gains_sum + losses_sum;
59 if total != 0.0 {
60 result[period] = Some(((gains_sum - losses_sum) / total) * 100.0);
61 } else {
62 result[period] = Some(0.0);
63 }
64
65 for i in (period + 1)..data.len() {
67 let old_change = changes[i - period];
69 if old_change > 0.0 {
70 gains_sum -= old_change;
71 } else {
72 losses_sum -= -old_change;
73 }
74
75 let new_change = changes[i];
77 if new_change > 0.0 {
78 gains_sum += new_change;
79 } else {
80 losses_sum += -new_change;
81 }
82
83 let total = gains_sum + losses_sum;
84 if total != 0.0 {
85 result[i] = Some(((gains_sum - losses_sum) / total) * 100.0);
86 } else {
87 result[i] = Some(0.0);
88 }
89 }
90
91 Ok(result)
92}
93
94#[cfg(test)]
95mod tests {
96 use super::*;
97
98 #[test]
99 fn test_cmo() {
100 let prices = vec![10.0, 11.0, 12.0, 11.0, 10.0];
101 let result = cmo(&prices, 3).unwrap();
102
103 assert_eq!(result.len(), 5);
104 assert!(result[0].is_none());
105 assert!(result[1].is_none());
106 assert!(result[2].is_none());
107
108 assert!(result[3].is_some());
110 let val = result[3].unwrap();
111 assert!((val - 33.333).abs() < 0.01);
112 }
113}