Skip to main content

finance_query/indicators/
cmf.rs

1//! Chaikin Money Flow (CMF) indicator.
2
3use super::{IndicatorError, Result};
4
5/// Calculate Chaikin Money Flow (CMF).
6///
7/// Measures buying/selling pressure over a period.
8/// Returns value between -1 and 1.
9///
10/// # Arguments
11///
12/// * `highs` - High prices
13/// * `lows` - Low prices
14/// * `closes` - Close prices
15/// * `volumes` - Volume data
16/// * `period` - Number of periods
17///
18/// # Example
19///
20/// ```
21/// use finance_query::indicators::cmf;
22///
23/// let highs = vec![10.0, 11.0, 12.0, 11.0, 10.0];
24/// let lows = vec![8.0, 9.0, 10.0, 9.0, 8.0];
25/// let closes = vec![9.0, 10.0, 11.0, 10.0, 9.0];
26/// let volumes = vec![100.0, 200.0, 150.0, 100.0, 50.0];
27/// let result = cmf(&highs, &lows, &closes, &volumes, 3).unwrap();
28/// ```
29pub fn cmf(
30    highs: &[f64],
31    lows: &[f64],
32    closes: &[f64],
33    volumes: &[f64],
34    period: usize,
35) -> Result<Vec<Option<f64>>> {
36    if period == 0 {
37        return Err(IndicatorError::InvalidPeriod(
38            "Period must be greater than 0".to_string(),
39        ));
40    }
41    let len = highs.len();
42    if lows.len() != len || closes.len() != len || volumes.len() != len {
43        return Err(IndicatorError::InvalidPeriod(
44            "Data lengths must match".to_string(),
45        ));
46    }
47    if len < period {
48        return Err(IndicatorError::InsufficientData {
49            need: period,
50            got: len,
51        });
52    }
53
54    let mut result = vec![None; len];
55
56    let mut mf_volumes = Vec::with_capacity(len);
57
58    for i in 0..len {
59        let high_low = highs[i] - lows[i];
60        if high_low == 0.0 {
61            mf_volumes.push(0.0);
62        } else {
63            let mf_multiplier = ((closes[i] - lows[i]) - (highs[i] - closes[i])) / high_low;
64            mf_volumes.push(mf_multiplier * volumes[i]);
65        }
66    }
67
68    let mut mf_volume_sum = 0.0;
69    let mut volume_sum = 0.0;
70
71    for i in 0..period {
72        mf_volume_sum += mf_volumes[i];
73        volume_sum += volumes[i];
74    }
75
76    if volume_sum != 0.0 {
77        result[period - 1] = Some(mf_volume_sum / volume_sum);
78    } else {
79        result[period - 1] = Some(0.0);
80    }
81
82    for i in period..len {
83        let old_idx = i - period;
84        mf_volume_sum -= mf_volumes[old_idx];
85        volume_sum -= volumes[old_idx];
86
87        mf_volume_sum += mf_volumes[i];
88        volume_sum += volumes[i];
89
90        if volume_sum != 0.0 {
91            result[i] = Some(mf_volume_sum / volume_sum);
92        } else {
93            result[i] = Some(0.0);
94        }
95    }
96
97    Ok(result)
98}
99
100#[cfg(test)]
101mod tests {
102    use super::*;
103
104    #[test]
105    fn test_cmf() {
106        let highs = vec![10.0, 11.0, 12.0, 11.0, 10.0];
107        let lows = vec![8.0, 9.0, 10.0, 9.0, 8.0];
108        let closes = vec![9.0, 10.0, 11.0, 10.0, 9.0];
109        let volumes = vec![100.0, 200.0, 150.0, 100.0, 50.0];
110        let result = cmf(&highs, &lows, &closes, &volumes, 3).unwrap();
111
112        assert_eq!(result.len(), 5);
113        assert!(result[1].is_none());
114        assert!(result[2].is_some());
115    }
116}