Adaptive MACD

Parameters: 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 returns AdaptiveMacdError::AllValuesNaN.
  • length, fast_period, slow_period, and signal_period must all be at least 2 and no larger than the data length.
  • The indicator requires at least length valid points after the first non-NaN value; otherwise it returns AdaptiveMacdError::NotEnoughValidData.
  • Single-run output is allocated with a warmup prefix of first_valid + length - 1, so the earliest bars remain NaN while the adaptive state initializes.
  • Streaming correlation state resets when it receives a non-finite input.
  • Batch kernels must be batch-capable or AdaptiveMacdError::InvalidKernelForBatch is 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

Comparison:
View:
Placeholder data (no recorded benchmarks for this indicator)

Across sizes, Rust CPU runs about 1.14× faster than Tulip C in this benchmark.

Loading chart...

AMD Ryzen 9 9950X (CPU) | NVIDIA RTX 4090 (GPU)

Related Indicators