mirror of
https://github.com/mihakralj/QuanTAlib.git
synced 2026-08-12 15:48:05 +00:00
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com> Co-authored-by: aider (openrouter/anthropic/claude-sonnet-4) <aider@aider.chat> Co-authored-by: Warp <agent@warp.dev>
132 lines
5.8 KiB
Plaintext
132 lines
5.8 KiB
Plaintext
// The MIT License (MIT)
|
|
// © mihakralj
|
|
//@version=6
|
|
indicator("Yang-Zhang Volatility Adjusted Moving Average (YZVAMA)", "YZVAMA", overlay=true)
|
|
|
|
//@function Calculates YZVAMA by adjusting MA length based on percentile rank of short-term YZV
|
|
//@param source Series to calculate YZVAMA from
|
|
//@param yzv_short_period Short-term YZV period for current volatility
|
|
//@param yzv_long_period Long-term YZV period for baseline volatility
|
|
//@param percentile_lookback Lookback for percentile calculation
|
|
//@param min_length Minimum allowed adjusted length
|
|
//@param max_length Maximum allowed adjusted length
|
|
//@returns YZVAMA value
|
|
//@optimized Uses RMA compensators for YZV and circular buffers for O(1) sum updates
|
|
yzvama(series float source, simple int yzv_short_period=3, simple int yzv_long_period=50, simple int percentile_lookback=100, simple int min_length=5, simple int max_length=100) =>
|
|
if yzv_short_period <= 0 or yzv_long_period <= 0
|
|
runtime.error("All periods must be greater than 0")
|
|
if min_length <= 0 or max_length <= 0
|
|
runtime.error("Min and max length must be greater than 0")
|
|
if min_length > max_length
|
|
runtime.error("Min length must be less than or equal to max length")
|
|
if percentile_lookback <= 0
|
|
runtime.error("Percentile lookback must be greater than 0")
|
|
var float prev_close = close
|
|
float o = open
|
|
float h = high
|
|
float l = low
|
|
float c = close
|
|
float pc = na(prev_close) ? open : prev_close
|
|
prev_close := c
|
|
float ro = math.log(o / pc)
|
|
float rc = math.log(c / o)
|
|
float rh = math.log(h / o)
|
|
float rl = math.log(l / o)
|
|
float s_o_sq = ro * ro
|
|
float s_c_sq = rc * rc
|
|
float s_rs_sq = rh * (rh - rc) + rl * (rl - rc)
|
|
float ratio_N_short = yzv_short_period <= 1 ? 1.0 : (float(yzv_short_period) + 1.0) / (float(yzv_short_period) - 1.0)
|
|
float k_yz_short = 0.34 / (1.34 + ratio_N_short)
|
|
float s_sq_daily_short = s_o_sq + k_yz_short * s_c_sq + (1.0 - k_yz_short) * s_rs_sq
|
|
float EPSILON = 1e-10
|
|
var float raw_rma_short = 0.0
|
|
var float e_comp_short = 1.0
|
|
float yzv_short = na
|
|
if not na(s_sq_daily_short)
|
|
float rma_alpha_short = 1.0 / float(yzv_short_period)
|
|
float rma_beta_short = 1.0 - rma_alpha_short
|
|
raw_rma_short := (raw_rma_short * (yzv_short_period - 1) + s_sq_daily_short) / yzv_short_period
|
|
e_comp_short := rma_beta_short * e_comp_short
|
|
float smoothed_s_sq_short = e_comp_short > EPSILON ? raw_rma_short / (1.0 - e_comp_short) : raw_rma_short
|
|
yzv_short := math.sqrt(smoothed_s_sq_short)
|
|
float ratio_N_long = yzv_long_period <= 1 ? 1.0 : (float(yzv_long_period) + 1.0) / (float(yzv_long_period) - 1.0)
|
|
float k_yz_long = 0.34 / (1.34 + ratio_N_long)
|
|
float s_sq_daily_long = s_o_sq + k_yz_long * s_c_sq + (1.0 - k_yz_long) * s_rs_sq
|
|
var float raw_rma_long = 0.0
|
|
var float e_comp_long = 1.0
|
|
float yzv_long = na
|
|
if not na(s_sq_daily_long)
|
|
float rma_alpha_long = 1.0 / float(yzv_long_period)
|
|
float rma_beta_long = 1.0 - rma_alpha_long
|
|
raw_rma_long := (raw_rma_long * (yzv_long_period - 1) + s_sq_daily_long) / yzv_long_period
|
|
e_comp_long := rma_beta_long * e_comp_long
|
|
float smoothed_s_sq_long = e_comp_long > EPSILON ? raw_rma_long / (1.0 - e_comp_long) : raw_rma_long
|
|
yzv_long := math.sqrt(smoothed_s_sq_long)
|
|
var array<float> yzv_buffer = array.new_float(percentile_lookback, na)
|
|
var int yzv_head = 0
|
|
if not na(yzv_short)
|
|
array.set(yzv_buffer, yzv_head, yzv_short)
|
|
yzv_head := (yzv_head + 1) % percentile_lookback
|
|
array<float> sorted_yzv = array.new_float()
|
|
for i = 0 to percentile_lookback - 1
|
|
float val = array.get(yzv_buffer, i)
|
|
if not na(val)
|
|
array.push(sorted_yzv, val)
|
|
int n_valid = array.size(sorted_yzv)
|
|
float percentile_value = 50.0
|
|
if n_valid > 1 and not na(yzv_short)
|
|
array.sort(sorted_yzv)
|
|
int rank_pos = 0
|
|
for i = 0 to n_valid - 1
|
|
if array.get(sorted_yzv, i) < yzv_short
|
|
rank_pos += 1
|
|
percentile_value := (float(rank_pos) / float(n_valid - 1)) * 100.0
|
|
float length_range = max_length - min_length
|
|
float adjusted_length_f = max_length - (percentile_value / 100.0) * length_range
|
|
int adjusted_length = int(math.max(min_length, math.min(max_length, adjusted_length_f)))
|
|
var array<float> buffer = array.new_float(max_length, na)
|
|
var int head = 0
|
|
var float sum = 0.0
|
|
var int valid_count = 0
|
|
if array.size(buffer) != max_length
|
|
buffer := array.new_float(max_length, na)
|
|
head := 0
|
|
sum := 0.0
|
|
valid_count := 0
|
|
float oldest = array.get(buffer, head)
|
|
if not na(oldest)
|
|
sum -= oldest
|
|
valid_count -= 1
|
|
if not na(source)
|
|
sum += source
|
|
valid_count += 1
|
|
array.set(buffer, head, source)
|
|
head := (head + 1) % max_length
|
|
float avg = valid_count > 0 ? sum / valid_count : source
|
|
int actual_count = math.min(valid_count, adjusted_length)
|
|
float partial_sum = 0.0
|
|
int partial_count = 0
|
|
for i = 0 to actual_count - 1
|
|
int idx = (head - 1 - i + max_length) % max_length
|
|
float val = array.get(buffer, idx)
|
|
if not na(val)
|
|
partial_sum += val
|
|
partial_count += 1
|
|
partial_count > 0 ? partial_sum / partial_count : nz(avg, source)
|
|
|
|
// ---------- Main loop ----------
|
|
|
|
// Inputs
|
|
i_source = input.source(close, "Source")
|
|
i_yzv_short = input.int(3, "Short YZV Period", minval=1)
|
|
i_yzv_long = input.int(50, "Long YZV Period", minval=1)
|
|
i_percentile_lookback = input.int(100, "Percentile Lookback", minval=1)
|
|
i_min_length = input.int(5, "Minimum Length", minval=1)
|
|
i_max_length = input.int(100, "Maximum Length", minval=1)
|
|
|
|
// Calculation
|
|
yzvama_value = yzvama(i_source, i_yzv_short, i_yzv_long, i_percentile_lookback, i_min_length, i_max_length)
|
|
|
|
// Plot
|
|
plot(yzvama_value, "YZVAMA", color=color.yellow, linewidth=2)
|