ta_lib_in_rust/indicators/volume/cmf.rs
1use polars::prelude::*;
2
3/// Calculates the Chaikin Money Flow (CMF) indicator
4///
5/// The Chaikin Money Flow measures the amount of Money Flow Volume over a specific period.
6/// It's particularly useful for intraday trading as it helps identify buying and selling pressure.
7///
8/// # Arguments
9///
10/// * `df` - DataFrame containing OHLCV data with "high", "low", "close", and "volume" columns
11/// * `window` - Lookback period for calculating the CMF (typically 20 or 21)
12///
13/// # Returns
14///
15/// * `PolarsResult<Series>` - Series containing CMF values named "cmf_{window}"
16///
17/// # Formula
18///
19/// 1. Calculate Money Flow Multiplier: ((close - low) - (high - close)) / (high - low)
20/// 2. Calculate Money Flow Volume: Money Flow Multiplier * volume
21/// 3. Sum Money Flow Volume over the period and divide by sum of Volume over the period
22///
23/// # Example
24///
25/// ```
26/// use polars::prelude::*;
27/// use ta_lib_in_rust::indicators::volume::calculate_cmf;
28///
29/// // Create or load a DataFrame with OHLCV data
30/// let df = DataFrame::default(); // Replace with actual data
31///
32/// // Calculate CMF with period 20
33/// let cmf = calculate_cmf(&df, 20).unwrap();
34/// ```
35pub fn calculate_cmf(df: &DataFrame, window: usize) -> PolarsResult<Series> {
36 // Validate that necessary columns exist
37 if !df.schema().contains("high")
38 || !df.schema().contains("low")
39 || !df.schema().contains("close")
40 || !df.schema().contains("volume")
41 {
42 return Err(PolarsError::ShapeMismatch(
43 "Missing required columns for CMF calculation. Required: high, low, close, volume"
44 .to_string()
45 .into(),
46 ));
47 }
48
49 // Validate window size
50 if window == 0 {
51 return Err(PolarsError::ComputeError(
52 "Window size must be greater than 0".into(),
53 ));
54 }
55
56 // Extract the required columns
57 let high = df.column("high")?.f64()?;
58 let low = df.column("low")?.f64()?;
59 let close = df.column("close")?.f64()?;
60 let volume = df.column("volume")?.f64()?;
61
62 // Calculate Money Flow Multiplier for each period
63 let mut money_flow_multipliers = Vec::with_capacity(df.height());
64 let mut money_flow_volumes = Vec::with_capacity(df.height());
65
66 // Use standard for loop with index
67 for i in 0..df.height() {
68 let high_val = high.get(i).unwrap_or(f64::NAN);
69 let low_val = low.get(i).unwrap_or(f64::NAN);
70 let close_val = close.get(i).unwrap_or(f64::NAN);
71 let vol = volume.get(i).unwrap_or(f64::NAN);
72
73 // Calculate money flow multiplier only if all values are valid
74 if !high_val.is_nan() && !low_val.is_nan() && !close_val.is_nan() && high_val != low_val {
75 let money_flow_multiplier =
76 ((close_val - low_val) - (high_val - close_val)) / (high_val - low_val);
77 money_flow_multipliers.push(money_flow_multiplier);
78
79 // Money flow volume is the product of money flow multiplier and volume
80 let money_flow_volume = money_flow_multiplier * vol;
81 money_flow_volumes.push(money_flow_volume);
82 } else {
83 money_flow_multipliers.push(f64::NAN);
84 money_flow_volumes.push(f64::NAN);
85 }
86 }
87
88 // Calculate CMF using a sliding window
89 let mut cmf_values = Vec::with_capacity(df.height());
90
91 for i in 0..df.height() {
92 if i < window - 1 {
93 cmf_values.push(f64::NAN);
94 continue;
95 }
96
97 // Calculate the sum of money flow volumes in the window
98 let mut sum_money_flow_volume = 0.0;
99 let mut sum_volume = 0.0;
100
101 // Use an iterator-based approach as suggested by Clippy
102 let window_start = i - (window - 1);
103 for (idx, money_flow_vol) in money_flow_volumes
104 .iter()
105 .enumerate()
106 .skip(window_start)
107 .take(window)
108 {
109 let vol = volume.get(idx).unwrap_or(f64::NAN);
110
111 if !money_flow_vol.is_nan() && !vol.is_nan() {
112 sum_money_flow_volume += money_flow_vol;
113 sum_volume += vol;
114 }
115 }
116
117 // Calculate CMF as the ratio of sum of money flow volumes to sum of volume
118 if sum_volume > 0.0 {
119 cmf_values.push(sum_money_flow_volume / sum_volume);
120 } else {
121 cmf_values.push(f64::NAN);
122 }
123 }
124
125 // Return the CMF as a Polars Series
126 Ok(Series::new(format!("cmf_{}", window).into(), cmf_values))
127}