Average Sentiment Oscillator (ASO)

Parameters: period = 10 | mode = 0 (0–2)

Overview

ASO quantifies market sentiment by analyzing where price closes within individual bars and across rolling windows, producing bull and bear readings that always sum to 100 after initialization. The indicator calculates sentiment from two angles: intrabar positioning measures how close price finished relative to its range, while group analysis compares current levels to recent history over the specified period. Mode 0 averages both perspectives for balanced sentiment, mode 1 uses only intrabar data for immediate reactions, and mode 2 relies solely on group context for smoother signals. When bulls exceed 60, buying pressure dominates the market; conversely, bears above 60 indicate selling control. Traders use ASO to confirm breakouts when sentiment aligns with price direction, identify exhaustion when sentiment diverges from price trends, and time reversals when sentiment shifts from extreme readings back toward equilibrium at 50.

Implementation Examples

Compute Bulls/Bears from OHLC slices or Candles:

use vector_ta::indicators::aso::{aso, AsoInput, AsoParams};
use vector_ta::utilities::data_loader::{Candles, read_candles_from_csv};

// Using with OHLC slices (need at least period values)
let (open, high, low, close): (Vec<f64>, Vec<f64>, Vec<f64>, Vec<f64>) = (
    vec![/* ... */],
    vec![/* ... */],
    vec![/* ... */],
    vec![/* ... */],
);
let params = AsoParams { period: Some(10), mode: Some(0) };
let input = AsoInput::from_slices(&open, &high, &low, &close, params);
let out = aso(&input)?;
for (b, e) in out.bulls.iter().zip(out.bears.iter()) {
    println!("bulls={}, bears={}", b, e);
}

// Using with Candles (defaults: period=10, mode=0; source="close")
let candles: Candles = read_candles_from_csv("data/sample.csv")?;
let input = AsoInput::with_default_candles(&candles);
let out = aso(&input)?;

API Reference

Input Methods
// From candles (uses OHLC from Candles; source argument currently ignored)
AsoInput::from_candles(&Candles, &str, AsoParams) -> AsoInput

// From OHLC slices
AsoInput::from_slices(&[f64], &[f64], &[f64], &[f64], AsoParams) -> AsoInput

// From candles with default params (period=10, mode=0; source="close")
AsoInput::with_default_candles(&Candles) -> AsoInput
Parameters Structure
pub struct AsoParams {
    pub period: Option<usize>, // Default: 10
    pub mode: Option<usize>,   // Default: 0 (0/1/2)
}
Output Structure
pub struct AsoOutput {
    pub bulls: Vec<f64>, // sentiment in [0, 100]
    pub bears: Vec<f64>, // sentiment in [0, 100]
}
// Property: for non-NaN outputs, bulls + bears == 100
Validation, Warmup & NaNs
  • period > 0 and period ≤ len; else AsoError::InvalidPeriod.
  • mode ∈ {0,1,2}; else AsoError::InvalidMode.
  • All OHLC arrays must have equal length; else AsoError::MissingData.
  • Empty input → AsoError::EmptyInputData; all-NaN close series → AsoError::AllValuesNaN.
  • Requires at least period valid points after the first finite close; else AsoError::NotEnoughValidData.
  • Warmup: indices [0 .. first + period − 1) are NaN for both outputs.
Error Handling
use vector_ta::indicators::aso::{aso, AsoError};

match aso(&input) {
    Ok(output) => consume(output.bulls, output.bears),
    Err(AsoError::EmptyInputData) => eprintln!("ASO: input is empty"),
    Err(AsoError::AllValuesNaN) => eprintln!("ASO: all values are NaN"),
    Err(AsoError::InvalidPeriod { period, data_len }) =>
        eprintln!("ASO: invalid period {period} for length {data_len}"),
    Err(AsoError::NotEnoughValidData { needed, valid }) =>
        eprintln!("ASO: need {needed} valid points, have {valid}"),
    Err(AsoError::InvalidMode { mode }) =>
        eprintln!("ASO: invalid mode {mode}, must be 0, 1, or 2"),
    Err(AsoError::MissingData) => eprintln!("ASO: OHLC mismatch or missing"),
}

Python Bindings

Basic Usage

Work with NumPy arrays for open, high, low, close:

import numpy as np
from vector_ta import aso

open_ = np.asarray([...], dtype=np.float64)
high = np.asarray([...], dtype=np.float64)
low  = np.asarray([...], dtype=np.float64)
close= np.asarray([...], dtype=np.float64)

bulls, bears = aso(open_, high, low, close, period=10, mode=0, kernel="auto")
print(bulls.shape, bears.shape)
Streaming Updates
from vector_ta import AsoStream

stream = AsoStream(period=10, mode=0)
for o,h,l,c in ohlc_stream():
    val = stream.update(o, h, l, c)
    if val is not None:
        bulls, bears = val
        use(bulls, bears)
Batch Sweeps
from vector_ta import aso_batch

result = aso_batch(
    open_, high, low, close,
    period_range=(5, 20, 5),
    mode_range=(0, 2, 1),
    kernel="auto",
)

# result is a dict with reshaped NumPy arrays
bulls = result["bulls"]   # shape: (rows, len)
bears = result["bears"]   # shape: (rows, len)
periods = result["periods"]
modes = result["modes"]
CUDA Acceleration

