Moving Average Convergence/Divergence (MACD)
fast_period = 12 | slow_period = 26 | signal_period = 9 | ma_type = ema Overview
Moving Average Convergence Divergence, created by Gerald Appel in 1979, reveals changes in trend strength, direction, and momentum by calculating the difference between a 12 period EMA and a 26 period EMA, then smoothing this MACD line with a 9 period EMA signal line. The original parameter choice reflected market timing, with 12 representing two trading weeks, 26 representing one month, and 9 representing one and a half weeks when markets operated six days per week. Thomas Aspray enhanced the indicator in 1986 by adding the histogram component, which displays the difference between MACD and signal lines as vertical bars oscillating around zero, providing clearer visualization of momentum shifts. Traders generate signals through three primary methods: signal line crossovers where MACD crossing above signal suggests buying while crossing below indicates selling, centerline crossovers where MACD moving above zero confirms upward momentum, and divergences where price action contradicts MACD movement to forecast potential reversals. Since MACD derives from moving averages, it functions as a lagging indicator that confirms trends rather than predicting them, making it most effective in trending markets while producing whipsaws during consolidation periods.
Implementation Examples
Get MACD, Signal, and Histogram in a few lines:
use vectorta::indicators::macd::{macd, MacdInput, MacdParams};
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 = MacdParams {
fast_period: Some(12),
slow_period: Some(26),
signal_period: Some(9),
ma_type: Some("ema".to_string()),
};
let input = MacdInput::from_slice(&prices, params);
let out = macd(&input)?;
// Using with Candles structure (defaults: fast=12, slow=26, signal=9; source="close")
let candles: Candles = read_candles_from_csv("data/sample.csv")?;
let input = MacdInput::with_default_candles(&candles);
let out = macd(&input)?;
// Access MACD components
for i in 0..out.macd.len() {
println!("MACD: {} Signal: {} Hist: {}", out.macd[i], out.signal[i], out.hist[i]);
} API Reference
Input Methods ▼
// From price slice
MacdInput::from_slice(&[f64], MacdParams) -> MacdInput
// From candles with custom source
MacdInput::from_candles(&Candles, &str, MacdParams) -> MacdInput
// From candles with default params (close, 12/26/9)
MacdInput::with_default_candles(&Candles) -> MacdInput Parameters Structure ▼
pub struct MacdParams {
pub fast_period: Option<usize>, // Default: 12
pub slow_period: Option<usize>, // Default: 26
pub signal_period: Option<usize>, // Default: 9
pub ma_type: Option<String>, // Default: "ema"
} Output Structure ▼
pub struct MacdOutput {
pub macd: Vec<f64>, // MACD line
pub signal: Vec<f64>, // Signal line
pub hist: Vec<f64>, // Histogram = MACD - Signal
} Validation, Warmup & NaNs ▼
fast_period > 0,slow_period > 0,signal_period > 0; otherwiseMacdError::InvalidPeriod.- There must be at least
slow_periodvalid points after the first finite input; elseMacdError::NotEnoughValidData. - Warmup indices are
NaN: first MACD atfirst + slow − 1, first Signal/Hist atfirst + slow + signal − 2. ma_type: unknown identifiers returnMacdError::UnknownMA. EMA path is kernel-optimized.
Error Handling ▼
use vectorta::indicators::macd::{macd, MacdError};
match macd(&input) {
Ok(out) => process(out),
Err(MacdError::AllValuesNaN) =>
eprintln!("All input values are NaN"),
Err(MacdError::InvalidPeriod { fast, slow, signal, data_len }) =>
eprintln!("Invalid periods: fast={}, slow={}, signal={} (len={})", fast, slow, signal, data_len),
Err(MacdError::NotEnoughValidData { needed, valid }) =>
eprintln!("Need {} valid points after first finite; have {}", needed, valid),
Err(MacdError::UnknownMA(t)) =>
eprintln!("Unknown MA type: {}", t),
Err(MacdError::InvalidKernel(k)) =>
eprintln!("Invalid kernel for batch: {}", k),
} Python Bindings
Basic Usage ▼
Compute MACD components with NumPy arrays (EMA by default):
import numpy as np
from vectorta import macd
prices = np.array([100.0, 102.0, 101.5, 103.0, 105.0], dtype=np.float64)
# Explicit parameters (fast=12, slow=26, signal=9, ma_type="ema")
macd_arr, signal_arr, hist_arr = macd(
prices, 12, 26, 9, "ema", kernel="auto"
)
print(macd_arr.shape, signal_arr.shape, hist_arr.shape) Streaming Real-time Updates ▼
Process real-time MACD values:
from vectorta import MacdStream
stream = MacdStream(fast_period=12, slow_period=26, signal_period=9, ma_type="ema")
for price in price_feed:
triple = stream.update(price)
if triple is not None:
macd_val, sig_val, hist_val = triple
handle(macd_val, sig_val, hist_val) Batch Parameter Optimization ▼
Test multiple combinations and get structured outputs:
import numpy as np
from vectorta import macd_batch
prices = np.array([...], dtype=np.float64)
res = macd_batch(
prices,
fast_period_range=(8, 16, 4),
slow_period_range=(20, 30, 5),
signal_period_range=(9, 9, 0),
ma_type="ema",
kernel="auto"
)
# res is a dict with 2D arrays and metadata
MACD = res["macd"] # shape: [rows, len(prices)]
SIGNAL = res["signal"] # shape: [rows, len(prices)]
HIST = res["hist"] # shape: [rows, len(prices)]
fasts = res["fast_periods"]
slows = res["slow_periods"]
signals = res["signal_periods"] CUDA Acceleration ▼
CUDA support for MACD is coming soon.
# Coming soon: CUDA-accelerated MACD APIs JavaScript/WASM Bindings
Basic Usage ▼
Compute MACD, Signal, and Histogram in JS/TS:
import { macd_js } from 'vectorta-wasm';
const prices = new Float64Array([100, 102, 101.5, 103, 105]);
// Returns an object with concatenated values and shape info
const res = macd_js(prices, 12, 26, 9, 'ema');
const values = res.values; // Float64Array of length 3 * prices.length
const rows = res.rows; // 3
const cols = res.cols; // prices.length
// Reconstruct separate series
const macd = values.slice(0, cols);
const signal = values.slice(cols, 2 * cols);
const hist = values.slice(2 * cols, 3 * cols); Memory-Efficient Operations ▼
Use zero-copy operations with explicit buffers:
import { macd_alloc, macd_free, macd_into, memory } from 'vectorta-wasm';
const prices = new Float64Array([/* your data */]);
const len = prices.length;
// Allocate WASM buffers
const inPtr = macd_alloc(len);
const macdPtr = macd_alloc(len);
const sigPtr = macd_alloc(len);
const histPtr = macd_alloc(len);
// Copy input data into WASM memory
new Float64Array(memory.buffer, inPtr, len).set(prices);
// Compute directly into the provided output buffers
macd_into(inPtr, macdPtr, sigPtr, histPtr, len, 12, 26, 9, 'ema');
// Read back results (slice to copy out)
const macd = new Float64Array(memory.buffer, macdPtr, len).slice();
const signal = new Float64Array(memory.buffer, sigPtr, len).slice();
const hist = new Float64Array(memory.buffer, histPtr, len).slice();
// Free allocated buffers
macd_free(inPtr, len);
macd_free(macdPtr, len);
macd_free(sigPtr, len);
macd_free(histPtr, len); Batch Processing ▼
Test multiple parameter combinations with metadata:
import { macd_batch_js } from 'vectorta-wasm';
const prices = new Float64Array([/* historical prices */]);
const config = {
fast_period_range: [8, 16, 4],
slow_period_range: [20, 30, 5],
signal_period_range: [9, 9, 0],
ma_type: 'ema',
};
const out = macd_batch_js(prices, config);
// out: { values: Float64Array, rows: number, cols: number, fast_periods: number[], slow_periods: number[], signal_periods: number[] }
// values layout: [macd block | signal block | hist block], each of length rows*cols
const { values, rows, cols, fast_periods, slow_periods, signal_periods } = out; Performance Analysis
Across sizes, Rust CPU runs about 1.43× faster than Tulip C in this benchmark.
AMD Ryzen 9 9950X (CPU) | NVIDIA RTX 4090 (GPU) | Benchmarks: 2026-01-05