Dual Volume Divergence QQE (DVDIQQE)

Parameters: period = 13 | smoothing_period = 6 | fast_multiplier = 2.618 | slow_multiplier = 4.236 | volume_type = default | center_type = dynamic

Overview

The Dual Volume Divergence QQE (DVDIQQE) combines volume analysis with momentum oscillation by merging concepts from the Positive and Negative Volume Indexes with QQE style adaptive trailing levels. The indicator first constructs a Dual Volume Divergence Index (DVDI) by analyzing how price responds differently on up volume days versus down volume days. This process involves calculating separate EMAs of the Positive Volume Index and Negative Volume Index, then measuring their divergence to reveal whether accumulation or distribution dominates. The resulting oscillator then receives QQE treatment, with fast and slow trailing levels that adapt to volatility, creating dynamic bands around the main signal. The center line can operate in dynamic mode, adjusting to the cumulative average of the oscillator, or static mode with a fixed zero baseline.

DVDIQQE outputs four distinct series that work together to signal market conditions. The main DVDI oscillator reveals volume driven momentum, oscillating above and below the center line to indicate accumulation versus distribution phases. Fast and slow trailing levels create adaptive bands that expand during volatile periods and contract during quiet accumulation or distribution. When DVDI crosses these trailing levels, it signals potential trend changes with the volume conviction to sustain them. The configurable center line provides flexibility in interpretation, with dynamic mode revealing long term bias shifts and static mode offering traditional oscillator analysis. The Fibonacci based default multipliers (2.618 and 4.236) align the trailing levels with natural market rhythms.

Traders use DVDIQQE to identify volume confirmed momentum shifts and potential reversals where price and volume diverge. The indicator excels at spotting stealth accumulation or distribution phases where price appears stable but volume patterns reveal underlying institutional activity. Crosses above the fast trailing level with expanding volume suggest breakout potential, while drops below signal distribution and potential declines. The slow trailing level acts as a stronger confirmation, with crosses indicating major trend changes backed by significant volume shifts. Many traders combine DVDIQQE with price based indicators, using it as a volume filter to confirm or reject signals from traditional technical analysis.

Implementation Examples

Compute DVDIQQE from either slices or the Candles helper. Outputs include dvdi, fast_tl, slow_tl, and center_line.

use vector_ta::indicators::dvdiqqe::{dvdiqqe, DvdiqqeInput, DvdiqqeParams};
use vector_ta::utilities::data_loader::{Candles, read_candles_from_csv};

// Using with OHLC[V] slices
let (open, high, low, close) = (
    vec![100.0, 101.0, 102.0],
    vec![101.0, 102.0, 103.0],
    vec![ 99.0, 100.0, 101.0],
    vec![100.5, 101.5, 102.5],
);
let volume: Option<Vec<f64>> = None; // or Some(vec![...])
let params = DvdiqqeParams { period: Some(13), smoothing_period: Some(6), fast_multiplier: Some(2.618), slow_multiplier: Some(4.236), volume_type: Some("default".into()), center_type: Some("dynamic".into()), tick_size: Some(0.01) };
let input = DvdiqqeInput::from_slices(&open, &high, &low, &close, volume.as_deref(), params);
let out = dvdiqqe(&input)?;

// Using Candles with defaults
let candles: Candles = read_candles_from_csv("data/sample.csv")?;
let out = dvdiqqe(&DvdiqqeInput::with_default_candles(&candles))?;

// Access series
for (i, v) in out.dvdi.iter().enumerate() {
    println!("t={i}: dvdi={}, fast_tl={}, slow_tl={}, center={}", v, out.fast_tl[i], out.slow_tl[i], out.center_line[i]);
}

API Reference

Input Methods
// From candles
DvdiqqeInput::from_candles(&Candles, DvdiqqeParams) -> DvdiqqeInput

// From raw OHLC[V] slices
DvdiqqeInput::from_slices(&[f64], &[f64], &[f64], &[f64], Option<&[f64]>, DvdiqqeParams) -> DvdiqqeInput

// With defaults (period=13, smoothing=6, multipliers=2.618/4.236, volume="default", center="dynamic", tick=0.01)
DvdiqqeInput::with_default_candles(&Candles) -> DvdiqqeInput
Parameters Structure
#[derive(Debug, Clone)]
pub struct DvdiqqeParams {
    pub period: Option<usize>,            // Default: 13 (must be > 0)
    pub smoothing_period: Option<usize>,  // Default: 6 (must be > 0)
    pub fast_multiplier: Option<f64>,     // Default: 2.618 (> 0)
    pub slow_multiplier: Option<f64>,     // Default: 4.236 (> 0)
    pub volume_type: Option<String>,      // Default: "default" (or "tick")
    pub center_type: Option<String>,      // Default: "dynamic" (or "static")
    pub tick_size: Option<f64>,           // Default: 0.01 (> 0)
}
Output Structure
pub struct DvdiqqeOutput {
    pub dvdi: Vec<f64>,       // Oscillator
    pub fast_tl: Vec<f64>,    // Fast trailing level
    pub slow_tl: Vec<f64>,    // Slow trailing level
    pub center_line: Vec<f64> // Center (dynamic mean or 0)
}
Validation, Warmup & NaNs
  • period > 0, smoothing_period > 0, fast_multiplier > 0, slow_multiplier > 0, and tick_size > 0.
  • All OHLC arrays must be same length; optional volume must match length if provided, else fallback rules apply.
  • First finite close index defines the start of valid data; if none: AllValuesNaN.
  • Requires at least period valid points after the first finite value, else NotEnoughValidData.
  • Warmup: indices before first + (2×period − 1) are NaN for all outputs.
  • Center line: dynamic uses cumulative mean from warmup; static uses 0.0.
