Adaptive MACD
length = 20 | fast_period = 10 | slow_period = 20 | signal_period = 9 Overview
Adaptive MACD keeps the familiar MACD readout of line, signal, and histogram, but it adjusts its internal response using rolling correlation strength instead of relying on a fully static oscillator backbone. In the VectorTA implementation, the close series is fed through an adaptive state machine that tracks correlation, derives a responsive MACD curve, and then applies an EMA-like smoothing pass to generate the signal line and histogram. The result is a momentum oscillator that can react more quickly during orderly directional moves and become more restrained when the underlying series loses structure.
Practically, traders can read the `macd` output as the primary adaptive momentum line, use `signal` for crossovers and smoothing confirmation, and watch `hist` for momentum expansion or contraction around the zero line. Because the implementation resets around non-finite values and respects a warmup window tied to the adaptive length, the earliest bars should be treated as initialization rather than live trading signals.
Defaults: Adaptive MACD uses `length = 20`, `fast_period = 10`, `slow_period = 20`, and `signal_period = 9`.
Implementation Examples
Run Adaptive MACD on a close series or directly from candles:
use vector_ta::indicators::adaptive_macd::{
adaptive_macd, AdaptiveMacdInput, AdaptiveMacdParams
};
use vector_ta::utilities::data_loader::{Candles, read_candles_from_csv};
let close = vec![100.0, 101.2, 102.4, 101.8, 103.1, 104.0, 103.6];
let params = AdaptiveMacdParams {
length: Some(20),
fast_period: Some(10),
slow_period: Some(20),
signal_period: Some(9),
};
let input = AdaptiveMacdInput::from_slice(&close, params);
let result = adaptive_macd(&input)?;
let candles: Candles = read_candles_from_csv("data/sample.csv")?;
let candle_input = AdaptiveMacdInput::with_default_candles(&candles);
let candle_result = adaptive_macd(&candle_input)?;
println!("Latest adaptive MACD: {:?}", result.macd.last());
println!("Latest signal: {:?}", result.signal.last());
println!("Latest histogram: {:?}", result.hist.last());
println!("Candle output length: {}", candle_result.macd.len()); API Reference
Input Methods ▼
// From candles using an explicit source field
AdaptiveMacdInput::from_candles(&Candles, "close", AdaptiveMacdParams) -> AdaptiveMacdInput
// From a raw slice
AdaptiveMacdInput::from_slice(&[f64], AdaptiveMacdParams) -> AdaptiveMacdInput
// From candles with the default close source and default params
AdaptiveMacdInput::with_default_candles(&Candles) -> AdaptiveMacdInput Parameters Structure ▼
pub struct AdaptiveMacdParams {
pub length: Option<usize>, // default: 20
pub fast_period: Option<usize>, // default: 10
pub slow_period: Option<usize>, // default: 20
pub signal_period: Option<usize>, // default: 9
} Output Structure ▼
pub struct AdaptiveMacdOutput {
pub macd: Vec<f64>, // adaptive MACD line
pub signal: Vec<f64>, // smoothed signal line
pub hist: Vec<f64>, // macd - signal
} Validation, Warmup & NaNs ▼
- Empty input returns
AdaptiveMacdError::EmptyInputData. - If every value is
NaN, the function returnsAdaptiveMacdError::AllValuesNaN. length,fast_period,slow_period, andsignal_periodmust all be at least2and no larger than the data length.- The indicator requires at least
lengthvalid points after the first non-NaN value; otherwise it returnsAdaptiveMacdError::NotEnoughValidData. - Single-run output is allocated with a warmup prefix of
first_valid + length - 1, so the earliest bars remainNaNwhile the adaptive state initializes. - Streaming correlation state resets when it receives a non-finite input.
- Batch kernels must be batch-capable or
AdaptiveMacdError::InvalidKernelForBatchis returned.
Error Handling ▼
use vector_ta::indicators::adaptive_macd::AdaptiveMacdError;
match adaptive_macd(&input) {
Ok(output) => {
println!("hist last = {:?}", output.hist.last());
}
Err(AdaptiveMacdError::EmptyInputData) =>
eprintln!("Adaptive MACD needs at least one close value."),
Err(AdaptiveMacdError::AllValuesNaN) =>
eprintln!("Adaptive MACD cannot start from an all-NaN series."),
Err(AdaptiveMacdError::InvalidPeriod { length, fast, slow, signal, data_len }) =>
eprintln!("Invalid periods: length={length}, fast={fast}, slow={slow}, signal={signal}, data_len={data_len}"),
Err(AdaptiveMacdError::NotEnoughValidData { needed, valid }) =>
eprintln!("Need {needed} valid points after the first finite value, got {valid}."),
Err(AdaptiveMacdError::OutputLengthMismatch { expected, got }) =>
eprintln!("Output buffers must be {expected} long, got {got}."),
Err(AdaptiveMacdError::InvalidRange { axis, start, end, step }) =>
eprintln!("Bad batch range for {axis}: ({start}, {end}, {step})"),
Err(AdaptiveMacdError::InvalidKernelForBatch(kernel)) =>
eprintln!("Kernel {:?} cannot be used for Adaptive MACD batch mode.", kernel),
} Python Bindings
Basic Usage ▼
The Python binding returns three NumPy arrays: MACD, signal, and histogram.
import numpy as np
from vector_ta import adaptive_macd
close = np.array([100.0, 101.2, 102.4, 101.8, 103.1, 104.0, 103.6], dtype=np.float64)
macd, signal, hist = adaptive_macd(
close,
length=20,
fast_period=10,
slow_period=20,
signal_period=9,
kernel="auto",
)
print(macd[-5:])
print(signal[-5:])
print(hist[-5:]) Streaming Real-time Updates ▼
Use the streaming wrapper when close values arrive one bar at a time.
from vector_ta import AdaptiveMacdStream
stream = AdaptiveMacdStream(
length=20,
fast_period=10,
slow_period=20,
signal_period=9,
)
for close in live_close_feed:
value = stream.update(close)
if value is not None:
macd, signal, hist = value
print(macd, signal, hist) Batch Processing ▼
Batch mode returns a dictionary with flattened outputs and the parameter grid used for each row.
import numpy as np
from vector_ta import adaptive_macd_batch
close = np.asarray(load_close_series(), dtype=np.float64)
result = adaptive_macd_batch(
close,
length_range=(16, 24, 4),
fast_period_range=(8, 12, 2),
slow_period_range=(18, 24, 3),
signal_period_range=(7, 9, 2),
kernel="auto",
)
print(result["rows"], result["cols"])
print(result["lengths"])
print(result["macd"].shape, result["signal"].shape, result["hist"].shape) JavaScript/WASM Bindings
Basic Usage ▼
The WASM helper returns a serialized object with `macd`, `signal`, and `hist` arrays.
import { adaptive_macd_js } from 'vectorta-wasm';
const close = new Float64Array([100.0, 101.2, 102.4, 101.8, 103.1, 104.0, 103.6]);
const result = adaptive_macd_js(close, 20, 10, 20, 9) as {
macd: number[];
signal: number[];
hist: number[];
};
console.log(result.macd.slice(-5));
console.log(result.signal.slice(-5));
console.log(result.hist.slice(-5)); Memory-Efficient Operations ▼
Use the allocation helpers plus `adaptive_macd_into` when you want explicit buffer control.
import {
adaptive_macd_alloc,
adaptive_macd_free,
adaptive_macd_into,
memory,
} from 'vectorta-wasm';
const close = new Float64Array([/* close prices */]);
const len = close.length;
const inPtr = adaptive_macd_alloc(len);
const macdPtr = adaptive_macd_alloc(len);
const signalPtr = adaptive_macd_alloc(len);
const histPtr = adaptive_macd_alloc(len);
new Float64Array(memory.buffer, inPtr, len).set(close);
adaptive_macd_into(inPtr, macdPtr, signalPtr, histPtr, len, 20, 10, 20, 9);
const macd = new Float64Array(memory.buffer, macdPtr, len).slice();
const signal = new Float64Array(memory.buffer, signalPtr, len).slice();
const hist = new Float64Array(memory.buffer, histPtr, len).slice();
adaptive_macd_free(inPtr, len);
adaptive_macd_free(macdPtr, len);
adaptive_macd_free(signalPtr, len);
adaptive_macd_free(histPtr, len); Batch Processing ▼
The unified batch helper accepts a range config and returns rows, cols, combos, and flattened outputs.
import { adaptive_macd_batch } from 'vectorta-wasm';
const close = new Float64Array([/* historical closes */]);
const batch = adaptive_macd_batch(close, {
length_range: [16, 24, 4],
fast_period_range: [8, 12, 2],
slow_period_range: [18, 24, 3],
signal_period_range: [7, 9, 2],
}) as {
macd: number[];
signal: number[];
hist: number[];
combos: Array<{
length?: number;
fast_period?: number;
slow_period?: number;
signal_period?: number;
}>;
rows: number;
cols: number;
};
console.log(batch.rows, batch.cols);
console.log(batch.combos[0]);
console.log(batch.hist.slice(0, batch.cols)); CUDA Bindings (Rust)
Additional details for the CUDA bindings can be found inside the VectorTA repository.
Performance Analysis
Across sizes, Rust CPU runs about 1.14× faster than Tulip C in this benchmark.
AMD Ryzen 9 9950X (CPU) | NVIDIA RTX 4090 (GPU)
Related Indicators
Acceleration Oscillator
Technical analysis indicator
Accumulation/Distribution
Technical analysis indicator
Awesome Oscillator
Technical analysis indicator
Absolute Price Oscillator
Technical analysis indicator
Commodity Channel Index
Technical analysis indicator
CCI Cycle
Technical analysis indicator