indexes_rs/v2/mfi/
main.rs1use crate::v2::mfi::types::{MFIConfig, MFIError, MFIInput, MFIMarketCondition, MFIOutput, MFIState, MoneyFlow};
2
3pub struct MFI {
15 state: MFIState,
16}
17
18impl MFI {
19 pub fn new() -> Self {
21 Self::with_config(MFIConfig::default())
22 }
23
24 pub fn with_period(period: usize) -> Result<Self, MFIError> {
26 if period == 0 {
27 return Err(MFIError::InvalidPeriod);
28 }
29
30 let config = MFIConfig { period, ..Default::default() };
31 Ok(Self::with_config(config))
32 }
33
34 pub fn with_config(config: MFIConfig) -> Self {
36 Self { state: MFIState::new(config) }
37 }
38
39 pub fn calculate(&mut self, input: MFIInput) -> Result<MFIOutput, MFIError> {
41 self.validate_input(&input)?;
43 self.validate_config()?;
44
45 let typical_price = self.calculate_typical_price(&input);
47
48 let raw_money_flow = typical_price * input.volume;
50
51 let flow_direction = self.determine_flow_direction(typical_price);
53
54 let money_flow = MoneyFlow {
56 typical_price,
57 raw_money_flow,
58 flow_direction,
59 };
60
61 self.update_money_flow_history(money_flow);
63
64 let mfi = if self.state.has_sufficient_data {
66 self.calculate_mfi_value()?
67 } else {
68 50.0 };
70
71 let market_condition = self.determine_market_condition(mfi);
73
74 self.state.previous_typical_price = Some(typical_price);
76
77 Ok(MFIOutput {
78 mfi,
79 typical_price,
80 raw_money_flow,
81 flow_direction,
82 market_condition,
83 })
84 }
85
86 pub fn calculate_batch(&mut self, inputs: &[MFIInput]) -> Result<Vec<MFIOutput>, MFIError> {
88 inputs.iter().map(|input| self.calculate(*input)).collect()
89 }
90
91 pub fn reset(&mut self) {
93 self.state = MFIState::new(self.state.config);
94 }
95
96 pub fn get_state(&self) -> &MFIState {
98 &self.state
99 }
100
101 pub fn set_state(&mut self, state: MFIState) {
103 self.state = state;
104 }
105
106 pub fn positive_money_flow(&self) -> f64 {
108 self.state.positive_money_flow_sum
109 }
110
111 pub fn negative_money_flow(&self) -> f64 {
113 self.state.negative_money_flow_sum
114 }
115
116 fn validate_input(&self, input: &MFIInput) -> Result<(), MFIError> {
119 if !input.high.is_finite() || !input.low.is_finite() || !input.close.is_finite() {
121 return Err(MFIError::InvalidPrice);
122 }
123
124 if input.high < input.low {
126 return Err(MFIError::InvalidOHLC);
127 }
128
129 if input.close < input.low || input.close > input.high {
130 return Err(MFIError::InvalidOHLC);
131 }
132
133 if input.volume < 0.0 {
135 return Err(MFIError::NegativeVolume);
136 }
137
138 Ok(())
139 }
140
141 fn validate_config(&self) -> Result<(), MFIError> {
142 if self.state.config.period == 0 {
143 return Err(MFIError::InvalidPeriod);
144 }
145
146 if self.state.config.overbought <= self.state.config.oversold {
147 return Err(MFIError::InvalidThresholds);
148 }
149
150 if self.state.config.overbought > 100.0 || self.state.config.oversold < 0.0 {
151 return Err(MFIError::InvalidThresholds);
152 }
153
154 Ok(())
155 }
156
157 fn calculate_typical_price(&self, input: &MFIInput) -> f64 {
158 (input.high + input.low + input.close) / 3.0
159 }
160
161 fn determine_flow_direction(&self, current_typical_price: f64) -> f64 {
162 match self.state.previous_typical_price {
163 Some(prev_tp) => {
164 if current_typical_price > prev_tp {
165 1.0 } else if current_typical_price < prev_tp {
167 -1.0 } else {
169 0.0 }
171 }
172 None => 0.0, }
174 }
175
176 fn update_money_flow_history(&mut self, money_flow: MoneyFlow) {
177 if self.state.money_flows.len() >= self.state.config.period {
179 if let Some(oldest) = self.state.money_flows.pop_front() {
180 if oldest.flow_direction > 0.0 {
182 self.state.positive_money_flow_sum -= oldest.raw_money_flow;
183 } else if oldest.flow_direction < 0.0 {
184 self.state.negative_money_flow_sum -= oldest.raw_money_flow;
185 }
186 }
187 }
188
189 if money_flow.flow_direction > 0.0 {
191 self.state.positive_money_flow_sum += money_flow.raw_money_flow;
192 } else if money_flow.flow_direction < 0.0 {
193 self.state.negative_money_flow_sum += money_flow.raw_money_flow;
194 }
195
196 self.state.money_flows.push_back(money_flow);
197
198 self.state.has_sufficient_data = self.state.money_flows.len() >= self.state.config.period;
200 }
201
202 fn calculate_mfi_value(&self) -> Result<f64, MFIError> {
203 if self.state.negative_money_flow_sum == 0.0 {
204 return Ok(100.0);
206 }
207
208 if self.state.positive_money_flow_sum == 0.0 {
209 return Ok(0.0);
211 }
212
213 let money_ratio = self.state.positive_money_flow_sum / self.state.negative_money_flow_sum;
214 let mfi = 100.0 - (100.0 / (1.0 + money_ratio));
215
216 if !mfi.is_finite() {
217 return Err(MFIError::DivisionByZero);
218 }
219
220 Ok(mfi)
221 }
222
223 fn determine_market_condition(&self, mfi: f64) -> MFIMarketCondition {
224 if !self.state.has_sufficient_data {
225 MFIMarketCondition::Insufficient
226 } else if mfi >= self.state.config.overbought {
227 MFIMarketCondition::Overbought
228 } else if mfi <= self.state.config.oversold {
229 MFIMarketCondition::Oversold
230 } else {
231 MFIMarketCondition::Normal
232 }
233 }
234}
235
236impl Default for MFI {
237 fn default() -> Self {
238 Self::new()
239 }
240}
241
242pub fn calculate_mfi_simple(highs: &[f64], lows: &[f64], closes: &[f64], volumes: &[f64], period: usize) -> Result<Vec<f64>, MFIError> {
244 let len = highs.len();
245 if len != lows.len() || len != closes.len() || len != volumes.len() {
246 return Err(MFIError::InvalidInput("All price and volume arrays must have same length".to_string()));
247 }
248
249 if len == 0 {
250 return Ok(Vec::new());
251 }
252
253 let mut mfi_calculator = MFI::with_period(period)?;
254 let mut results = Vec::with_capacity(len);
255
256 for i in 0..len {
257 let input = MFIInput {
258 high: highs[i],
259 low: lows[i],
260 close: closes[i],
261 volume: volumes[i],
262 };
263 let output = mfi_calculator.calculate(input)?;
264 results.push(output.mfi);
265 }
266
267 Ok(results)
268}