Error Handling
use vector_ta::indicators::dvdiqqe::DvdiqqeError;

match dvdiqqe(&input) {
    Ok(out) => use_outputs(out),
    Err(DvdiqqeError::EmptyInputData) => eprintln!("Input data is empty"),
    Err(DvdiqqeError::AllValuesNaN) => eprintln!("All values are NaN"),
    Err(DvdiqqeError::MissingData) => eprintln!("Mismatched input lengths"),
    Err(DvdiqqeError::InvalidPeriod { period, data_len }) =>
        eprintln!("Invalid period {} for data len {}", period, data_len),
    Err(DvdiqqeError::InvalidSmoothing { smoothing }) =>
        eprintln!("Invalid smoothing period {}", smoothing),
    Err(DvdiqqeError::InvalidTick { tick }) =>
        eprintln!("Invalid tick size {}", tick),
    Err(DvdiqqeError::InvalidMultiplier { multiplier, which }) =>
        eprintln!("{} multiplier must be positive (got {})", which, multiplier),
    Err(DvdiqqeError::NotEnoughValidData { needed, valid }) =>
        eprintln!("Need {} valid points after first finite; got {}", needed, valid),
    Err(DvdiqqeError::EmaError(e)) => eprintln!("EMA failed: {}", e),
}

Python Bindings

Basic Usage

Calculate DVDIQQE (returns 4 arrays: dvdi, fast_tl, slow_tl, center_line). All parameters are optional and default to the library’s values.

import numpy as np
from vector_ta import dvdiqqe, dvdiqqe_batch, DvdiqqeStreamPy

open_ = np.asarray([100.0, 101.0, 101.5, 102.0], dtype=np.float64)
high = np.asarray([101.0, 102.0, 102.5, 103.0], dtype=np.float64)
low  = np.asarray([ 99.5, 100.5, 100.8, 101.0], dtype=np.float64)
close= np.asarray([100.5, 101.5, 101.9, 102.5], dtype=np.float64)

dvdi, fast, slow, center = dvdiqqe(
    open_, high, low, close,
    volume=None,
    period=None,
    smoothing_period=None,
    fast_multiplier=None,
    slow_multiplier=None,
    volume_type=None,
    center_type=None,
    tick_size=None,
    kernel=None,
)

# Streaming from Python
stream = DvdiqqeStreamPy(period=13, smoothing_period=6, fast_multiplier=2.618, slow_multiplier=4.236, volume_type="default", center_type="dynamic", tick_size=0.01)
for (o, h, l, c, v) in live_ohlcv_feed:
    out = stream.update(o, h, l, c, v)  # tuple(dvdi, fast_tl, slow_tl, center) or None
    if out is not None:
        use_realtime(*out)
Batch Sweeps
# Evaluate multiple parameter sets
result = dvdiqqe_batch(
    open_, high, low, close,
    period_range=(10, 14, 2),            # 10, 12, 14
    smoothing_period_range=(4, 8, 2),    # 4, 6, 8
    fast_mult_range=(2.0, 3.0, 0.5),     # 2.0, 2.5, 3.0
    slow_mult_range=(4.0, 5.0, 0.5),     # 4.0, 4.5, 5.0
    kernel=None,
)

values = result["values"]  # flat or matrix per build; see project docs for shape
rows   = result["rows"]
cols   = result["cols"]

JavaScript/WASM Bindings

Basic Usage

Compute DVDIQQE in JavaScript/TypeScript. The dvdiqqe binding returns a flat block with 4 series (dvdi, fast_tl, slow_tl, center_line).

import { dvdiqqe } from 'vectorta-wasm';

const open  = new Float64Array([100, 101, 102]);
const high  = new Float64Array([101, 102, 103]);
const low   = new Float64Array([ 99, 100, 101]);
const close = new Float64Array([100.5, 101.5, 102.5]);

// Optional arguments default to library values when omitted or null
const result = dvdiqqe(open, high, low, close, /* volume */ null, /* period */ null, /* smoothing */ null,
  /* fast */ null, /* slow */ null, /* volume_type */ null, /* center_type */ null, /* tick_size */ null);

// result: { values: Float64Array, rows: 4, cols: N }
const { values, rows, cols } = result;
const plane = cols; // series are contiguous planes
const dvdi    = values.slice(0 * plane, 1 * plane);
const fast_tl = values.slice(1 * plane, 2 * plane);
const slow_tl = values.slice(2 * plane, 3 * plane);
const center  = values.slice(3 * plane, 4 * plane);
Memory-Efficient Operations
import { dvdiqqe_alloc, dvdiqqe_free, dvdiqqe_into, memory } from 'vectorta-wasm';

