Average Sentiment Oscillator (ASO)
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 > 0andperiod ≤ len; elseAsoError::InvalidPeriod.mode ∈ {0,1,2}; elseAsoError::InvalidMode.- All OHLC arrays must have equal length; else
AsoError::MissingData. - Empty input →
AsoError::EmptyInputData; all-NaN close series →AsoError::AllValuesNaN. - Requires at least
periodvalid points after the first finite close; elseAsoError::NotEnoughValidData. - Warmup: indices
[0 .. first + period − 1)areNaNfor 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
AMD Ryzen 9 9950X (CPU) | NVIDIA RTX 4090 (GPU) | Benchmarks: 2026-02-28