finlib_ta/indicators/
money_flow_index.rs

1use alloc::boxed::Box;
2use alloc::vec;
3use core::fmt;
4
5use crate::errors::{Result, TaError};
6use crate::{Close, High, Low, Next, Period, Reset, Volume};
7
8#[cfg(feature = "serde")]
9use serde::{Deserialize, Serialize};
10
11/// Money Flow Index (MFI).
12///
13/// The MFI is an volume and price based oscillator which gives moneyflow over n periods.
14/// MFI is used to measure buying and selling pressure.
15/// MFI is also known as volume-weighted RSI.
16///
17/// # Formula
18///
19/// Typical Price(TP) = (High + Low + Close)/3
20///
21/// Money Flow(MF) = Typical Price x Volume
22///
23/// MF is positive when currennt TP is greater that previous period TP and
24/// negative when current TP is less than preivous TP.
25///
26/// Positive money flow (PMF)- calculated by adding the money flow of all the days RMF is positive.
27///
28/// Negative money flow (NMF)- calculated by adding the money flow of all the days RMF is negative.
29///
30/// Money Flow Index(MFI) = PMF / (PMF + NMF) * 100
31///
32///
33/// # Parameters
34///
35/// * _period_ - number of periods, integer greater than 0
36///
37/// # Example
38///
39/// ```
40/// use finlib_ta::indicators::MoneyFlowIndex;
41/// use finlib_ta::{Next, DataItem};
42///
43/// let mut mfi = MoneyFlowIndex::new(3).unwrap();
44/// let di = DataItem::builder()
45///             .high(3.0)
46///             .low(1.0)
47///             .close(2.0)
48///             .open(1.5)
49///             .volume(1000.0)
50///             .build().unwrap();
51/// mfi.next(&di);
52///
53/// ```
54/// # Links
55/// * [Money Flow Index, Wikipedia](https://en.wikipedia.org/wiki/Money_flow_index)
56/// * [Money Flow Index, stockcharts](https://stockcharts.com/school/doku.php?id=chart_school:technical_indicators:money_flow_index_mfi)
57
58#[doc(alias = "MFI")]
59#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
60#[derive(Debug, Clone)]
61pub struct MoneyFlowIndex {
62    period: usize,
63    index: usize,
64    count: usize,
65    previous_typical_price: f64,
66    total_positive_money_flow: f64,
67    total_negative_money_flow: f64,
68    deque: Box<[f64]>,
69}
70
71impl MoneyFlowIndex {
72    pub fn new(period: usize) -> Result<Self> {
73        match period {
74            0 => Err(TaError::InvalidParameter),
75            _ => Ok(Self {
76                period,
77                index: 0,
78                count: 0,
79                previous_typical_price: 0.0,
80                total_positive_money_flow: 0.0,
81                total_negative_money_flow: 0.0,
82                deque: vec![0.0; period].into_boxed_slice(),
83            }),
84        }
85    }
86}
87
88impl Period for MoneyFlowIndex {
89    fn period(&self) -> usize {
90        self.period
91    }
92}
93
94impl<T: High + Low + Close + Volume> Next<&T> for MoneyFlowIndex {
95    type Output = f64;
96
97    fn next(&mut self, input: &T) -> f64 {
98        let tp = (input.close() + input.high() + input.low()) / 3.0;
99
100        self.index = if self.index + 1 < self.period {
101            self.index + 1
102        } else {
103            0
104        };
105
106        if self.count < self.period {
107            self.count = self.count + 1;
108            if self.count == 1 {
109                self.previous_typical_price = tp;
110                return 50.0;
111            }
112        } else {
113            let popped = self.deque[self.index];
114            if popped.is_sign_positive() {
115                self.total_positive_money_flow -= popped;
116            } else {
117                self.total_negative_money_flow += popped;
118            }
119        }
120
121        if tp > self.previous_typical_price {
122            let raw_money_flow = tp * input.volume();
123            self.total_positive_money_flow += raw_money_flow;
124            self.deque[self.index] = raw_money_flow;
125        } else if tp < self.previous_typical_price {
126            let raw_money_flow = tp * input.volume();
127            self.total_negative_money_flow += raw_money_flow;
128            self.deque[self.index] = -raw_money_flow;
129        } else {
130            self.deque[self.index] = 0.0;
131        }
132        self.previous_typical_price = tp;
133
134        self.total_positive_money_flow
135            / (self.total_positive_money_flow + self.total_negative_money_flow)
136            * 100.0
137    }
138}
139
140impl Default for MoneyFlowIndex {
141    fn default() -> Self {
142        Self::new(14).unwrap()
143    }
144}
145
146impl fmt::Display for MoneyFlowIndex {
147    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
148        write!(f, "MFI({})", self.period)
149    }
150}
151
152impl Reset for MoneyFlowIndex {
153    fn reset(&mut self) {
154        self.index = 0;
155        self.count = 0;
156        self.previous_typical_price = 0.0;
157        self.total_positive_money_flow = 0.0;
158        self.total_negative_money_flow = 0.0;
159        for i in 0..self.period {
160            self.deque[i] = 0.0;
161        }
162    }
163}
164
165#[cfg(test)]
166mod tests {
167    use super::*;
168    use crate::test_helper::*;
169    use alloc::format;
170
171    #[test]
172    fn test_new() {
173        assert!(MoneyFlowIndex::new(0).is_err());
174        assert!(MoneyFlowIndex::new(1).is_ok());
175    }
176
177    #[test]
178    fn test_next_bar() {
179        let mut mfi = MoneyFlowIndex::new(3).unwrap();
180
181        let bar1 = Bar::new().high(3).low(1).close(2).volume(500.0);
182        assert_eq!(round(mfi.next(&bar1)), 50.0);
183
184        let bar2 = Bar::new().high(2.3).low(2.0).close(2.3).volume(1000.0);
185        assert_eq!(round(mfi.next(&bar2)), 100.0);
186
187        let bar3 = Bar::new().high(9).low(7).close(8).volume(200.0);
188        assert_eq!(round(mfi.next(&bar3)), 100.0);
189
190        let bar4 = Bar::new().high(5).low(3).close(4).volume(500.0);
191        assert_eq!(round(mfi.next(&bar4)), 65.517);
192
193        let bar5 = Bar::new().high(4).low(2).close(3).volume(5000.0);
194        assert_eq!(round(mfi.next(&bar5)), 8.602);
195
196        let bar6 = Bar::new().high(2).low(1).close(1.5).volume(6000.0);
197        assert_eq!(round(mfi.next(&bar6)), 0.0);
198
199        let bar7 = Bar::new().high(2).low(2).close(2).volume(7000.0);
200        assert_eq!(round(mfi.next(&bar7)), 36.842);
201
202        let bar8 = Bar::new().high(2).low(2).close(2).volume(7000.0);
203        assert_eq!(round(mfi.next(&bar8)), 60.87);
204    }
205
206    #[test]
207    fn test_reset() {
208        let mut mfi = MoneyFlowIndex::new(3).unwrap();
209
210        let bar1 = Bar::new().high(3).low(1).close(2).volume(500.0);
211        let bar2 = Bar::new().high(2.3).low(2.0).close(2.3).volume(1000.0);
212
213        assert_eq!(round(mfi.next(&bar1)), 50.0);
214        assert_eq!(round(mfi.next(&bar2)), 100.0);
215
216        mfi.reset();
217
218        assert_eq!(round(mfi.next(&bar1)), 50.0);
219        assert_eq!(round(mfi.next(&bar2)), 100.0);
220    }
221
222    #[test]
223    fn test_default() {
224        MoneyFlowIndex::default();
225    }
226
227    #[test]
228    fn test_display() {
229        let mfi = MoneyFlowIndex::new(10).unwrap();
230        assert_eq!(format!("{}", mfi), "MFI(10)");
231    }
232}