Volume Weighted MACD (VWMACD)

Parameters: fast_period = 12 | slow_period = 26 | signal_period = 9 | fast_ma_type = sma | slow_ma_type = sma | signal_ma_type = ema

Overview

Buff Dormeier created the Volume Weighted MACD in 2000 as a straightforward yet powerful enhancement to Gerald Appel's original MACD indicator by replacing exponential moving averages with volume-weighted moving averages. This modification makes the indicator more responsive to price movements that occur with significant volume, filtering out low-volume noise that often creates false signals in traditional MACD. The indicator calculates the difference between fast and slow volume-weighted moving averages to form the MACD line, then applies an exponential moving average to generate the signal line, keeping it unweighted since the MACD line already incorporates volume. When volume confirms price movements, the VWMACD responds more quickly than its traditional counterpart, providing earlier signals at potential turning points. The histogram visualizes momentum shifts by displaying the difference between the MACD and signal lines, expanding during strong trends and contracting at potential reversals. By weighting price changes according to their accompanying volume, VWMACD reveals whether institutional money supports price movements, making it particularly effective for identifying genuine breakouts versus low-volume drift that often precedes reversals.

Implementation Examples

Get started with VWMACD using price and volume data:

use vectorta::indicators::vwmacd::{vwmacd, VwmacdInput, VwmacdParams};
use vectorta::utilities::data_loader::{Candles, read_candles_from_csv};

// Using with slices (close and volume of equal length)
let close = vec![100.0, 102.0, 101.5, 103.0, 105.0, 104.5];
let volume = vec![1200.0, 1500.0, 1300.0, 2000.0, 1800.0, 1600.0];
let params = VwmacdParams { 
    fast_period: Some(12),
    slow_period: Some(26),
    signal_period: Some(9),
    fast_ma_type: Some("sma".to_string()),
    slow_ma_type: Some("sma".to_string()),
    signal_ma_type: Some("ema".to_string()),
};
let input = VwmacdInput::from_slices(&close, &volume, params);
let out = vwmacd(&input)?; // -> VwmacdOutput { macd, signal, hist }

// Using with Candles (defaults: sources "close"/"volume", 12/26 VWMAs, 9 EMA signal)
let candles: Candles = read_candles_from_csv("data/sample.csv")?;
let input = VwmacdInput::with_default_candles(&candles);
let out = vwmacd(&input)?;

// Access the 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 close/volume slices (equal length)
VwmacdInput::from_slices(&[f64], &[f64], VwmacdParams) -> VwmacdInput

// From candles with custom sources
VwmacdInput::from_candles(&Candles, &str /*close_source*/, &str /*volume_source*/, VwmacdParams) -> VwmacdInput

// From candles with defaults (sources: "close"/"volume"; periods 12/26; signal 9 EMA)
VwmacdInput::with_default_candles(&Candles) -> VwmacdInput
Parameters Structure
pub struct VwmacdParams {
    pub fast_period: Option<usize>,    // Default: 12
    pub slow_period: Option<usize>,    // Default: 26
    pub signal_period: Option<usize>,  // Default: 9
    pub fast_ma_type: Option<String>,  // Default: "sma" (for fast VWMA)
    pub slow_ma_type: Option<String>,  // Default: "sma" (for slow VWMA)
    pub signal_ma_type: Option<String>,// Default: "ema" (signal)
}
Output Structure
pub struct VwmacdOutput {
    pub macd: Vec<f64>,   // VWMACD line: VWMA_fast - VWMA_slow
    pub signal: Vec<f64>, // Signal MA of MACD (default EMA 9)
    pub hist: Vec<f64>,   // Histogram: MACD - Signal
}
Validation, Warmup & NaNs
  • Inputs must be same length; otherwise VwmacdError::InvalidPeriod.
  • VwmacdError::AllValuesNaN if all close or all volume values are NaN.
  • VwmacdError::InvalidPeriod if any of fast, slow, signal is 0 or greater than data length.
  • VwmacdError::NotEnoughValidData if valid samples after the first finite pair are fewer than slow.
  • Warmup prefixes: macd[.. first + max(fast, slow) - 1] = NaN and signal/hist[.. macd_warmup + signal - 1] = NaN.
  • Where the VWMA denominator window sums to 0.0, outputs are NaN (no error).
Error Handling
use vectorta::indicators::vwmacd::{vwmacd, VwmacdError};

match vwmacd(&input) {
    Ok(out) => process(out),
    Err(VwmacdError::AllValuesNaN) => eprintln!("All inputs are NaN"),
    Err(VwmacdError::InvalidPeriod { fast, slow, signal, data_len }) =>
        eprintln!("Invalid periods: fast={}, slow={}, signal={}, len={}", fast, slow, signal, data_len),
    Err(VwmacdError::NotEnoughValidData { needed, valid }) =>
        eprintln!("Need {} valid samples after first finite pair, have {}", needed, valid),
    Err(VwmacdError::MaError(msg)) => eprintln!("MA error: {}", msg),
}

Python Bindings

Basic Usage

