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 vectorta::indicators::dvdiqqe::{dvdiqqe, DvdiqqeInput, DvdiqqeParams};
use vectorta::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 vectorta::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 vectorta 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 acceleration for DVDIQQE is coming soon.

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