egui_charts/studies/builtin/
cmf.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 ChaikinMoneyFlow {
36 period: usize,
37 values: IndicatorValues,
38 positive_color: Color32,
39 negative_color: Color32,
40 visible: bool,
41}
42
43impl ChaikinMoneyFlow {
44 pub fn new(period: usize) -> Self {
49 Self {
50 period: period.max(1),
51 values: IndicatorValues::new(),
52 positive_color: palette::DARK_GREEN,
53 negative_color: palette::RED,
54 visible: true,
55 }
56 }
57
58 pub fn default_params() -> Self {
60 Self::new(20)
61 }
62
63 pub fn with_colors(mut self, positive: Color32, negative: Color32) -> Self {
65 self.positive_color = positive;
66 self.negative_color = negative;
67 self
68 }
69}
70
71impl Default for ChaikinMoneyFlow {
73 fn default() -> Self {
74 Self::new(20)
75 }
76}
77
78impl ChaikinMoneyFlow {
79 fn compute(&self, data: &[Bar]) -> IndicatorValues {
81 let mut values = IndicatorValues::new();
82 if data.is_empty() {
83 return values;
84 }
85
86 let mut mfv: Vec<f64> = Vec::with_capacity(data.len());
88 let mut volumes: Vec<f64> = Vec::with_capacity(data.len());
89
90 for bar in data {
91 let high_low = bar.high - bar.low;
92
93 if high_low > 0.0 {
94 let mfm = (2.0 * bar.close - bar.high - bar.low) / high_low;
95 mfv.push(mfm * bar.volume);
96 } else {
97 mfv.push(0.0);
98 }
99 volumes.push(bar.volume);
100 }
101
102 for i in 0..data.len() {
104 if i < self.period - 1 {
105 values.push(IndicatorValue::None);
106 continue;
107 }
108
109 let start = i + 1 - self.period;
110 let sum_mfv: f64 = mfv[start..=i].iter().sum();
111 let sum_vol: f64 = volumes[start..=i].iter().sum();
112
113 let cmf = if sum_vol > 0.0 {
114 sum_mfv / sum_vol
115 } else {
116 0.0
117 };
118
119 values.push(IndicatorValue::Single(cmf));
120 }
121
122 values
123 }
124}
125
126impl Indicator for ChaikinMoneyFlow {
127 crate::studies::inputs::study_inputs!(self;
128 period: length("Period", 1, usize::MAX)
129 ; {});
130
131 fn name(&self) -> &str {
132 "CMF"
133 }
134
135 fn desc(&self) -> &str {
136 "Chaikin Money Flow - Measures buying/selling pressure over period"
137 }
138
139 fn calculate(&mut self, data: &[Bar]) {
140 self.update(data, 0);
141 }
142
143 fn update(&mut self, data: &[Bar], from: usize) {
144 let mut values = std::mem::take(&mut self.values);
145 update_window(
146 &mut values,
147 data,
148 from,
149 usable_behind(self.period - 1, |b| {
150 b.high.is_finite()
151 && b.low.is_finite()
152 && b.close.is_finite()
153 && b.volume.is_finite()
154 }),
155 |data| {
156 over_finite_bars(
157 data,
158 |b| {
159 b.high.is_finite()
160 && b.low.is_finite()
161 && b.close.is_finite()
162 && b.volume.is_finite()
163 },
164 |data| self.compute(data),
165 )
166 },
167 );
168 self.values = values;
169 }
170
171 fn is_incremental(&self) -> bool {
172 true
173 }
174
175 fn values(&self) -> &IndicatorValues {
176 &self.values
177 }
178
179 fn colors(&self) -> Vec<Color32> {
180 vec![self.positive_color, self.negative_color]
181 }
182
183 fn set_colors(&mut self, colors: Vec<Color32>) {
184 if !colors.is_empty() {
185 self.positive_color = colors[0];
186 }
187 if colors.len() > 1 {
188 self.negative_color = colors[1];
189 }
190 }
191
192 fn is_overlay(&self) -> bool {
193 false }
195
196 fn line_count(&self) -> usize {
197 1
198 }
199
200 fn is_visible(&self) -> bool {
201 self.visible
202 }
203
204 fn set_visible(&mut self, visible: bool) {
205 self.visible = visible;
206 }
207
208 fn clone_box(&self) -> Box<dyn Indicator> {
209 Box::new(self.clone())
210 }
211
212 fn line_names(&self) -> Vec<String> {
213 vec![format!("CMF({})", self.period)]
214 }
215}