Awesome Oscillator (AO)

Parameters: short_period = 5 | long_period = 34

Overview

The Awesome Oscillator reveals market momentum by calculating the difference between a 5 period and 34 period simple moving average of median price, where median equals the midpoint between each bar's high and low. Bill Williams created AO as part of his trading methodology to identify the driving force behind price movements. When the 5 period average exceeds the 34 period average, AO turns positive and momentum accelerates in the bullish direction. Conversely, negative values signal bearish momentum as short term prices fall below the longer term average. Traders watch for zero line crosses to confirm trend changes, twin peaks patterns for divergence signals, and saucer setups where AO changes direction to identify momentum shifts. The histogram visualization makes it easy to spot accelerating and decelerating phases as bars expand or contract from the zero line, providing clear signals about whether buyers or sellers control the immediate market direction.

Implementation Examples

Get started with AO using median price inputs:

use vectorta::indicators::ao::{ao, compute_hl2, AoInput, AoParams};
use vectorta::utilities::data_loader::{Candles, read_candles_from_csv};

// Median price derived from raw high/low arrays
let high = vec![110.0, 111.5, 109.8, 112.0, 110.7];
let low = vec![108.5, 109.9, 108.2, 110.4, 109.6];
let hl2 = compute_hl2(&high, &low)?;
let params = AoParams {
    short_period: Some(5),
    long_period: Some(34),
};
let input = AoInput::from_slice(&hl2, params);
let result = ao(&input)?;

// Or work directly with a Candles collection (uses hl2 + 5/34 by default)
let candles: Candles = read_candles_from_csv("data/sample.csv")?;
let input = AoInput::with_default_candles(&candles);
let result = ao(&input)?;

// Iterate over histogram values
for value in result.values {
    println!("AO: {value}");
}

API Reference

Input Methods
use vectorta::indicators::ao::{AoInput, AoParams, compute_hl2};
use vectorta::utilities::data_loader::Candles;

// From high/low arrays -> median price slice
let hl2 = compute_hl2(&high, &low)?;
AoInput::from_slice(&hl2, AoParams::default());

// From candles with custom source column
AoInput::from_candles(&candles, "hl2", AoParams::default());

// Convenience helper (hl2 + default periods)
AoInput::with_default_candles(&candles);
Parameters Structure
pub struct AoParams {
    pub short_period: Option<usize>, // Default: 5
    pub long_period: Option<usize>,  // Default: 34
}
Output Structure
pub struct AoOutput {
    pub values: Vec<f64>, // Histogram: short_sma - long_sma
}
Validation, Warmup & NaNs
  • short_period > 0, long_period > 0, and short_period < long_period.
  • Must have at least long_period valid points after the first finite input; else AoError::NotEnoughValidData.
  • Warmup: indices [0 .. first + long − 1) are NaN; first computed output at index first + long − 1.
  • For high/low input, lengths must match; else AoError::MismatchedArrayLengths.
Error Handling

AO returns detailed errors for invalid periods, insufficient data, or shape mismatches:

use vectorta::indicators::ao::{ao, AoError, AoInput, AoParams};

match ao(&input) {
    Ok(output) => process_results(output.values),
    Err(AoError::AllValuesNaN) => eprintln!("all values are NaN"),
    Err(AoError::InvalidPeriods { short, long }) =>
        eprintln!("invalid periods: short={}, long={}", short, long),
    Err(AoError::ShortPeriodNotLess { short, long }) =>
        eprintln!("short period {} must be < long period {}", short, long),
    Err(AoError::NoData) => eprintln!("no data provided"),
    Err(AoError::NotEnoughValidData { needed, valid }) =>
        eprintln!("need {} valid points, only have {}", needed, valid),
    Err(AoError::MismatchedArrayLengths { high_len, low_len }) =>
        eprintln!("high/low length mismatch: {} vs {}", high_len, low_len),
    Err(AoError::OutputLenMismatch { expected, got }) =>
        eprintln!("destination buffer mismatch (expected {}, got {})", expected, got),
    Err(AoError::InvalidKernel { kernel }) =>
        eprintln!("invalid kernel for batch: {:?}", kernel),
    Err(other) => eprintln!("ao failed: {}", other),
}

Python Bindings

Basic Usage

Compute AO from NumPy high/low arrays:

import numpy as np
from vectorta import ao

high = np.array([110.0, 111.5, 109.8, 112.0, 110.7], dtype=np.float64)
low = np.array([108.5, 109.9, 108.2, 110.4, 109.6], dtype=np.float64)

