Skip to main content

finance_query/indicators/
mfi.rs

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