Money Flow Index (MFI)


The Money Flow Index (MFI) is a momentum indicator that incorporates both price and volume data to measure buying and selling pressure. Often referred to as the “volume-weighted RSI,” the MFI is used to identify overbought or oversold conditions by analyzing the intensity of money flowing into and out of a security.

MFI

=MFI(data, period)

Example Usage

=MFI(A2:F500, 14)

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 to calculate the MFI. Default is 14.
Optional

Returns

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

MFI Formula Result in Google Sheets

Source Code

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

mfi.js
/**
 * Calculates the Money Flow Index (MFI) for a given dataset and period.
 * 
 * @param {array} data - An array of historical stock data. Expected to have at least 6 columns (Date, Open, High, Low, Close, Volume).
 * @param {number} period - The number of periods for calculating the MFI.
 * @returns {array} - A 2D array with headers: Date, MFI.
 * @customfunction
 */
function MFI(data, period) {
  // 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);

  // --- NEW: Function-level validation ---
  const columnCount = processedData[0].length;
  if (columnCount < 6) {
    throw new Error(`Invalid data structure for MFI. Expected at least 6 columns (Date, Open, High, Low, Close, Volume), but got ${columnCount}.`);
  }
  // --- END of validation ---

  const dates = processedData.slice(1).map(row => row[0]);
  const highs = processedData.slice(1).map(row => row[2]);
  const lows = processedData.slice(1).map(row => row[3]);
  const closes = processedData.slice(1).map(row => row[4]);
  const volumes = processedData.slice(1).map(row => row[5]);

  if (period > closes.length) {
    throw new Error(`Invalid period. The period (${period}) cannot be greater than the number of data points (${closes.length}).`);
  }
  if (closes.length < period + 1) { // Need at least period + 1 data points for comparison (current vs previous TP)
      throw new Error(`Insufficient data for MFI. At least ${period + 1} data points are required for the specified period.`);
  }

  const mfiValues = [["Date", `MFI(${period})`]];

  const typicalPrices = closes.map((close, i) => (highs[i] + lows[i] + close) / 3);
  const moneyFlows = typicalPrices.map((tp, i) => tp * volumes[i]);

  for (let i = period; i < typicalPrices.length; i++) { // Start from 'period' to ensure previousTP is available
    let positiveFlow = 0;
    let negativeFlow = 0;

    for (let j = 0; j < period; j++) {
      const currentTP = typicalPrices[i - j];
      const previousTP = typicalPrices[i - j - 1];

      if (currentTP > previousTP) {
        positiveFlow += moneyFlows[i - j];
      } else if (currentTP < previousTP) {
        negativeFlow += moneyFlows[i - j];
      }
    }

    let moneyFlowRatio;
    if (negativeFlow === 0) {
        moneyFlowRatio = Infinity; // Handle division by zero for negativeFlow
    } else {
        moneyFlowRatio = positiveFlow / negativeFlow;
    }

    const mfi = 100 - (100 / (1 + moneyFlowRatio));

    mfiValues.push([dates[i], mfi]);
  }

  return mfiValues;
}