mirror of
https://github.com/mihakralj/QuanTAlib.git
synced 2026-07-27 17:27:43 +00:00
129 lines
4.1 KiB
Plaintext
129 lines
4.1 KiB
Plaintext
// Licensed under the Apache License, Version 2.0
|
|
// © 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))
|