Dual Volume Divergence QQE (DVDIQQE)
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, andtick_size > 0.- All OHLC arrays must be same length; optional
volumemust match length if provided, else fallback rules apply. - First finite close index defines the start of valid data; if none:
AllValuesNaN. - Requires at least
periodvalid points after the first finite value, elseNotEnoughValidData. - Warmup: indices before
first + (2×period − 1)areNaNfor all outputs. - Center line:
dynamicuses cumulative mean from warmup;staticuses0.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
Across sizes, Rust CPU runs about 1.14× faster than Tulip C in this benchmark.
AMD Ryzen 9 9950X (CPU) | NVIDIA RTX 4090 (GPU)