# Required short/long periods (defaults mirror 5/34)
values = ao(high, low, short_period=5, long_period=34)

# Explicit kernel selection ("auto", "scalar", "avx2", "avx512" when available)
values = ao(high, low, short_period=6, long_period=40, kernel="avx2")

print("AO tail:", values[-5:])
Streaming Real-time Updates

Maintain rolling buffers without reprocessing history:

from vectorta import AoStream

stream = AoStream(short_period=5, long_period=34)

for candle in price_feed:
    ao_value = stream.update(candle.high, candle.low)  # median price computed internally

    if ao_value is not None:
        if ao_value > 0:
            print("Momentum building:", ao_value)
        else:
            print("Momentum fading:", ao_value)
Batch Parameter Optimization

Evaluate multiple short/long combinations in one call:

import numpy as np
from vectorta import ao_batch

high = np.array([...], dtype=np.float64)
low = np.array([...], dtype=np.float64)

results = ao_batch(
    high,
    low,
    short_period_range=(4, 12, 2),   # 4, 6, 8, 10, 12
    long_period_range=(26, 60, 4),   # 26, 30, ..., 58, 60
    kernel="auto",
)

values = results["values"]  # shape: (num_combinations, len(high))
short_periods = results["short_periods"]
long_periods = results["long_periods"]
CUDA Acceleration

CUDA kernels for AO are on the roadmap. The API will follow the same pattern as other CUDA-enabled indicators once released.

JavaScript/WASM Bindings

Basic Usage

Call AO from JavaScript or TypeScript:

import { ao_js } from 'vectorta-wasm';

const high = new Float64Array([110.0, 111.5, 109.8, 112.0, 110.7]);
const low = new Float64Array([108.5, 109.9, 108.2, 110.4, 109.6]);

// short=5, long=34 by default, but configurable
const values = ao_js(high, low, 5, 34);

console.log('AO values:', values);
Memory-Efficient Operations

Reuse WASM memory and avoid extra allocations for high/low streams:

import { ao_alloc, ao_free, ao_into, memory } from 'vectorta-wasm';

// Assume vectorta-wasm has already been initialised and you kept the exports around
const wasmExports = vectortaWasmInstance.exports;

const high = new Float64Array([...]);
const low = new Float64Array([...]);
const length = high.length;
const bytes = length * Float64Array.BYTES_PER_ELEMENT;

const highPtr = wasmExports.__wbindgen_malloc(bytes);
const lowPtr = wasmExports.__wbindgen_malloc(bytes);

const wasmHigh = new Float64Array(memory.buffer, highPtr, length);
wasmHigh.set(high);
const wasmLow = new Float64Array(memory.buffer, lowPtr, length);
wasmLow.set(low);

// Allocate output buffer inside WASM
const outputPtr = ao_alloc(length);

ao_into(
  highPtr,
  lowPtr,
  outputPtr,
  length,
  5,  // short period
  34  // long period
);

const aoValues = new Float64Array(memory.buffer, outputPtr, length);

// Clean up when finished
ao_free(outputPtr, length);
wasmExports.__wbindgen_free(highPtr, bytes);
wasmExports.__wbindgen_free(lowPtr, bytes);
Batch Processing

Benchmark multiple AO configurations in a single pass:

import { ao_batch_js, ao_batch_metadata_js } from 'vectorta-wasm';

const high = new Float64Array([...]);
const low = new Float64Array([...]);

// Describe the parameter sweep
const shortStart = 4, shortEnd = 12, shortStep = 2;
const longStart = 26, longEnd = 60, longStep = 4;

const metadata = ao_batch_metadata_js(
  shortStart, shortEnd, shortStep,
  longStart, longEnd, longStep
);

// metadata is [short1, long1, short2, long2, ...]
const combos = metadata.length / 2;

const results = ao_batch_js(
  high,
  low,
  shortStart, shortEnd, shortStep,
  longStart, longEnd, longStep
);

// Flattened output -> reshape to [combos][length]
const length = high.length;
const resultMatrix: number[][] = [];
for (let i = 0; i < combos; i++) {
  const start = i * length;
  resultMatrix.push(Array.from(results.slice(start, start + length)));
}

// Inspect the first combination
const shortPeriod = metadata[0];
const longPeriod = metadata[1];
const aoValues = resultMatrix[0];

Performance Analysis

Comparison:
View:

Across sizes, Rust CPU runs about 1.15× slower than Tulip C in this benchmark.

Loading chart...

AMD Ryzen 9 9950X (CPU) | NVIDIA RTX 4090 (GPU) | Benchmarks: 2026-01-05

Related Indicators