Chaikin Money Flow (CMF)


Chaikin Money Flow (CMF) is a volume-weighted average of accumulation and distribution over a specified period. It measures buying and selling pressure. A CMF above zero indicates buying pressure (accumulation), while a CMF below zero indicates selling pressure (distribution).

CMF

=CMF(data, period)

Example Usage

=CMF(A2:F500, 20)

Parameters

Parameter Type Description Status
data
Range
The input range of columns containing the Date, Open, High, Low, Close, and Volume data.
Required
period
Number
The number of periods (e.g., 20 or 21) for the lookback window.
Required

Returns

A two-column array of dates and their corresponding CMF values.

Chaikin Money Flow (CMF) Formula Result in Google Sheets

Source Code

Copy the following code into your Apps Script editor (Extensions > Apps Script) to use the CMF function in your spreadsheet.

cmf.js
/**
 * Calculates the Chaikin Money Flow (CMF) over a specified period.
 * CMF measures the amount of Money Flow Volume over a set period.
 * Requires data with at least 6 columns: Date, Open, High, Low, Close, and Volume.
 *
 * @param {array} data - A 2D array of historical stock data (DOHLCV) from a source like GOOGLEFINANCE.
 * @param {number} period - The lookback period, typically 20 or 21.
 * @returns {array} A 2D array with headers: Date and CMF.
 * @customfunction
 */
function CMF(data, period) {
    checkPremium();

  // Argument validation
  if (arguments.length !== 2) {
    throw new Error(`Wrong number of arguments. Expected 2, but got ${arguments.length}.`);
  }
  if (typeof period !== 'number' || period <= 0 || !Number.isInteger(period)) {
    throw new Error(`Invalid period. The period must be a positive integer. Got: ${period}`);
  }

  const processedData = getData(data);

  // --- Function-level validation for DOHLCV data ---
  const columnCount = processedData[0].length;
  if (columnCount < 6) {
      throw new Error(`Invalid data structure for CMF. Expected at least 6 columns (Date, O, H, L, C, V), but got ${columnCount}.`);
  }
  // --- END of validation ---

  const dataRows = processedData.slice(1);

  if (period >= dataRows.length) {
    throw new Error(`Invalid period. The period (${period}) cannot be greater than or equal to the number of data points (${dataRows.length}).`);
  }

  // Pre-calculate Money Flow Volume for each day
  const moneyFlowVolumes = [];
  const volumes = [];
  for (let i = 0; i < dataRows.length; i++) {
    const row = dataRows[i];
    const high = row[2];
    const low = row[3];
    const close = row[4];
    const volume = row[5];
    volumes.push(volume);

    let moneyFlowMultiplier = 0;
    if (high !== low) {
      moneyFlowMultiplier = ((close - low) - (high - close)) / (high - low);
    }
    moneyFlowVolumes.push(moneyFlowMultiplier * volume);
  }

  const results = [["Date", `CMF(${period})`]];

  // Calculate CMF using a sliding window
  let sumMoneyFlow = 0;
  let sumVolume = 0;

  // First, calculate for the initial period
  for (let i = 0; i < period; i++) {
    sumMoneyFlow += moneyFlowVolumes[i];
    sumVolume += volumes[i];
  }

  let cmf = sumVolume === 0 ? 0 : sumMoneyFlow / sumVolume;
  results.push([dataRows[period - 1][0], cmf]);

  // Then, slide the window for the rest of the data
  for (let i = period; i < dataRows.length; i++) {
    sumMoneyFlow = sumMoneyFlow - moneyFlowVolumes[i - period] + moneyFlowVolumes[i];
    sumVolume = sumVolume - volumes[i - period] + volumes[i];
    
    cmf = sumVolume === 0 ? 0 : sumMoneyFlow / sumVolume;
    results.push([dataRows[i][0], cmf]);
  }

  return results;
}