Skip to main content

egui_charts/studies/builtin/
cmf.rs

1//! Chaikin Money Flow (CMF) Indicator
2//!
3//! CMF measures the amount of Money Flow Volume over a specific period,
4//! providing a bounded oscillator between -1 and +1.
5//!
6//! # Formula
7//! CMF = Sum(Money Flow Volume, period) / Sum(Volume, period)
8//! Where Money Flow Volume = Money Flow Multiplier * Volume
9//! And Money Flow Multiplier = ((Close - Low) - (High - Close)) / (High - Low)
10//!
11//! # Interpretation
12//! - CMF > 0: Buying pressure (accumulation)
13//! - CMF < 0: Selling pressure (distribution)
14//! - CMF near +1: Strong accumulation
15//! - CMF near -1: Strong distribution
16//!
17//! # Example
18//! ```
19//! # let bars: Vec<egui_charts::model::Bar> = (0..60).map(|i| { let p = 100.0 + (i as f64 * 0.3).sin() * 5.0; egui_charts::model::Bar::new(chrono::Utc::now(), p, p + 1.0, p - 1.0, p + 0.5, 1_000.0) }).collect();
20//! use egui_charts::studies::{ChaikinMoneyFlow, Indicator};
21//!
22//! let mut cmf = ChaikinMoneyFlow::new(20);
23//! cmf.calculate(&bars);
24//! ```
25
26use 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/// Chaikin Money Flow indicator
34#[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    /// Create a new CMF indicator
45    ///
46    /// # Arguments
47    /// * `period` - Lookback period (typically 20 or 21)
48    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    /// Create with default param (20)
59    pub fn default_params() -> Self {
60        Self::new(20)
61    }
62
63    /// Set colors for positive and negative values
64    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
71/// Construct with the conventional default parameters.
72impl Default for ChaikinMoneyFlow {
73    fn default() -> Self {
74        Self::new(20)
75    }
76}
77
78impl ChaikinMoneyFlow {
79    /// The values over `data`, every bar of which has finite inputs.
80    fn compute(&self, data: &[Bar]) -> IndicatorValues {
81        let mut values = IndicatorValues::new();
82        if data.is_empty() {
83            return values;
84        }
85
86        // Calculate Money Flow Volume for each bar
87        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        // Calculate CMF for each period
103        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 // CMF is a separate oscillator pane
194    }
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}