Calculate VWMACD using NumPy arrays of close and volume:

import numpy as np
from vectorta import vwmacd, VwmacdStream

close = np.array([100.0, 102.0, 101.5, 103.0, 105.0, 104.5])
volume = np.array([1200.0, 1500.0, 1300.0, 2000.0, 1800.0, 1600.0])

# Returns (macd, signal, hist) arrays
macd, signal, hist = vwmacd(
    close, volume,
    fast=12, slow=26, signal=9,
    fast_ma_type="sma", slow_ma_type="sma", signal_ma_type="ema",
    kernel="auto"
)

# Streaming API
stream = VwmacdStream(fast_period=12, slow_period=26, signal_period=9,
                      fast_ma_type="sma", slow_ma_type="sma", signal_ma_type="ema")
for c, v in zip(close, volume):
    m, s, h = stream.update(float(c), float(v))
    if m is not None:
        pass  # handle values
Batch Processing

Test multiple parameter combinations:

import numpy as np
from vectorta import vwmacd_batch

close = np.array([...])
volume = np.array([...])

result = vwmacd_batch(
    close, volume,
    fast_range=(8, 16, 4),
    slow_range=(20, 32, 6),
    signal_range=(7, 11, 2),
    fast_ma_type="sma",
    slow_ma_type="sma",
    signal_ma_type="ema",
    kernel="auto"
)

# result is a dict with flattened arrays reshaped as (rows, cols):
#   macd, signal, hist, plus parameter vectors
print(result["macd"].shape, result["signal"].shape, result["hist"].shape)
print(result["fast_periods"], result["slow_periods"], result["signal_periods"])
CUDA Acceleration

CUDA support for VWMACD is coming soon. APIs will follow the pattern of other CUDA-enabled indicators.

JavaScript/WASM Bindings

Basic Usage

Calculate VWMACD in JavaScript/TypeScript:

import { vwmacd_js } from 'vectorta-wasm';

const close = new Float64Array([/* close prices */]);
const volume = new Float64Array([/* volumes */]);

// Returns a flattened Float64Array: [macd..., signal..., hist...] (length = 3 * N)
const flat = vwmacd_js(close, volume, 12, 26, 9, 'sma', 'sma', 'ema');

// Helper to slice components
function split(flat: Float64Array) {
  const n = flat.length / 3;
  return {
    macd: flat.slice(0, n),
    signal: flat.slice(n, 2*n),
    hist: flat.slice(2*n),
  };
}
const { macd, signal, hist } = split(flat);
console.log(macd[macd.length-1], signal[signal.length-1], hist[hist.length-1]);
Memory-Efficient Operations

Use zero-copy into pre-allocated buffers:

import { vwmacd_alloc, vwmacd_free, vwmacd_into, memory } from 'vectorta-wasm';

const n = close.length;

// Allocate WASM memory for inputs and outputs
const closePtr = vwmacd_alloc(n);
const volumePtr = vwmacd_alloc(n);
const macdPtr = vwmacd_alloc(n);
const signalPtr = vwmacd_alloc(n);
const histPtr = vwmacd_alloc(n);

// Copy inputs into WASM memory
new Float64Array(memory.buffer, closePtr, n).set(close);
new Float64Array(memory.buffer, volumePtr, n).set(volume);

// Compute directly into WASM memory
vwmacd_into(closePtr, volumePtr, macdPtr, signalPtr, histPtr, n, 12, 26, 9, 'sma', 'sma', 'ema');

// Read results
const macd = new Float64Array(memory.buffer, macdPtr, n).slice();
const signal = new Float64Array(memory.buffer, signalPtr, n).slice();
const hist = new Float64Array(memory.buffer, histPtr, n).slice();

// Free allocations
vwmacd_free(closePtr, n);
vwmacd_free(volumePtr, n);
vwmacd_free(macdPtr, n);
vwmacd_free(signalPtr, n);
vwmacd_free(histPtr, n);
Batch Processing

Unified batch API for values and combos:

import { vwmacd_batch } from 'vectorta-wasm';

const cfg = {
  fast_range: [8, 16, 4],
  slow_range: [20, 32, 6],
  signal_range: [7, 11, 2],
  fast_ma_type: 'sma',
  slow_ma_type: 'sma',
  signal_ma_type: 'ema',
};
const { values, combos, rows, cols } = vwmacd_batch(close, volume, cfg);

// Row helper
const rowSeries = (row: number) => {
  const start = row * cols;
  return {
    macd: Float64Array.from(values.slice(start, start + cols)),
    signal: Float64Array.from(values.slice(start + rows*cols, start + rows*cols + cols)),
    hist: Float64Array.from(values.slice(start + 2*rows*cols, start + 2*rows*cols + cols)),
  };
};

console.log('First combo:', combos[0]);
console.log('MACD series for first combo:', rowSeries(0).macd);

Performance Analysis

Comparison:
View:
Loading chart...

AMD Ryzen 9 9950X (CPU) | NVIDIA RTX 4090 (GPU) | Benchmarks: 2026-01-05

Related Indicators