mirror of
https://github.com/mihakralj/QuanTAlib.git
synced 2026-08-02 11:37:42 +00:00
142 lines
4.9 KiB
Plaintext
142 lines
4.9 KiB
Plaintext
// Licensed under the Apache License, Version 2.0
|
|
// © mihakralj
|
|
//@version=6
|
|
indicator("Jurik Volatility", "Jvolty", overlay=false)
|
|
|
|
//@function Jurik Volatility - extracted volatility component from JMA
|
|
//@param source Series to calculate Jvolty from
|
|
//@param period Number of bars used in the calculation (>= 1)
|
|
//@returns Normalized volatility measure (1 = low volatility, logParam = high volatility)
|
|
jvolty(series float source, simple int period) =>
|
|
// ---- Precomputed length parameters (constant per series) ----
|
|
simple float _LEN0 = period < 1.0000000002 ? 1e-10 : (period - 1.0) / 2.0
|
|
simple float _LOG_PARAM = math.max(math.log(math.sqrt(_LEN0)) / math.log(2.0) + 2.0, 0.0)
|
|
simple float _SQRT_PARAM = math.sqrt(_LEN0) * _LOG_PARAM
|
|
simple float _SQRT_DIV = _SQRT_PARAM / (_SQRT_PARAM + 1.0)
|
|
simple float _P_EXP = math.max(_LOG_PARAM - 2.0, 0.5)
|
|
|
|
// ---- Internal state (persists across bars) ----
|
|
var float upperBand = na
|
|
var float lowerBand = na
|
|
var int bars = 0
|
|
|
|
// 10-bar local deviation window
|
|
var float cycleDelta = 0.0
|
|
var int volIndex = 0
|
|
var int volCount = 0
|
|
var array<float> volWindow = array.new_float(10, 0.0)
|
|
|
|
// 128-bar volatility distribution
|
|
var int distIndex = 0
|
|
var int distCount = 0
|
|
var array<float> distWindow = array.new_float(128, 0.0)
|
|
var array<float> sorted = array.new_float(0)
|
|
|
|
float current_volty = na
|
|
|
|
if not na(source)
|
|
bars += 1
|
|
|
|
// ---- First bar: initialize anchors ----
|
|
if bars == 1
|
|
upperBand := source
|
|
lowerBand := source
|
|
current_volty := 1.0
|
|
else
|
|
// 1) Local deviation vs. upperBand / lowerBand
|
|
float diffA = source - upperBand
|
|
float diffB = source - lowerBand
|
|
float absA = math.abs(diffA)
|
|
float absB = math.abs(diffB)
|
|
float absValue = absA > absB ? absA : absB
|
|
float dLocal = absValue + 1e-10
|
|
|
|
// 2) 10-bar SMA of local deviation -> highD
|
|
float oldVol = array.get(volWindow, volIndex)
|
|
cycleDelta += dLocal - oldVol
|
|
array.set(volWindow, volIndex, dLocal)
|
|
volIndex += 1
|
|
if volIndex >= 10
|
|
volIndex := 0
|
|
if volCount < 10
|
|
volCount += 1
|
|
float highD = volCount > 0 ? cycleDelta / (volCount < 10 ? volCount : 10) : dLocal
|
|
|
|
// 3) 128-bar volatility distribution + trimmed mean
|
|
array.set(distWindow, distIndex, highD)
|
|
distIndex += 1
|
|
if distIndex >= 128
|
|
distIndex := 0
|
|
if distCount < 128
|
|
distCount += 1
|
|
|
|
float dRef = highD
|
|
if distCount >= 16
|
|
int count = distCount
|
|
array.clear(sorted)
|
|
for i = 0 to count - 1
|
|
int idx = distIndex - 1 - i
|
|
if idx < 0
|
|
idx += 128
|
|
array.push(sorted, array.get(distWindow, idx))
|
|
array.sort(sorted)
|
|
|
|
int idxLo = 0
|
|
int idxHi = 0
|
|
if count >= 128
|
|
idxLo := 32
|
|
idxHi := 96
|
|
else
|
|
int slice = int(math.max(5.0, math.round(count * 0.5)))
|
|
int drop = (count - slice) / 2
|
|
idxLo := drop
|
|
idxHi := drop + slice - 1
|
|
|
|
if idxLo < 0
|
|
idxLo := 0
|
|
if idxHi >= count
|
|
idxHi := count - 1
|
|
|
|
float sum = 0.0
|
|
for i = idxLo to idxHi
|
|
sum += array.get(sorted, i)
|
|
dRef := sum / float(idxHi - idxLo + 1)
|
|
|
|
if dRef <= 0.0
|
|
dRef := dLocal
|
|
|
|
// 4) Jurik dynamic exponent
|
|
float ratio = absValue / dRef
|
|
if ratio < 0.0
|
|
ratio := 0.0
|
|
float d = math.pow(ratio, _P_EXP)
|
|
d := math.min(math.max(d, 1.0), _LOG_PARAM)
|
|
|
|
// 5) Update upperBand / lowerBand via sqrtDivider ^ sqrt(d)
|
|
float adapt = math.pow(_SQRT_DIV, math.sqrt(d))
|
|
if source > upperBand
|
|
upperBand := source
|
|
else
|
|
upperBand := source - (source - upperBand) * adapt
|
|
if source < lowerBand
|
|
lowerBand := source
|
|
else
|
|
lowerBand := source - (source - lowerBand) * adapt
|
|
|
|
current_volty := d
|
|
|
|
current_volty
|
|
|
|
// ---------- Main loop ----------
|
|
|
|
// Inputs
|
|
i_period = input.int(10, "Period", minval=1, tooltip="Number of bars used in the calculation")
|
|
i_source = input.source(close, "Source")
|
|
|
|
// Calculation
|
|
jvolty_value = jvolty(i_source, i_period)
|
|
|
|
// Plot
|
|
plot(jvolty_value, "Jvolty", color=color.orange, linewidth=2)
|
|
hline(1.0, "Min Volatility", color=color.gray, linestyle=hline.style_dotted)
|