mirror of
https://github.com/mihakralj/QuanTAlib.git
synced 2026-07-29 18:17:43 +00:00
130 lines
5.2 KiB
Plaintext
130 lines
5.2 KiB
Plaintext
// Licensed under the Apache License, Version 2.0
|
||
// © mihakralj
|
||
//@version=6
|
||
indicator("Quantitative Qualitative Estimation (QQE)", "QQE", overlay=false)
|
||
|
||
//@function Calculates QQE — smoothed RSI with dynamic volatility bands
|
||
//@param source Series to evaluate (typically close)
|
||
//@param rsiPeriod RSI lookback period
|
||
//@param smoothFactor EMA smoothing factor for RSI (SF)
|
||
//@param qqeFactor Multiplier for the ATR-like trailing band (Wilders factor)
|
||
//@returns [qqeLine, trailingLevel] QQE smoothed RSI and its trailing signal
|
||
//@description QQE applies a multi-stage smoothing pipeline to RSI:
|
||
// Stage 1: Compute RSI using Wilder's RMA (alpha=1/rsiPeriod) with §2 warmup.
|
||
// Stage 2: Smooth RSI with EMA(smoothFactor) → rsiMA. Also §2 warmup.
|
||
// Stage 3: Compute |rsiMA − rsiMA[1]|, smooth with double EMA(2*SF-1)
|
||
// → "dar" (dynamic average range). Both EMAs use §2 warmup.
|
||
// Stage 4: Build trailing level: upper/lower bands at rsiMA ± qqeFactor*dar.
|
||
// Trailing level follows price directionally (like Parabolic SAR logic):
|
||
// if rsiMA > prevTrail and rsiMA[1] > prevTrail → trail = max(trail, lower)
|
||
// if rsiMA < prevTrail and rsiMA[1] < prevTrail → trail = min(trail, upper)
|
||
// else → trail flips to upper or lower based on rsiMA side.
|
||
// The QQE line is rsiMA; the trailing level acts as signal for crossover triggers.
|
||
// Crosses of QQE above/below trailing level indicate momentum shifts.
|
||
// Zero-crossing of (QQE - 50) can also be used for trend direction.
|
||
qqe(series float source, simple int rsiPeriod, simple int smoothFactor, simple float qqeFactor) =>
|
||
if rsiPeriod <= 0
|
||
runtime.error("RSI period must be greater than 0")
|
||
if smoothFactor <= 0
|
||
runtime.error("Smooth factor must be greater than 0")
|
||
if qqeFactor <= 0.0
|
||
runtime.error("QQE factor must be greater than 0")
|
||
|
||
float EPSILON = 1e-10
|
||
|
||
// --- Stage 1: Wilder RSI via RMA (alpha = 1/rsiPeriod) with §2 warmup ---
|
||
var float prevSrc = na
|
||
var float rmaGain = 0.0
|
||
var float rmaLoss = 0.0
|
||
var float eRma = 1.0
|
||
float rmaAlpha = 1.0 / float(rsiPeriod)
|
||
float rmaBeta = 1.0 - rmaAlpha
|
||
|
||
float chg = not na(prevSrc) ? nz(source) - prevSrc : 0.0
|
||
prevSrc := nz(source)
|
||
float gain = chg > 0 ? chg : 0.0
|
||
float loss = chg < 0 ? -chg : 0.0
|
||
|
||
rmaGain := rmaGain * rmaBeta + gain * rmaAlpha
|
||
rmaLoss := rmaLoss * rmaBeta + loss * rmaAlpha
|
||
eRma *= rmaBeta
|
||
float cRma = eRma > EPSILON ? 1.0 / (1.0 - eRma) : 1.0
|
||
float avgGain = rmaGain * cRma
|
||
float avgLoss = rmaLoss * cRma
|
||
float rs = avgLoss == 0.0 ? 100.0 : avgGain / avgLoss
|
||
float rsiVal = 100.0 - 100.0 / (1.0 + rs)
|
||
|
||
// --- Stage 2: EMA smooth of RSI (alpha = 2/(SF+1)) with §2 warmup ---
|
||
var float rawRsiMa = 0.0
|
||
var float eRsiMa = 1.0
|
||
float sfAlpha = 2.0 / (float(smoothFactor) + 1.0)
|
||
float sfBeta = 1.0 - sfAlpha
|
||
|
||
rawRsiMa := rawRsiMa * sfBeta + rsiVal * sfAlpha
|
||
eRsiMa *= sfBeta
|
||
float cRsiMa = eRsiMa > EPSILON ? 1.0 / (1.0 - eRsiMa) : 1.0
|
||
float rsiMa = rawRsiMa * cRsiMa
|
||
|
||
// --- Stage 3: Double EMA of |delta(rsiMA)| → dar ---
|
||
// Both EMAs use period = 2*SF - 1 → alpha = 2 / (2*SF)
|
||
var float prevRsiMa = na
|
||
float darPeriod = float(2 * smoothFactor - 1)
|
||
float darAlpha = 2.0 / (darPeriod + 1.0)
|
||
float darBeta = 1.0 - darAlpha
|
||
|
||
float absDelta = not na(prevRsiMa) ? math.abs(rsiMa - prevRsiMa) : 0.0
|
||
prevRsiMa := rsiMa
|
||
|
||
var float rawDar1 = 0.0
|
||
var float eDar1 = 1.0
|
||
rawDar1 := rawDar1 * darBeta + absDelta * darAlpha
|
||
eDar1 *= darBeta
|
||
float cDar1 = eDar1 > EPSILON ? 1.0 / (1.0 - eDar1) : 1.0
|
||
float dar1 = rawDar1 * cDar1
|
||
|
||
var float rawDar2 = 0.0
|
||
var float eDar2 = 1.0
|
||
rawDar2 := rawDar2 * darBeta + dar1 * darAlpha
|
||
eDar2 *= darBeta
|
||
float cDar2 = eDar2 > EPSILON ? 1.0 / (1.0 - eDar2) : 1.0
|
||
float dar = rawDar2 * cDar2
|
||
|
||
// --- Stage 4: Trailing level (directional flip logic) ---
|
||
float band = qqeFactor * dar
|
||
float upperBand = rsiMa + band
|
||
float lowerBand = rsiMa - band
|
||
|
||
var float trail = 0.0
|
||
var float prevRsiMa2 = 50.0
|
||
|
||
float newTrail = trail
|
||
if rsiMa > trail and prevRsiMa2 > trail
|
||
newTrail := math.max(trail, lowerBand)
|
||
else if rsiMa < trail and prevRsiMa2 < trail
|
||
newTrail := math.min(trail, upperBand)
|
||
else
|
||
newTrail := rsiMa > trail ? lowerBand : upperBand
|
||
|
||
prevRsiMa2 := rsiMa
|
||
trail := newTrail
|
||
|
||
[rsiMa, trail]
|
||
|
||
// ---------- Main loop ----------
|
||
|
||
// Inputs
|
||
i_source = input.source(close, "Source")
|
||
i_rsiPeriod = input.int(14, "RSI Period", minval=1)
|
||
i_smoothFactor = input.int(5, "Smooth Factor", minval=1)
|
||
i_qqeFactor = input.float(4.236, "QQE Factor", minval=0.001, step=0.001)
|
||
|
||
// Calculation
|
||
[qqeLine, trailLine] = qqe(i_source, i_rsiPeriod, i_smoothFactor, i_qqeFactor)
|
||
|
||
// Plot
|
||
plot(qqeLine, "QQE", color=color.yellow, linewidth=2)
|
||
plot(trailLine, "Trailing", color=color.aqua, linewidth=1)
|
||
hline(50, "Midline", color=color.gray, linestyle=hline.style_dotted)
|
||
hline(70, "Overbought", color=color.red, linestyle=hline.style_dashed)
|
||
hline(30, "Oversold", color=color.green, linestyle=hline.style_dashed)
|