CUDA helpers are available when the Python package is built with CUDA support. Inputs must be float32; outputs are device arrays (DLPack / __cuda_array_interface__ compatible).

import numpy as np
from vector_ta import aso_cuda_batch_dev, aso_cuda_many_series_one_param_dev

# One series (float32)
open = np.asarray(load_open(), dtype=np.float32)
high = np.asarray(load_high(), dtype=np.float32)
low = np.asarray(load_low(), dtype=np.float32)
close = np.asarray(load_close(), dtype=np.float32)

dev = aso_cuda_batch_dev(
    open=open,
    high=high,
    low=low,
    close=close,
    period_range=(5, 30, 5),
    mode_range=(0, 4, 1),
    device_id=0,
)

# Many series (time-major)
open_tm = np.asarray(load_open_time_major_matrix(), dtype=np.float32)
rows, cols = open_tm.shape
open_tm = open_tm.ravel()
high_tm = np.asarray(load_high_time_major_matrix(), dtype=np.float32)
high_tm = high_tm.ravel()
low_tm = np.asarray(load_low_time_major_matrix(), dtype=np.float32)
low_tm = low_tm.ravel()
close_tm = np.asarray(load_close_time_major_matrix(), dtype=np.float32)
close_tm = close_tm.ravel()

dev_tm = aso_cuda_many_series_one_param_dev(
    open_tm=open_tm,
    high_tm=high_tm,
    low_tm=low_tm,
    close_tm=close_tm,
    cols=cols,
    rows=rows,
    period=14,
    mode=14,
    device_id=0,
)
Return Structure
# aso(...)
np.ndarray, np.ndarray  # bulls, bears, each shape: (len,)

# aso_batch(...)
{
    "bulls": np.ndarray,   # shape: (rows, len)
    "bears": np.ndarray,   # shape: (rows, len)
    "periods": np.ndarray, # row-wise period
    "modes": np.ndarray,   # row-wise mode
}

JavaScript / WASM Bindings

Quick Usage

Call ASO from TypeScript/JavaScript:

import { aso_js } from 'vectorta-wasm';

const open = new Float64Array([/* ... */]);
const high = new Float64Array([/* ... */]);
const low  = new Float64Array([/* ... */]);
const close= new Float64Array([/* ... */]);

// Returns { values: Float64Array, rows: 2, cols: len }
const res = aso_js(open, high, low, close, 10, 0);
const { values, rows, cols } = res;
const bulls = values.slice(0, cols);
const bears = values.slice(cols, 2 * cols);
Memory-Efficient Operations

Use zero-copy buffers for large datasets:

import { aso_alloc, aso_free, aso_into, memory } from 'vectorta-wasm';

const len = close.length;
const oPtr = aso_alloc(len), hPtr = aso_alloc(len);
const lPtr = aso_alloc(len), cPtr = aso_alloc(len);
const bullsPtr = aso_alloc(len), bearsPtr = aso_alloc(len);

// Copy input data into WASM memory
new Float64Array(memory.buffer, oPtr, len).set(open);
new Float64Array(memory.buffer, hPtr, len).set(high);
new Float64Array(memory.buffer, lPtr, len).set(low);
new Float64Array(memory.buffer, cPtr, len).set(close);

// Compute directly into allocated memory
// Args: open_ptr, high_ptr, low_ptr, close_ptr, bulls_ptr, bears_ptr, len, period, mode
aso_into(oPtr, hPtr, lPtr, cPtr, bullsPtr, bearsPtr, len, 10, 0);

// Read out
const bulls = new Float64Array(memory.buffer, bullsPtr, len).slice();
const bears = new Float64Array(memory.buffer, bearsPtr, len).slice();

// Free
aso_free(oPtr, len); aso_free(hPtr, len); aso_free(lPtr, len); aso_free(cPtr, len);
aso_free(bullsPtr, len); aso_free(bearsPtr, len);
Batch Processing

Test multiple parameter combinations efficiently:

import { aso_batch } from 'vectorta-wasm';

const cfg = { period_range: [5, 20, 5], mode_range: [0, 2, 1] };
const result = aso_batch(open, high, low, close, cfg);

// result: { values, combos, rows, cols }
// rows = 2 * num_combinations (bulls rows first, then bears rows)
const { values, combos, rows, cols } = result;
const numCombos = combos.length;

// Extract the first combo's bulls/bears rows
const bulls0 = values.slice(0 * cols, 1 * cols);
const bears0 = values.slice(numCombos * cols, (numCombos + 1) * cols);

CUDA Bindings (Rust)

use vector_ta::cuda::CudaAso;
use vector_ta::indicators::aso::AsoBatchRange;

let cuda = CudaAso::new(0)?;

let open_f32: [f32] = /* ... */;
let high_f32: [f32] = /* ... */;
let low_f32: [f32] = /* ... */;
let close_f32: [f32] = /* ... */;
let sweep = AsoBatchRange::default();

let out = cuda.aso_batch_dev(&open_f32, &high_f32, &low_f32, &close_f32, &sweep)?;
let _ = out;

Performance Analysis

Comparison:
View:
Loading chart...

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

Related Indicators