Impulse MACD

Parameters: length_ma = 34 | length_signal = 9

Overview

Impulse MACD combines a zero-centered impulse line with a signal line and histogram. For each bar, the implementation starts from typical price, smooths high and low with SMMA(length_ma), smooths the typical price twice with EMA(length_ma), and forms an impulse midpoint from that double-smoothed structure. The impulse_macd output is the distance from that midpoint to the smoothed high or low band when the midpoint pushes outside the envelope, otherwise 0.0. The signal output is an SMA of impulse_macd over length_signal, and impulse_histo is the difference between impulse_macd and signal once the signal line is warmed up.

Defaults: length_ma = 34 and length_signal = 9. Candle input uses built-in high, low, and close fields directly; there is no separate source selector.

Implementation Examples

Calculate Impulse MACD from slices or candles:

use vector_ta::indicators::impulse_macd::{
    impulse_macd, ImpulseMacdInput, ImpulseMacdParams,
};
use vector_ta::utilities::data_loader::{Candles, read_candles_from_csv};

let high = vec![101.0, 102.5, 103.2, 104.0, 105.4];
let low = vec![99.4, 100.3, 101.1, 101.8, 103.0];
let close = vec![100.2, 101.8, 102.7, 103.4, 104.9];

let input = ImpulseMacdInput::from_slices(
    &high,
    &low,
    &close,
    ImpulseMacdParams::default(),
);
let out = impulse_macd(&input)?;

// Candles use built-in high/low/close fields
let candles: Candles = read_candles_from_csv("data/sample.csv")?;
let out_from_candles = impulse_macd(&ImpulseMacdInput::with_default_candles(&candles))?;

for ((md, hist), signal) in out
    .impulse_macd
    .iter()
    .zip(out.impulse_histo.iter())
    .zip(out.signal.iter())
{
    println!("md={:.4}, hist={:.4}, signal={:.4}", md, hist, signal);
}

API Reference

Input Methods
// From candles (uses candles.high, candles.low, candles.close)
ImpulseMacdInput::from_candles(&Candles, ImpulseMacdParams) -> ImpulseMacdInput

// From explicit OHLC slices
ImpulseMacdInput::from_slices(&[f64], &[f64], &[f64], ImpulseMacdParams) -> ImpulseMacdInput

// From candles with default params
ImpulseMacdInput::with_default_candles(&Candles) -> ImpulseMacdInput
Parameters and Outputs
pub struct ImpulseMacdParams {
    pub length_ma: Option<usize>,      // default 34
    pub length_signal: Option<usize>,  // default 9
}

pub struct ImpulseMacdOutput {
    pub impulse_macd: Vec<f64>,
    pub impulse_histo: Vec<f64>,
    pub signal: Vec<f64>,
}
Builder, Stream, and Batch Types
ImpulseMacdBuilder::new()
    .length_ma(usize)
    .length_signal(usize)
    .kernel(Kernel)
    .apply(&Candles) -> Result<ImpulseMacdOutput, ImpulseMacdError>

ImpulseMacdBuilder::new()
    .length_ma(usize)
    .length_signal(usize)
    .kernel(Kernel)
    .apply_slices(&[f64], &[f64], &[f64]) -> Result<ImpulseMacdOutput, ImpulseMacdError>

ImpulseMacdBuilder::new()
    .length_ma(usize)
    .length_signal(usize)
    .into_stream() -> Result<ImpulseMacdStream, ImpulseMacdError>

ImpulseMacdStream::try_new(ImpulseMacdParams) -> Result<ImpulseMacdStream, ImpulseMacdError>
ImpulseMacdStream::update(high, low, close) -> Option<(f64, f64, f64)>
ImpulseMacdStream::update_reset_on_nan(high, low, close) -> Option<(f64, f64, f64)>

ImpulseMacdBatchBuilder::new()
    .kernel(Kernel)
    .length_ma_range(start, end, step)
    .length_signal_range(start, end, step)
    .length_ma_static(value)
    .length_signal_static(value)
    .apply_slices(&[f64], &[f64], &[f64]) -> Result<ImpulseMacdBatchOutput, ImpulseMacdError>

ImpulseMacdBatchBuilder::new()
    .apply_candles(&Candles) -> Result<ImpulseMacdBatchOutput, ImpulseMacdError>
