mirror of
https://github.com/mihakralj/QuanTAlib.git
synced 2026-07-27 17:27:43 +00:00
35a6702b06
Deep review of all indicator categories verified .md headers against .cs WarmupPeriod, parameters, inputs, and outputs. Fixes include warmup corrections, parameter documentation, output type accuracy, and Pine Script alignment.
128 lines
5.7 KiB
Plaintext
128 lines
5.7 KiB
Plaintext
// Licensed under the Apache License, Version 2.0
|
|
// © 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) =>
|
|
var float prev_close = na
|
|
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
|
|
// EMA-smooth the percentile to prevent wild adjusted_length swings
|
|
var float smooth_pct = 50.0
|
|
float pct_alpha = 2.0 / (float(percentile_lookback) + 1.0)
|
|
smooth_pct := pct_alpha * percentile_value + (1.0 - pct_alpha) * smooth_pct
|
|
float length_range = max_length - min_length
|
|
float adjusted_length_f = max_length - (smooth_pct / 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)
|