Awesome Oscillator


Awesome Oscillator (AO) is a momentum indicator that reflects changes in the market driving force. It is calculated by taking the difference between a 34-period and 5-period Simple Moving Average (SMA), which are based on the midpoints of the bars (Higher + Lower) / 2.

AO

=AO(data)

Example Usage

=AO(A2:F500)

Parameters

Parameter Type Description Status
data
Range
The input range of columns containing the Date, Open, High, Low, Close, and Volume data. The function automatically uses the High and Low values to calculate midpoints (Average Price).
Required

Returns

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

Output Example

Awesome Oscillator Formula Result in Google Sheets

Source Code

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

ao.js
/**
 * Calculates the Awesome Oscillator (AO).
 * AO = SMA(Median Price, 5) - SMA(Median Price, 34)
 * Median Price = (High + Low) / 2
 *
 * @param {array} data - The input range. Must include at least 4 columns: Date, Open, High, Low.
 * @returns {array} A two-column array with headers "Date" and "AO".
 * @customfunction
 */
function AO(data) {
    checkPremium();

    // AO has fixed periods 5 and 34 usually, but we could allow params.
    // Standard AO is strictly 5/34.
    const fastPeriod = 5;
    const slowPeriod = 34;

    const processedData = getData(data);

    // --- Validate Data Structure ---
    const columnCount = processedData[0].length;
    if (columnCount < 4) {
        throw new Error(`Invalid data structure. Expected at least 4 columns (Date, O, H, L), but got ${columnCount}.`);
    }

    const dataRows = processedData.slice(1);
    const results = [["Date", `AO (${fastPeriod}, ${slowPeriod})`]];

    // We need two SMA states running on "Median Price".
    // We can use buffers for calculation.
    const fastBuffer = [];
    const slowBuffer = [];
    let fastSum = 0;
    let slowSum = 0;

    for (let i = 0; i < dataRows.length; i++) {
        const row = dataRows[i];
        const date = row[0];
        const high = row[2];
        const low = row[3];

        const medianPrice = (high + low) / 2;

        // Update Fast SMA (5)
        fastBuffer.push(medianPrice);
        fastSum += medianPrice;
        if (fastBuffer.length > fastPeriod) {
            fastSum -= fastBuffer.shift();
        }

        // Update Slow SMA (34)
        slowBuffer.push(medianPrice);
        slowSum += medianPrice;
        if (slowBuffer.length > slowPeriod) {
            slowSum -= slowBuffer.shift();
        }

        // Calculate AO
        // We need both SMAs to be valid (i.e., we need at least 34 periods of data).
        if (slowBuffer.length < slowPeriod) {
            results.push([date, ""]);
        } else {
            const smaFast = fastSum / fastPeriod;
            const smaSlow = slowSum / slowPeriod;

            results.push([date, smaFast - smaSlow]);
        }
    }

    // Trim Output
    let firstValidIndex = -1;
    for (let i = 1; i < results.length; i++) {
        if (results[i][1] !== "" && results[i][1] !== null) {
            firstValidIndex = i;
            break;
        }
    }

    if (firstValidIndex !== -1) {
        return [results[0], ...results.slice(firstValidIndex)];
    } else {
        return [results[0]];
    }
}