Calculation, Warmup, and NaN Behavior
  • Valid bars require finite high, low, and close, and also high >= low.
  • The implementation requires non-empty equal-length OHLC inputs plus 1 <= length_ma,length_signal <= data_len.
  • It finds the first valid bar, then requires at least max(length_ma, length_signal) valid bars from that point or returns ImpulseMacdError::NotEnoughValidData.
  • impulse_macd is NaN before the first valid bar. After that, bars can still be 0.0 while the SMMA high/low channel is not ready.
  • signal and impulse_histo are NaN until length_signal valid impulse values have accumulated, so the warmup boundary is first_valid + length_signal - 1.
  • Batch and slice paths call update_reset_on_nan, so any invalid bar resets internal state and writes NaN at that index before the next valid segment starts over.
  • The core formula is src = (high + low + close) / 3, mi = ema1 + (ema1 - ema2), then impulse_macd = mi - hi_smma if mi > hi_smma, mi - lo_smma if mi < lo_smma, else 0.0.
Batch Output and Errors
pub struct ImpulseMacdBatchOutput {
    pub impulse_macd: Vec<f64>,
    pub impulse_histo: Vec<f64>,
    pub signal: Vec<f64>,
    pub combos: Vec<ImpulseMacdParams>,
    pub rows: usize,
    pub cols: usize,
}

// Common errors:
// - EmptyInputData
// - DataLengthMismatch
// - AllValuesNaN
// - InvalidLengthMa / InvalidLengthSignal
// - NotEnoughValidData
// - OutputLengthMismatch
// - InvalidRange
// - InvalidKernelForBatch

Python Bindings

Basic Usage

The Python function returns three NumPy arrays in order:

import numpy as np
from vector_ta import impulse_macd

high = np.array([101.0, 102.5, 103.2, 104.0, 105.4], dtype=np.float64)
low = np.array([99.4, 100.3, 101.1, 101.8, 103.0], dtype=np.float64)
close = np.array([100.2, 101.8, 102.7, 103.4, 104.9], dtype=np.float64)

impulse_macd_values, impulse_histo, signal = impulse_macd(
    high,
    low,
    close,
    length_ma=34,
    length_signal=9,
    kernel="auto",
)

print(impulse_macd_values.shape, impulse_histo.shape, signal.shape)
Streaming Real-time Updates

The Python stream wrapper resets automatically on invalid bars:

from vector_ta import ImpulseMacdStream

stream = ImpulseMacdStream(length_ma=34, length_signal=9)

for high, low, close in live_feed:
    result = stream.update(high, low, close)
    if result is not None:
        impulse_macd_value, impulse_histo_value, signal_value = result
        print(impulse_macd_value, impulse_histo_value, signal_value)
Batch Processing

Batch output keys match the Rust batch contract plus parameter columns:

import numpy as np
from vector_ta import impulse_macd_batch

result = impulse_macd_batch(
    high,
    low,
    close,
    length_ma_range=(21, 55, 7),
    length_signal_range=(9, 9, 0),
    kernel="auto",
)

md = result["impulse_macd"]
hist = result["impulse_histo"]
signal = result["signal"]
length_mas = result["length_mas"]
length_signals = result["length_signals"]
rows = result["rows"]
cols = result["cols"]

JavaScript/WASM Bindings

Basic Usage

The WASM function returns a plain object with three arrays:

import { impulse_macd_js } from 'vectorta-wasm';

const high = new Float64Array([101.0, 102.5, 103.2, 104.0, 105.4]);
const low = new Float64Array([99.4, 100.3, 101.1, 101.8, 103.0]);
const close = new Float64Array([100.2, 101.8, 102.7, 103.4, 104.9]);

const result = impulse_macd_js(high, low, close, 34, 9) as {
  impulse_macd: number[];
  impulse_histo: number[];
  signal: number[];
};

console.log(result.impulse_macd);
console.log(result.impulse_histo);
console.log(result.signal);
Host Buffer API

Low-level output buffers are packed as impulse_macd, then impulse_histo, then signal:

import {
  impulse_macd_alloc,
  impulse_macd_free,
  impulse_macd_into_host,
} from 'vectorta-wasm';

const len = close.length;
const ptr = impulse_macd_alloc(len);

try {
  impulse_macd_into_host(high, low, close, ptr, 34, 9);

  // Read 3 * len float64 values from WASM memory:
  // [impulse_macd..., impulse_histo..., signal...]
} finally {
  impulse_macd_free(ptr, len);
}
Batch Processing

Batch config uses two 3-element ranges: [start, end, step].

import { impulse_macd_batch_js } from 'vectorta-wasm';

const batch = impulse_macd_batch_js(high, low, close, {
  length_ma_range: [21, 55, 7],
  length_signal_range: [9, 9, 0],
}) as {
  impulse_macd: number[];
  impulse_histo: number[];
  signal: number[];
  rows: number;
  cols: number;
  combos: Array<{ length_ma?: number; length_signal?: number }>;
};

console.log(batch.rows, batch.cols);
console.log(batch.combos[0]);

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