const len = close.length;
const oPtr = dvdiqqe_alloc(len), hPtr = dvdiqqe_alloc(len), lPtr = dvdiqqe_alloc(len), cPtr = dvdiqqe_alloc(len);
const vPtr = dvdiqqe_alloc(len), outPtr = dvdiqqe_alloc(4 * len); // 4 series × len

// Copy input data into WASM memory
new Float64Array(memory.buffer, oPtr, len).set(open);
new Float64Array(memory.buffer, hPtr, len).set(high);
new Float64Array(memory.buffer, lPtr, len).set(low);
new Float64Array(memory.buffer, cPtr, len).set(close);
// If you have volume: set it; otherwise pass 0 and let volume_type decide behavior
new Float64Array(memory.buffer, vPtr, len).fill(0);

// Compute directly into a pre-allocated output block (layout: [dvdi | fast | slow | center])
dvdiqqe_into(oPtr, hPtr, lPtr, cPtr, vPtr, len, 13, 6, 2.618, 4.236, 'default', 'dynamic', 0.01, outPtr);

const out = new Float64Array(memory.buffer, outPtr, 4 * len).slice();
const dvdi    = out.slice(0 * len, 1 * len);
const fast_tl = out.slice(1 * len, 2 * len);
const slow_tl = out.slice(2 * len, 3 * len);
const center  = out.slice(3 * len, 4 * len);

// Free allocations
[oPtr, hPtr, lPtr, cPtr, vPtr, outPtr].forEach(ptr => dvdiqqe_free(ptr, ptr === outPtr ? 4 * len : len));
Batch Processing

Sweep multiple period/multiplier combinations in one call.

import { dvdiqqe_batch_unified } from 'vectorta-wasm';

const config = {
  period_range: [10, 14, 2],           // 10, 12, 14
  smoothing_period_range: [4, 8, 2],   // 4, 6, 8
  fast_mult_range: [2.0, 3.0, 0.5],    // 2.0, 2.5, 3.0
  slow_mult_range: [4.0, 5.0, 0.5],    // 4.0, 4.5, 5.0
};

const out = dvdiqqe_batch_unified(open, high, low, close, /* volume */ null, config, 'default', 'dynamic', 0.01);
// out: { values: Float64Array, rows: 4 * numCombos, cols, combos: [{ period, smoothing_period, fast_multiplier, slow_multiplier }, ...] }
// 4× comes from 4 output series per combo (dvdi, fast, slow, center)

CUDA

CUDA helpers are available when the Python package is built with CUDA support. Inputs must be float32; outputs are device arrays (DLPack / __cuda_array_interface__ compatible).

import numpy as np
  from vector_ta import dvdiqqe_cuda_batch_dev, dvdiqqe_cuda_many_series_one_param_dev

  # One series (float32)
  open_f32 = np.asarray(load_open(), dtype=np.float32)
  close_f32 = np.asarray(load_close(), dtype=np.float32)

  dev = dvdiqqe_cuda_batch_dev(
      open_f32=open_f32,
      close_f32=close_f32,
      volume_f32=14,
      period_range=(5, 30, 5),
      smoothing_period_range=(5, 30, 5),
      fast_mult_range=(0.5, 2.0, 0.5),
      slow_mult_range=(0.5, 2.0, 0.5),
      volume_type=14,
      center_type=14,
      tick_size=1.0,
      device_id=0,
  )

  # Many series (time-major)
  open_tm_f32 = np.asarray(load_open_time_major_matrix(), dtype=np.float32)
  rows, cols = open_tm_f32.shape
  open_tm_f32 = open_tm_f32.ravel()
  close_tm_f32 = np.asarray(load_close_time_major_matrix(), dtype=np.float32)
  close_tm_f32 = close_tm_f32.ravel()

  dev_tm = dvdiqqe_cuda_many_series_one_param_dev(
      open_tm_f32=open_tm_f32,
      close_tm_f32=close_tm_f32,
      cols=cols,
      rows=rows,
      period=14,
      smoothing=14,
      fast_mult=1.0,
      slow_mult=1.0,
      volume_tm_f32=14,
      volume_type=14,
      center_type=14,
      tick_size=1.0,
      device_id=0,
  )

CUDA Bindings (Rust)

use vector_ta::cuda::CudaDvdiqqe;
use vector_ta::indicators::dvdiqqe::DvdiqqeBatchRange;

let cuda = CudaDvdiqqe::new(0)?;

let open: [f32] = /* ... */;
let close: [f32] = /* ... */;
let volume: Option<&[f32]> = /* ... */;
let sweep = DvdiqqeBatchRange::default();
let volume_type: str = /* ... */;
let center_type: str = /* ... */;
let tick_size: f32 = /* ... */;

let out = cuda.dvdiqqe_batch_dev(&open, &close, volume, &sweep, &volume_type, &center_type, tick_size)?;
let _ = out;

Performance Analysis

Comparison:
View:
Loading chart...

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

Related Indicators