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}