mirror of
https://github.com/mihakralj/QuanTAlib.git
synced 2026-07-28 01:37:43 +00:00
99 lines
3.4 KiB
Plaintext
99 lines
3.4 KiB
Plaintext
// Licensed under the Apache License, Version 2.0
|
|
// © mihakralj
|
|
//@version=6
|
|
indicator("Relative Vigor Index (RVGI)", "RVGI", overlay=false)
|
|
|
|
//@function Calculates RVGI — momentum oscillator comparing close-open vs high-low
|
|
//@param period SMA smoothing period for the SWMA-weighted numerator/denominator
|
|
//@returns [rvgi, signal] RVGI value and its 4-bar SWMA signal line
|
|
//@description RVGI measures the conviction of a price move by comparing closing
|
|
// strength to the full range:
|
|
// Step 1: SWMA(close-open, 4 bars) with weights [1/6, 2/6, 2/6, 1/6]
|
|
// Step 2: SWMA(high-low, 4 bars) with same weights
|
|
// Step 3: SMA(step1, period) / SMA(step2, period) → RVGI
|
|
// Step 4: SWMA(RVGI, 4 bars) → Signal line
|
|
// The SWMA (Symmetrically Weighted Moving Average) uses fixed 4-bar kernel:
|
|
// swma(x) = (x[3] + 2*x[2] + 2*x[1] + x[0]) / 6
|
|
// SMA sums use O(1) circular buffers with §3 count-based warmup.
|
|
// Defensive division: denominator == 0 returns 0.
|
|
// Crossovers of RVGI and Signal indicate momentum shifts.
|
|
// In uptrends, closes tend near highs → positive RVGI; downtrends → negative.
|
|
rvgi(simple int period) =>
|
|
if period <= 0
|
|
runtime.error("Period must be greater than 0")
|
|
|
|
// --- SWMA of (close - open): weights [1,2,2,1]/6 over 4 bars ---
|
|
float co0 = close - open
|
|
float co1 = nz(close[1]) - nz(open[1])
|
|
float co2 = nz(close[2]) - nz(open[2])
|
|
float co3 = nz(close[3]) - nz(open[3])
|
|
float swmaNum = (co3 + 2.0 * co2 + 2.0 * co1 + co0) / 6.0
|
|
|
|
// --- SWMA of (high - low): weights [1,2,2,1]/6 over 4 bars ---
|
|
float hl0 = high - low
|
|
float hl1 = nz(high[1]) - nz(low[1])
|
|
float hl2 = nz(high[2]) - nz(low[2])
|
|
float hl3 = nz(high[3]) - nz(low[3])
|
|
float swmaDen = (hl3 + 2.0 * hl2 + 2.0 * hl1 + hl0) / 6.0
|
|
|
|
// --- SMA of swmaNum over period (circular buffer, §3 warmup) ---
|
|
var array<float> numBuf = array.new_float(period, na)
|
|
var int numHead = 0
|
|
var float numSum = 0.0
|
|
var int numCount = 0
|
|
|
|
float oldNum = array.get(numBuf, numHead)
|
|
if not na(oldNum)
|
|
numSum -= oldNum
|
|
else
|
|
numCount += 1
|
|
numSum += nz(swmaNum)
|
|
array.set(numBuf, numHead, nz(swmaNum))
|
|
numHead := (numHead + 1) % period
|
|
|
|
float smaNum = numSum / math.max(1, numCount)
|
|
|
|
// --- SMA of swmaDen over period (circular buffer, §3 warmup) ---
|
|
var array<float> denBuf = array.new_float(period, na)
|
|
var int denHead = 0
|
|
var float denSum = 0.0
|
|
var int denCount = 0
|
|
|
|
float oldDen = array.get(denBuf, denHead)
|
|
if not na(oldDen)
|
|
denSum -= oldDen
|
|
else
|
|
denCount += 1
|
|
denSum += nz(swmaDen)
|
|
array.set(denBuf, denHead, nz(swmaDen))
|
|
denHead := (denHead + 1) % period
|
|
|
|
float smaDen = denSum / math.max(1, denCount)
|
|
|
|
// --- RVGI = numSMA / denSMA ---
|
|
float rvgiVal = smaDen == 0.0 ? 0.0 : smaNum / smaDen
|
|
|
|
// --- Signal: SWMA of RVGI over 4 bars ---
|
|
var float rv1 = 0.0
|
|
var float rv2 = 0.0
|
|
var float rv3 = 0.0
|
|
float sig = (rv3 + 2.0 * rv2 + 2.0 * rv1 + rvgiVal) / 6.0
|
|
rv3 := rv2
|
|
rv2 := rv1
|
|
rv1 := rvgiVal
|
|
|
|
[rvgiVal, sig]
|
|
|
|
// ---------- Main loop ----------
|
|
|
|
// Inputs
|
|
i_period = input.int(10, "Period", minval=1)
|
|
|
|
// Calculation
|
|
[rvgiLine, sigLine] = rvgi(i_period)
|
|
|
|
// Plot
|
|
plot(rvgiLine, "RVGI", color=color.yellow, linewidth=2)
|
|
plot(sigLine, "Signal", color=color.aqua, linewidth=1)
|
|
hline(0, "Zero", color=color.gray, linestyle=hline.style_dotted)
|