mirror of
https://github.com/mihakralj/QuanTAlib.git
synced 2026-08-15 09:08:04 +00:00
- Updated BBWN, BBWP, CCV, CV, CVI, EWMA, GKV, HLV, HV, Jvolty, JVOLTYN, MASSI, NATR, RSV, RV, RVI, TR, UI, VOV, VR, YZV indicators with documentation links. - Added documentation links for Aberration, Acceleration Bands, Andrews' Pitchfork, Adaptive Price Zone, ATR Bands, Bollinger Bands, Center of Gravity, Donchian Channels, Decay Min-Max Channel, Detrended Synthetic Price, EACP, EBSW, HOMOD, Jurik Volatility Bands, Keltner Channel, MA Envelope, Min-Max Channel, Price Channel, Regression Channels, Standard Deviation Channel, Stoller Average Range Channel, Super Trend Bands, Ultimate Bands, Ultimate Channel, VWAP Bands, and VWAP with Standard Deviation Bands.
129 lines
4.1 KiB
Plaintext
129 lines
4.1 KiB
Plaintext
// The MIT License (MIT)
|
|
// © mihakralj
|
|
//@version=6
|
|
indicator("JVOLTYN - Normalized Jurik Volatility", shorttitle="JVOLTYN", overlay=false)
|
|
|
|
//@function Calculates Normalized Jurik Volatility (0-100 scale)
|
|
//@param period Number of bars used in the calculation (>= 2)
|
|
//@param src Source series for volatility measurement
|
|
//@returns Normalized volatility value (0 = low, 100 = high)
|
|
//@optimized Uses adaptive bands with distribution-based normalization
|
|
|
|
// Inputs
|
|
period = input.int(14, "Period", minval=2)
|
|
src = input.source(close, "Source")
|
|
|
|
// Calculate logParam (same as JVOLTY)
|
|
len1 = math.max((period - 1) / 2.0, 0)
|
|
logParam = len1 > 0 ? math.max(math.log(math.sqrt(len1)) / math.log(2) + 2.0, 0) : 0
|
|
|
|
// Normalization factor: maps [1, logParam] to [0, 100]
|
|
normFactor = logParam > 1 ? 100.0 / (logParam - 1.0) : 100.0
|
|
|
|
// JVOLTY core parameters
|
|
powParam = math.max(logParam - 2.0, 0.5)
|
|
sqrtDivider = len1 > 0 ? math.sqrt(len1) * logParam / (math.sqrt(len1) * logParam + 1.0) : 1.0
|
|
|
|
// State variables
|
|
var float upperBand = na
|
|
var float lowerBand = na
|
|
var float[] voltyBuffer = array.new_float(10, 0.0)
|
|
var float[] distBuffer = array.new_float(128, 0.0)
|
|
var int distIndex = 0
|
|
var int distCount = 0
|
|
var float lastValid = 0.0
|
|
|
|
// Initialize bands
|
|
if na(upperBand)
|
|
upperBand := src
|
|
lowerBand := src
|
|
|
|
// Finite value helper
|
|
getFinite(float val, float fallback) =>
|
|
na(val) or not math.isfinite(val) ? fallback : val
|
|
|
|
price = getFinite(src, lastValid)
|
|
lastValid := price
|
|
|
|
// Calculate deviation from bands
|
|
del1 = math.abs(price - nz(upperBand[1], price))
|
|
del2 = math.abs(price - nz(lowerBand[1], price))
|
|
deviation = math.max(del1, del2) + 1e-10
|
|
|
|
// Update 10-bar volatility buffer (ring buffer style)
|
|
array.shift(voltyBuffer)
|
|
array.push(voltyBuffer, deviation)
|
|
|
|
// Short volatility: 10-bar SMA
|
|
shortVolty = array.avg(voltyBuffer)
|
|
|
|
// Update distribution buffer
|
|
if distCount < 128
|
|
array.set(distBuffer, distCount, shortVolty)
|
|
distCount += 1
|
|
else
|
|
array.set(distBuffer, distIndex, shortVolty)
|
|
distIndex := (distIndex + 1) % 128
|
|
|
|
// Calculate trimmed mean from distribution
|
|
calcTrimmedMean() =>
|
|
if distCount < 16
|
|
array.avg(distBuffer)
|
|
else
|
|
// Sort the buffer
|
|
sorted = array.copy(distBuffer)
|
|
array.sort(sorted)
|
|
|
|
// Calculate trim indices
|
|
if distCount >= 128
|
|
// Full buffer: use middle 65 values (indices 32-96)
|
|
sum = 0.0
|
|
for i = 32 to 96
|
|
sum += array.get(sorted, i)
|
|
sum / 65.0
|
|
else
|
|
// Partial buffer: adaptive trim
|
|
sampleSize = math.max(5, math.round(0.5 * distCount))
|
|
startIdx = math.floor((distCount - sampleSize) / 2.0)
|
|
sum = 0.0
|
|
for i = 0 to sampleSize - 1
|
|
sum += array.get(sorted, int(startIdx) + i)
|
|
sum / sampleSize
|
|
|
|
refVolty = calcTrimmedMean()
|
|
|
|
// Calculate dynamic exponent (raw JVOLTY value)
|
|
ratio = refVolty > 0 ? math.abs(shortVolty) / refVolty : 1.0
|
|
rawD = math.max(1.0, math.min(math.pow(ratio, powParam), logParam))
|
|
|
|
// Normalize to 0-100 scale
|
|
jvoltyn = (rawD - 1.0) * normFactor
|
|
|
|
// Update adaptive bands
|
|
adapt = math.pow(sqrtDivider, math.sqrt(rawD))
|
|
if price > upperBand
|
|
upperBand := price
|
|
else
|
|
upperBand := nz(upperBand[1], price) + adapt * (price - nz(upperBand[1], price))
|
|
|
|
if price < lowerBand
|
|
lowerBand := price
|
|
else
|
|
lowerBand := nz(lowerBand[1], price) + adapt * (price - nz(lowerBand[1], price))
|
|
|
|
// Plot
|
|
plot(jvoltyn, "JVOLTYN", color=color.new(color.orange, 0), linewidth=2)
|
|
|
|
// Reference levels
|
|
hline(0, "Min Volatility", color=color.gray, linestyle=hline.style_dotted)
|
|
hline(25, "Low", color=color.green, linestyle=hline.style_dotted)
|
|
hline(50, "Medium", color=color.yellow, linestyle=hline.style_dotted)
|
|
hline(75, "High", color=color.red, linestyle=hline.style_dotted)
|
|
hline(100, "Max Volatility", color=color.gray, linestyle=hline.style_dotted)
|
|
|
|
// Background coloring for volatility regimes
|
|
bgcolor(jvoltyn < 25 ? color.new(color.green, 90) :
|
|
jvoltyn < 50 ? color.new(color.yellow, 90) :
|
|
jvoltyn < 75 ? color.new(color.orange, 90) :
|
|
color.new(color.red, 90))
|