Quantitative Qualitative Estimation (QQE)

Parameters: rsi_period = 14 | smoothing_factor = 5 | fast_factor = 4.236

Overview

The Quantitative Qualitative Estimation indicator enhances traditional RSI analysis by adding adaptive volatility bands and a trailing reference line, though interestingly its original creator remains unknown despite the indicator's popularity. QQE begins with a standard RSI calculation, then applies exponential smoothing to create the fast line, which represents smoothed momentum oscillating between 0 and 100. From this fast line, the indicator derives a volatility measure by computing the exponential moving average of absolute period to period changes, then scales this by the fast factor parameter to construct dynamic bands. The slow line trails the fast line within these bands, switching between upper and lower boundaries as momentum shifts direction. Traders watch for fast line crossovers of the slow line to identify momentum regime changes with built in volatility adaptation. When the fast line exceeds the slow line, bullish momentum conditions prevail; when it falls below, bearish pressure dominates. The default parameters of RSI(14), smoothing(5), and factor(4.236) balance sensitivity with stability across diverse market conditions.

Defaults: rsi_period = 14, smoothing_factor = 5, fast_factor = 4.236.

Implementation Examples

Get started with QQE in just a few lines:

use vectorta::indicators::qqe::{qqe, QqeInput, QqeParams};
use vectorta::utilities::data_loader::{Candles, read_candles_from_csv};

// Using with a price slice
let prices = vec![100.0, 102.0, 101.0, 103.0, 104.5, 104.0];
let params = QqeParams::default(); // rsi=14, smoothing=5, fast_k=4.236
let input = QqeInput::from_slice(&prices, params);
let out = qqe(&input)?; // out.fast, out.slow

// Using with Candles (defaults: source = "close")
let candles: Candles = read_candles_from_csv("data/sample.csv")?;
let input = QqeInput::with_default_candles(&candles);
let out = qqe(&input)?;

println!("fast: {:?}", &out.fast[out.fast.len().saturating_sub(5)..]);
println!("slow: {:?}", &out.slow[out.slow.len().saturating_sub(5)..]);

API Reference

Input Methods
// From a price slice
QqeInput::from_slice(&[f64], QqeParams) -> QqeInput

// From candles with explicit source (e.g., "close")
QqeInput::from_candles(&Candles, &str, QqeParams) -> QqeInput

// From candles with defaults (rsi=14, smoothing=5, fast_k=4.236, source="close")
QqeInput::with_default_candles(&Candles) -> QqeInput
Parameters Structure
pub struct QqeParams {
    pub rsi_period: Option<usize>,
    pub smoothing_factor: Option<usize>,
    pub fast_factor: Option<f64>,
}

// Defaults: rsi_period = 14, smoothing_factor = 5, fast_factor = 4.236
Output Structure
pub struct QqeOutput {
    pub fast: Vec<f64>, // EMA-smoothed RSI
    pub slow: Vec<f64>, // Trailing band-following line
}
Validation, Warmup & NaNs
  • Empty input → QqeError::EmptyInputData; all-NaN → QqeError::AllValuesNaN.
  • Invalid period if rsi_period or smoothing_factor is 0 or exceeds data length.
  • Requires enough valid data after the first finite value: needed = rsi_period + smoothing_factor; otherwise QqeError::NotEnoughValidData.
  • Warmup prefix for slow is set to NaN until first + rsi_period + smoothing_factor − 2. The fast series is defined from RSI start.
Error Handling
use vectorta::indicators::qqe::QqeError;

match qqe(&input) {
    Ok(out) => {
        process(out.fast, out.slow);
    },
    Err(QqeError::EmptyInputData) => eprintln!("Empty input data"),
    Err(QqeError::AllValuesNaN) => eprintln!("All values are NaN"),
    Err(QqeError::InvalidPeriod { period, data_len }) =>
        eprintln!("Invalid period {} for length {}", period, data_len),
    Err(QqeError::NotEnoughValidData { needed, valid }) =>
        eprintln!("Need {} valid points, only {}", needed, valid),
    Err(QqeError::DependentIndicatorError { message }) =>
        eprintln!("Dependent indicator failed: {}", message),
}

Python Bindings

Basic Usage
import numpy as np
from vectorta import qqe

data = np.array([100, 102, 101, 103, 104.5, 104.0], dtype=np.float64)
fast, slow = qqe(data, rsi_period=14, smoothing_factor=5, fast_factor=4.236)
print('fast tail:', fast[-5:])
print('slow tail:', slow[-5:])
Streaming
from vectorta import QqeStream

stream = QqeStream(14, 5, 4.236)
for price in [100, 101, 100.5, 102, 101.8]:
    out = stream.update(price)
    if out is not None:
        fast, slow = out
        print('fast:', fast, 'slow:', slow)
Batch Processing
import numpy as np
from vectorta import qqe_batch

data = np.random.randn(200).cumsum().astype(np.float64)

# Define parameter ranges (inclusive)
rsi_rng = (10, 20, 5)          # 10,15,20
smooth_rng = (3, 7, 2)         # 3,5,7
fast_k_rng = (4.236, 4.236, 0) # single value

out = qqe_batch(data, rsi_rng, smooth_rng, fast_k_rng)
rows = len(out['combos'])
cols = out['fast'].shape[1]
print('rows x cols =', rows, 'x', cols)

JavaScript / WASM

Basic Usage

Compute QQE and get both series in a unified buffer:

import { qqe_js } from 'vectorta-wasm';

const prices = new Float64Array([100, 102, 101, 103, 104.5, 104.0]);
const res = await qqe_js(prices, 14, 5, 4.236);

// res: { values: Float64Array, rows: 2, cols: prices.length }
const { values, rows, cols } = res as { values: Float64Array; rows: number; cols: number };
const fast = values.slice(0, cols);
const slow = values.slice(cols);
console.log('QQE fast tail:', fast.slice(-3));
console.log('QQE slow tail:', slow.slice(-3));
Memory‑Efficient Operations

Use zero‑copy pointers for large arrays:

import { qqe_alloc, qqe_free, qqe_into, memory } from 'vectorta-wasm';

const data = new Float64Array([/* your prices */]);
const len = data.length;

// Allocate WASM memory for input and output (qqe_alloc reserves 2×len capacity)
const inPtr = qqe_alloc(len);
const outPtr = qqe_alloc(len); // layout: [fast..len][slow..len]

// Copy input into WASM memory
new Float64Array(memory.buffer, inPtr, len).set(data);

// Compute directly into pre‑allocated output buffer
await qqe_into(inPtr, outPtr, len, 14, 5, 4.236);

// Read results and copy out
const out = new Float64Array(memory.buffer, outPtr, len * 2);
const fast = out.slice(0, len);
const slow = out.slice(len);

// Free memory when done
qqe_free(inPtr, len);
qqe_free(outPtr, len);
Batch Processing

Sweep parameters and retrieve a structured result:

import { qqe_batch } from 'vectorta-wasm';

const data = new Float64Array([/* prices */]);
const config = {
  rsi_period_range: [10, 20, 5],
  smoothing_factor_range: [3, 7, 2],
  fast_factor_range: [4.236, 4.236, 0.0]
};

const result = await qqe_batch(data, config);
// result: { fast_values: Float64Array, slow_values: Float64Array, combos, rows, cols }
console.log('rows x cols =', result.rows, 'x', result.cols);

Performance Analysis

Comparison:
View:
Loading chart...

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

Related Indicators