egui_charts/studies/builtin/
market_facilitation.rs1use super::clamp_param;
2use crate::model::Bar;
3use crate::studies::palette;
4use crate::studies::{Indicator, IndicatorValue, IndicatorValues};
5use egui::Color32;
6
7#[derive(Clone)]
11pub struct MarketFacilitationIndex {
12 divisor: f64,
14 values: IndicatorValues,
15 color: Color32,
16 visible: bool,
17}
18
19impl MarketFacilitationIndex {
20 pub fn new() -> Self {
22 Self {
23 divisor: 1.0,
24 values: IndicatorValues::new(),
25 color: palette::UP, visible: true,
27 }
28 }
29
30 pub fn with_divisor(mut self, divisor: f64) -> Self {
32 self.divisor = clamp_param(divisor, f64::EPSILON, f64::MAX);
33 self
34 }
35
36 pub fn with_color(mut self, color: Color32) -> Self {
38 self.color = color;
39 self
40 }
41}
42
43impl Default for MarketFacilitationIndex {
44 fn default() -> Self {
45 Self::new()
46 }
47}
48
49impl Indicator for MarketFacilitationIndex {
50 crate::studies::inputs::study_inputs!(self;
51 divisor: number("Divisor", f64::EPSILON, f64::MAX)
52 ; {});
53
54 fn name(&self) -> &str {
55 "MFI BW"
56 }
57
58 fn desc(&self) -> &str {
59 "Market Facilitation Index - Price movement per volume"
60 }
61
62 fn calculate(&mut self, data: &[Bar]) {
63 self.update(data, 0);
64 }
65
66 fn update(&mut self, data: &[Bar], from: usize) {
67 let from = from.min(self.values.len()).min(data.len());
68 self.values.truncate(from);
69 for bar in &data[from..] {
70 let range = bar.high - bar.low;
71 let volume = bar.volume / self.divisor;
72
73 if volume == 0.0 {
74 self.values.push(IndicatorValue::None);
75 } else {
76 let mfi = range / volume;
77 self.values.push(IndicatorValue::Single(mfi));
78 }
79 }
80 }
81
82 fn is_incremental(&self) -> bool {
83 true
84 }
85
86 fn values(&self) -> &IndicatorValues {
87 &self.values
88 }
89
90 fn colors(&self) -> Vec<Color32> {
91 vec![self.color]
92 }
93
94 fn set_colors(&mut self, colors: Vec<Color32>) {
95 if !colors.is_empty() {
96 self.color = colors[0];
97 }
98 }
99
100 fn is_overlay(&self) -> bool {
101 false
102 }
103
104 fn is_visible(&self) -> bool {
105 self.visible
106 }
107
108 fn set_visible(&mut self, visible: bool) {
109 self.visible = visible;
110 }
111
112 fn clone_box(&self) -> Box<dyn Indicator> {
113 Box::new(self.clone())
114 }
115
116 fn line_names(&self) -> Vec<String> {
117 vec!["MFI".to_string()]
118 }
119}
120
121#[cfg(test)]
122mod tests {
123 use super::*;
124 use chrono::Utc;
125
126 fn make_bar(high: f64, low: f64, volume: f64) -> Bar {
127 Bar {
128 time: Utc::now(),
129 open: (high + low) / 2.0,
130 high,
131 low,
132 close: (high + low) / 2.0,
133 volume,
134 colors: None,
135 payload: None,
136 }
137 }
138
139 #[test]
140 fn test_mfi_calculation() {
141 let mut mfi = MarketFacilitationIndex::new();
142
143 let data = vec![
144 make_bar(102.0, 98.0, 1000.0), make_bar(105.0, 100.0, 2000.0), ];
147
148 mfi.calculate(&data);
149
150 assert_eq!(mfi.values.len(), 2);
151
152 let IndicatorValue::Single(v) = mfi.values.to_rows()[0] else {
153 panic!("no value: {:?}", mfi.values.to_rows()[0]);
154 };
155 assert!((v - 0.004).abs() < 0.0001);
156 }
157
158 #[test]
159 fn test_mfi_zero_volume() {
160 let mut mfi = MarketFacilitationIndex::new();
161
162 let data = vec![make_bar(102.0, 98.0, 0.0)];
163
164 mfi.calculate(&data);
165
166 assert!(matches!(mfi.values.to_rows()[0], IndicatorValue::None));
167 }
168}