egui_charts/studies/builtin/
mfi.rs1use super::over_finite_bars;
27use crate::model::Bar;
28use crate::studies::palette;
29use crate::studies::stepper::{update_window, usable_behind};
30use crate::studies::{Indicator, IndicatorValue, IndicatorValues};
31use egui::Color32;
32
33#[derive(Clone)]
35pub struct MoneyFlowIndex {
36 period: usize,
37 values: IndicatorValues,
38 color: Color32,
39 visible: bool,
40}
41
42impl MoneyFlowIndex {
43 pub fn new(period: usize) -> Self {
48 Self {
49 period: period.max(1),
50 values: IndicatorValues::new(),
51 color: palette::PURPLE,
52 visible: true,
53 }
54 }
55
56 pub fn with_color(mut self, color: Color32) -> Self {
58 self.color = color;
59 self
60 }
61}
62
63impl Default for MoneyFlowIndex {
65 fn default() -> Self {
66 Self::new(14)
67 }
68}
69
70impl MoneyFlowIndex {
71 fn compute(&self, data: &[Bar]) -> IndicatorValues {
73 let mut values = IndicatorValues::new();
74 if data.len() < self.period.saturating_add(1) {
75 for _ in 0..data.len() {
76 values.push(IndicatorValue::None);
77 }
78 return values;
79 }
80
81 let typical_prices: Vec<f64> = data.iter().map(Bar::typical_price).collect();
83 let raw_money_flows: Vec<f64> = data
84 .iter()
85 .zip(typical_prices.iter())
86 .map(|(bar, tp)| tp.abs() * bar.volume)
87 .collect();
88
89 let mut positive_flows = vec![0.0];
91 let mut negative_flows = vec![0.0];
92
93 for i in 1..data.len() {
94 if typical_prices[i] > typical_prices[i - 1] {
95 positive_flows.push(raw_money_flows[i]);
96 negative_flows.push(0.0);
97 } else if typical_prices[i] < typical_prices[i - 1] {
98 positive_flows.push(0.0);
99 negative_flows.push(raw_money_flows[i]);
100 } else {
101 positive_flows.push(0.0);
102 negative_flows.push(0.0);
103 }
104 }
105
106 for i in 0..data.len() {
108 if i < self.period {
109 values.push(IndicatorValue::None);
110 } else {
111 let start = i + 1 - self.period;
112 let pos_sum: f64 = positive_flows[start..=i].iter().sum();
113 let neg_sum: f64 = negative_flows[start..=i].iter().sum();
114
115 let mfi = super::rsi::from_averages(pos_sum, neg_sum);
116
117 values.push(IndicatorValue::Single(mfi.clamp(0.0, 100.0)));
118 }
119 }
120
121 values
122 }
123}
124
125impl Indicator for MoneyFlowIndex {
126 crate::studies::inputs::study_inputs!(self;
127 period: length("Period", 1, usize::MAX)
128 ; {});
129
130 fn name(&self) -> &str {
131 "MFI"
132 }
133
134 fn desc(&self) -> &str {
135 "Money Flow Index - Volume-weighted RSI (0-100)"
136 }
137
138 fn calculate(&mut self, data: &[Bar]) {
139 self.update(data, 0);
140 }
141
142 fn update(&mut self, data: &[Bar], from: usize) {
143 let mut values = std::mem::take(&mut self.values);
144 update_window(
145 &mut values,
146 data,
147 from,
148 usable_behind(self.period, |b| {
149 b.high.is_finite()
150 && b.low.is_finite()
151 && b.close.is_finite()
152 && b.volume.is_finite()
153 }),
154 |data| {
155 over_finite_bars(
156 data,
157 |b| {
158 b.high.is_finite()
159 && b.low.is_finite()
160 && b.close.is_finite()
161 && b.volume.is_finite()
162 },
163 |data| self.compute(data),
164 )
165 },
166 );
167 self.values = values;
168 }
169
170 fn is_incremental(&self) -> bool {
171 true
172 }
173
174 fn values(&self) -> &IndicatorValues {
175 &self.values
176 }
177
178 fn colors(&self) -> Vec<Color32> {
179 vec![self.color]
180 }
181
182 fn set_colors(&mut self, colors: Vec<Color32>) {
183 if !colors.is_empty() {
184 self.color = colors[0];
185 }
186 }
187
188 fn is_overlay(&self) -> bool {
189 false }
191
192 fn is_visible(&self) -> bool {
193 self.visible
194 }
195
196 fn set_visible(&mut self, visible: bool) {
197 self.visible = visible;
198 }
199
200 fn clone_box(&self) -> Box<dyn Indicator> {
201 Box::new(self.clone())
202 }
203
204 fn line_names(&self) -> Vec<String> {
205 vec![format!("MFI({})", self.period)]
206 }
207}
208
209#[cfg(test)]
210mod tests {
211 use super::*;
212
213 #[test]
214 fn endpoints_and_scale_invariance() {
215 for (closes, volume, expected) in [
218 ([1.0, 2.0, 3.0, 4.0], 0.0, [50.0, 50.0]),
219 ([5.0; 4], 700.0, [50.0, 50.0]),
220 ([1.0, 2.0, 3.0, 4.0], 1.0, [100.0, 100.0]),
221 ([4.0, 3.0, 2.0, 1.0], 1.0, [0.0, 0.0]),
222 ([1.0, 2.0, 1.0, 3.0], 1.0, [200.0 / 3.0, 75.0]),
223 ([-4.0, -2.0, -3.0, -1.0], 1.0, [40.0, 25.0]),
224 ] {
225 for scale in [1.0, 1e-17, 1e12] {
226 let bars: Vec<_> = closes
227 .iter()
228 .map(|p| {
229 let p = p * scale;
230 Bar::new(chrono::DateTime::UNIX_EPOCH, p, p, p, p, volume)
231 })
232 .collect();
233 let mut study = MoneyFlowIndex::new(2);
234 study.calculate(&bars);
235 assert_eq!(study.values().value(0, 1), None);
236 for (bar, want) in expected.into_iter().enumerate() {
237 let got = study.values().value(0, bar + 2).unwrap();
238 assert!(
239 (got - want).abs() < 1e-9,
240 "{closes:?}, {volume}, {scale}: {got}"
241 );
242 }
243 }
244 }
245 }
246}