Absolute Price Oscillator (APO)
short_period = 10 | long_period = 20 Overview
APO quantifies momentum by subtracting a long exponential moving average from a short EMA, displaying the result in absolute price units rather than percentages. When the fast EMA rises above the slow EMA, APO turns positive and indicates upward momentum proportional to the price difference. The calculation commonly uses a 12 period fast EMA and 26 period slow EMA, though traders adjust these based on their timeframe. Unlike percentage oscillators that normalize values, APO preserves the actual dollar or point difference between averages, making momentum directly comparable across different time periods for the same instrument. Traders use APO to identify trend strength when values expand from zero, spot divergences when price makes new highs while APO declines, and time entries when the oscillator crosses above zero after a pullback. The absolute price format particularly benefits traders who need to understand momentum in terms of actual price movement rather than relative percentages.
Implementation Examples
Get started with APO in just a few lines:
use vectorta::indicators::apo::{apo, ApoInput, ApoParams};
use vectorta::utilities::data_loader::{Candles, read_candles_from_csv};
// Using with price data slice
let prices = vec![100.0, 102.0, 101.5, 103.0, 105.0, 104.5];
let params = ApoParams {
short_period: Some(10),
long_period: Some(20),
};
let input = ApoInput::from_slice(&prices, params);
let result = apo(&input)?;
// Using with Candles data structure
// Quick and simple with default parameters (short=10, long=20; source="close")
let candles: Candles = read_candles_from_csv("data/sample.csv")?;
let input = ApoInput::with_default_candles(&candles);
let result = apo(&input)?;
// Access the APO values
for value in result.values {
println!("APO: {}", value);
} API Reference
Input Methods ▼
// From price slice
ApoInput::from_slice(&[f64], ApoParams) -> ApoInput
// From candles with custom source
ApoInput::from_candles(&Candles, &str, ApoParams) -> ApoInput
// From candles with default params (close prices, 10/20 periods)
ApoInput::with_default_candles(&Candles) -> ApoInput Parameters Structure ▼
pub struct ApoParams {
pub short_period: Option<usize>, // Default: 10
pub long_period: Option<usize>, // Default: 20
} Output Structure ▼
pub struct ApoOutput {
pub values: Vec<f64>, // APO values (short_ema - long_ema)
} Validation, Warmup & NaNs ▼
short_period > 0,long_period > 0, andshort_period < long_period.- There must be at least
long_periodvalid points after the first finite value; otherwiseApoError::NotEnoughValidData. - Indices before the first finite input are
NaN. The first finite output is0.0(both EMAs seeded to that price). - Streaming: leading
NaNs delay the first value; subsequentNaNs propagate.
Error Handling ▼
use vectorta::indicators::apo::ApoError;
match apo(&input) {
Ok(output) => process_results(output.values),
Err(ApoError::EmptyInputData) =>
println!("Input data is empty"),
Err(ApoError::ShortPeriodNotLessThanLong { short, long }) =>
println!("Short period {} must be < long period {}", short, long),
Err(ApoError::NotEnoughValidData { needed, valid }) =>
println!("Need {} data points, only {} valid", needed, valid),
Err(e) => println!("APO error: {}", e)
} Python Bindings
Basic Usage ▼
Calculate APO using NumPy arrays (defaults: short=10, long=20):
import numpy as np
from vectorta import apo
# Prepare price data as NumPy array
prices = np.array([100.0, 102.0, 101.5, 103.0, 105.0, 104.5])
# Calculate APO with defaults (10/20)
result = apo(prices)
# Or specify custom parameters
result = apo(prices, short_period=10, long_period=20)
# Specify kernel for performance optimization
result = apo(prices, short_period=10, long_period=20, kernel="avx2")
# Result is a NumPy array matching input length
print(f"APO values: {result}") Streaming Real-time Updates ▼
Process real-time price updates efficiently:
from vectorta import ApoStream
# Initialize streaming APO calculator
stream = ApoStream(short_period=10, long_period=20)
# Process real-time price updates
for price in price_feed:
apo_value = stream.update(price)
if apo_value is not None:
# APO value is ready (None during warmup period)
print(f"Current APO: {apo_value}")
# Make trading decisions based on APO
if apo_value > 0:
print("Momentum is positive")
else:
print("Momentum is negative") Batch Parameter Optimization ▼
Test multiple parameter combinations for optimization:
import numpy as np
from vectorta import apo_batch
# Your price data
prices = np.array([...]) # Your historical prices
# Define parameter ranges to test
# (start, end, step) for each parameter
short_range = (5, 20, 5) # Test: 5, 10, 15, 20
long_range = (15, 50, 5) # Test: 15, 20, 25, ..., 50
# Run batch calculation
results = apo_batch(
prices,
short_period_range=short_range,
long_period_range=long_range,
kernel="auto" # Auto-select best kernel
)
# Access results
print(f"Values shape: {results['values'].shape}") # (num_combinations, len(prices))
print(f"Short periods tested: {results['short_periods']}")
print(f"Long periods tested: {results['long_periods']}")
# Find best performing parameters
best_idx = np.argmax(some_performance_metric(results['values']))
best_short = results['short_periods'][best_idx]
best_long = results['long_periods'][best_idx] CUDA Acceleration ▼
CUDA support for APO is currently under development. The API will follow the same pattern as other CUDA-enabled indicators.
# Coming soon: CUDA-accelerated APO calculations
#
# from vectorta import apo_cuda_batch, apo_cuda_many_series_one_param
# import numpy as np
#
# # Option 1: One Series, Many Parameters (parameter optimization)
# # Test multiple parameter combinations on a single price series
# results = apo_cuda_batch(
# data=prices, # Single price series
# short_period_range=(5, 20, 1), # Test all shorts from 5 to 20
# long_period_range=(15, 50, 1), # Test all longs from 15 to 50
# device_id=0
# )
# # Returns:
# # - results['values']: 2D array [num_combinations x len(prices)]
# # - results['short_periods']: array of short periods tested
# # - results['long_periods']: array of long periods tested
#
# # Option 2: Many Series, One Parameter Set (portfolio processing)
# # Process multiple stocks/assets with the same APO parameters
# portfolio_data = np.array([...]) # Shape: [time_steps, num_assets]
#
# results = apo_cuda_many_series_one_param(
# data_tm=portfolio_data, # Time-major format [T, N]
# short_period=12,
# long_period=26,
# device_id=0
# )
# # Returns: 2D array [time_steps, num_assets] with APO for each asset
#
# # Zero-copy variant with pre-allocated output (F32 for GPU efficiency)
# out = np.empty((time_steps, num_assets), dtype=np.float32)
# apo_cuda_many_series_one_param_into(
# data_tm_f32=portfolio_data.astype(np.float32),
# short_period=12,
# long_period=26,
# out=out,
# device_id=0
# ) JavaScript/WASM Bindings
Basic Usage ▼
Calculate APO in JavaScript/TypeScript:
import { apo_js } from 'vectorta-wasm';
// Price data as Float64Array or regular array
const prices = new Float64Array([100.0, 102.0, 101.5, 103.0, 105.0, 104.5]);
// Calculate APO with specified parameters
const result = apo_js(prices, 10, 20); // short=10, long=20
// Result is a Float64Array
console.log('APO values:', result);
// TypeScript type definitions
interface ApoResult {
values: Float64Array;
}
// Use with async/await for better error handling
async function calculateAPO(prices: Float64Array): Promise<Float64Array> {
try {
return apo_js(prices, 10, 20);
} catch (error) {
console.error('APO calculation failed:', error);
throw error;
}
} Memory-Efficient Operations ▼
Use zero-copy operations for better performance with large datasets:
import { apo_alloc, apo_free, apo_into, memory } from 'vectorta-wasm';
// Prepare your price data
const prices = new Float64Array([/* your data */]);
const length = prices.length;
// Allocate WASM memory for input and output
const inPtr = apo_alloc(length);
const outPtr = apo_alloc(length);
// Copy input data into WASM memory
new Float64Array(memory.buffer, inPtr, length).set(prices);
// Calculate APO directly into allocated memory
// Args: in_ptr, out_ptr, len, short_period, long_period
apo_into(inPtr, outPtr, length, 10, 20);
// Read results from WASM memory (slice() to copy out)
const apoValues = new Float64Array(memory.buffer, outPtr, length).slice();
// Free allocated memory when done
apo_free(inPtr, length);
apo_free(outPtr, length);
console.log('APO values:', apoValues); Batch Processing ▼
Test multiple parameter combinations efficiently:
import { apo_batch_js, apo_batch_metadata_js } from 'vectorta-wasm';
// Your price data
const prices = new Float64Array([/* historical prices */]);
// Define parameter sweep ranges
const shortStart = 5, shortEnd = 20, shortStep = 5; // 5, 10, 15, 20
const longStart = 15, longEnd = 50, longStep = 5; // 15, 20, 25...50
// Get metadata about parameter combinations
const metadata = apo_batch_metadata_js(
shortStart, shortEnd, shortStep,
longStart, longEnd, longStep
);
// metadata contains [short1, long1, short2, long2, ...]
const numCombos = metadata.length / 2;
// Calculate all combinations
const results = apo_batch_js(
prices,
shortStart, shortEnd, shortStep,
longStart, longEnd, longStep
);
// Results is a flat array: [combo1_values..., combo2_values..., ...]
// Reshape based on your needs
const resultMatrix = [];
for (let i = 0; i < numCombos; i++) {
const start = i * prices.length;
const end = start + prices.length;
resultMatrix.push(results.slice(start, end));
}
// Access specific parameter combination results
const shortPeriod = metadata[0]; // First combo's short period
const longPeriod = metadata[1]; // First combo's long period
const apoValues = resultMatrix[0]; // First combo's APO values Performance Analysis
Across sizes, Rust CPU runs about 1.39× faster than Tulip C in this benchmark.
AMD Ryzen 9 9950X (CPU) | NVIDIA RTX 4090 (GPU) | Benchmarks: 2026-01-05