Remove multiple Pine Script indicators: SSFDSP, STARCHANNEL, STBANDS, STC, UBANDS, UCHANNEL, VWAPBANDS, and VWAPSD. These indicators were deleted to streamline the library and remove unused or redundant code.

This commit is contained in:
Miha Kralj
2026-02-20 18:44:56 -08:00
parent 3dd05f23e4
commit cbeefc9d64
283 changed files with 23963 additions and 3838 deletions
+124
View File
@@ -0,0 +1,124 @@
// The MIT License (MIT)
// © mihakralj
//@version=6
indicator("Beta Distribution CDF (BETADIST)", "BETADIST", overlay=false, precision=6)
//@function Log-gamma via Lanczos approximation (g=7, 9 coefficients)
//@param z Input value (z > 0)
//@returns ln(Gamma(z))
lnGamma(simple float z) =>
float g = 7.0
array<float> c = array.from(
0.99999999999980993,
676.5203681218851,
-1259.1392167224028,
771.32342877765313,
-176.61502916214059,
12.507343278686905,
-0.13857109526572012,
9.9843695780195716e-6,
1.5056327351493116e-7)
float zz = z - 1.0
float x = array.get(c, 0)
for i = 1 to 8
x += array.get(c, i) / (zz + i)
float t = zz + g + 0.5
0.5 * math.log(2.0 * math.pi) + (zz + 0.5) * math.log(t) - t + math.log(x)
//@function Regularized incomplete beta function I_x(a,b) via continued fraction (Lentz)
//@param x Evaluation point (0 <= x <= 1)
//@param a Shape parameter alpha (a > 0)
//@param b Shape parameter beta (b > 0)
//@returns CDF value P(X <= x) for Beta(a,b)
betaReg(series float x, simple float a, simple float b) =>
if x <= 0.0
0.0
else if x >= 1.0
1.0
else
float lnPfx = a * math.log(x) + b * math.log(1.0 - x) - math.log(a) - lnGamma(a) - lnGamma(b) + lnGamma(a + b)
float front = math.exp(lnPfx)
bool flip = x > (a + 1.0) / (a + b + 2.0)
float xx = flip ? 1.0 - x : x
float aa = flip ? b : a
float bb = flip ? a : b
float lnPfx2 = aa * math.log(xx) + bb * math.log(1.0 - xx) - math.log(aa) - lnGamma(aa) - lnGamma(bb) + lnGamma(aa + bb)
float front2 = math.exp(lnPfx2)
float TINY = 1e-30
float EPS = 1e-10
int MAXITER = 200
float f = TINY
float C = TINY
float D = 0.0
float delta = 0.0
for m = 0 to MAXITER - 1
float d_val = 0.0
int mm = m / 2
if m == 0
d_val := 1.0
else if m % 2 == 0
float mf = float(mm)
d_val := mf * (bb - mf) * xx / ((aa + 2.0 * mf - 1.0) * (aa + 2.0 * mf))
else
float mf = float(mm) + 1.0
d_val := -(aa + mf - 1.0) * (aa + bb + mf - 1.0) * xx / ((aa + 2.0 * mf - 2.0) * (aa + 2.0 * mf - 1.0))
D := 1.0 + d_val * D
if math.abs(D) < TINY
D := TINY
D := 1.0 / D
C := 1.0 + d_val / C
if math.abs(C) < TINY
C := TINY
delta := C * D
f *= delta
if math.abs(delta - 1.0) < EPS
break
float result = front2 * f
flip ? 1.0 - result : result
//@function Computes Beta Distribution CDF for a normalized price series
//@param source Series to transform
//@param period Lookback period for min-max normalization to [0,1]
//@param alpha Shape parameter alpha (controls left skew)
//@param beta_param Shape parameter beta (controls right skew)
//@returns Beta CDF value in [0,1]
//@optimized Lentz continued fraction converges in ~10-20 iterations for typical parameters
betadist(series float source, simple int period, simple float alpha, simple float beta_param) =>
if period <= 0
runtime.error("Period must be greater than 0")
if alpha <= 0.0
runtime.error("Alpha must be greater than 0")
if beta_param <= 0.0
runtime.error("Beta must be greater than 0")
float minVal = source
float maxVal = source
for i = 1 to period - 1
float v = source[i]
if not na(v)
if v < minVal
minVal := v
if v > maxVal
maxVal := v
float range = maxVal - minVal
float x = range > 0.0 ? (source - minVal) / range : 0.5
betaReg(x, alpha, beta_param)
// ---------- Main loop ----------
// Inputs
i_source = input.source(close, "Source")
i_period = input.int(50, "Lookback Period", minval=2, maxval=5000, tooltip="Min-max normalization window")
i_alpha = input.float(2.0, "Alpha (α)", minval=0.01, step=0.1, tooltip="Left shape; α<1 weight toward 0, α>1 weight toward center")
i_beta = input.float(2.0, "Beta (β)", minval=0.01, step=0.1, tooltip="Right shape; β<1 weight toward 1, β>1 weight toward center")
// Calculation
result = betadist(i_source, i_period, i_alpha, i_beta)
// Plot
plot(result, "BETADIST", color=color.yellow, linewidth=2)
hline(0.5, "Midline", color=color.gray, linestyle=hline.style_dotted)
hline(0.95, "Upper", color=color.red, linestyle=hline.style_dashed)
hline(0.05, "Lower", color=color.green, linestyle=hline.style_dashed)
+100
View File
@@ -0,0 +1,100 @@
// The MIT License (MIT)
// © mihakralj
//@version=6
indicator("Binomial Distribution CDF (BINOMDIST)", "BINOMDIST", overlay=false, precision=6)
//@function Log-gamma via Lanczos approximation (g=7, 9 coefficients)
//@param z Input value (z > 0)
//@returns ln(Gamma(z))
lnGamma(simple float z) =>
float g = 7.0
array<float> c = array.from(
0.99999999999980993,
676.5203681218851,
-1259.1392167224028,
771.32342877765313,
-176.61502916214059,
12.507343278686905,
-0.13857109526572012,
9.9843695780195716e-6,
1.5056327351493116e-7)
float zz = z - 1.0
float x = array.get(c, 0)
for i = 1 to 8
x += array.get(c, i) / (zz + i)
float t = zz + g + 0.5
0.5 * math.log(2.0 * math.pi) + (zz + 0.5) * math.log(t) - t + math.log(x)
//@function Log of binomial coefficient C(n, i) = ln(n!) - ln(i!) - ln((n-i)!)
//@param n Number of trials
//@param i Number of successes
//@returns ln(C(n, i))
lnBinom(simple int n, int i) =>
lnGamma(float(n + 1)) - lnGamma(float(i + 1)) - lnGamma(float(n - i + 1))
//@function Binomial Distribution CDF: P(X <= k) = sum_{i=0}^{k} C(n,i) * p^i * (1-p)^(n-i)
//@param p Probability of success per trial (0 <= p <= 1)
//@param n Number of trials
//@param k Threshold (compute P(X <= k))
//@returns CDF value in [0,1]
//@optimized Log-space summation avoids factorial overflow for large n
binomCdf(series float p, simple int n, simple int k) =>
if p <= 0.0
k >= 0 ? 1.0 : 0.0
else if p >= 1.0
k >= n ? 1.0 : 0.0
else
float lnP = math.log(p)
float lnQ = math.log(1.0 - p)
float cdf = 0.0
int kk = math.min(k, n)
for i = 0 to kk
float lnTerm = lnBinom(n, i) + i * lnP + (n - i) * lnQ
cdf += math.exp(lnTerm)
math.min(cdf, 1.0)
//@function Computes Binomial Distribution CDF for a normalized price series
//@param source Series to transform
//@param period Lookback period for min-max normalization to [0,1] as probability p
//@param trials Number of Bernoulli trials (n)
//@param threshold Success threshold (k) — compute P(X <= k)
//@returns Binomial CDF value in [0,1]
binomdist(series float source, simple int period, simple int trials, simple int threshold) =>
if period <= 0
runtime.error("Period must be greater than 0")
if trials <= 0
runtime.error("Trials must be greater than 0")
if threshold < 0
runtime.error("Threshold must be non-negative")
float minVal = source
float maxVal = source
for i = 1 to period - 1
float v = source[i]
if not na(v)
if v < minVal
minVal := v
if v > maxVal
maxVal := v
float range = maxVal - minVal
float p = range > 0.0 ? (source - minVal) / range : 0.5
binomCdf(p, trials, threshold)
// ---------- Main loop ----------
// Inputs
i_source = input.source(close, "Source")
i_period = input.int(50, "Lookback Period", minval=2, maxval=5000, tooltip="Min-max normalization window")
i_trials = input.int(20, "Trials (n)", minval=1, maxval=1000, tooltip="Number of Bernoulli trials")
i_threshold = input.int(10, "Threshold (k)", minval=0, maxval=1000, tooltip="Compute P(X <= k)")
// Calculation
result = binomdist(i_source, i_period, i_trials, i_threshold)
// Plot
plot(result, "BINOMDIST", color=color.yellow, linewidth=2)
hline(0.5, "Midline", color=color.gray, linestyle=hline.style_dotted)
hline(0.95, "Upper", color=color.red, linestyle=hline.style_dashed)
hline(0.05, "Lower", color=color.green, linestyle=hline.style_dashed)
+48
View File
@@ -0,0 +1,48 @@
// The MIT License (MIT)
// © mihakralj
//@version=6
indicator("Continuous Wavelet Transform (CWT)", "CWT", overlay=false, precision=6)
//@function Computes CWT magnitude using Morlet wavelet at a given scale
//@param source Series to analyze
//@param scale Wavelet scale parameter (controls frequency resolution)
//@param omega Central frequency of Morlet wavelet (default 6.0)
//@returns CWT magnitude (power) at the specified scale
cwt(series float source, simple float scale, simple float omega) =>
if scale <= 0.0
runtime.error("Scale must be greater than 0")
if omega <= 0.0
runtime.error("Omega must be greater than 0")
int halfWin = math.max(1, int(math.round(3.0 * scale)))
float invScale = 1.0 / scale
float normFactor = 1.0 / math.sqrt(scale)
float realSum = 0.0
float imagSum = 0.0
for k = -halfWin to halfWin
float srcVal = nz(source[halfWin - k])
float t = float(k) * invScale
float gauss = math.exp(-0.5 * t * t)
float angle = omega * t
realSum += srcVal * gauss * math.cos(angle)
imagSum += srcVal * gauss * math.sin(angle)
float magnitude = math.sqrt(realSum * realSum + imagSum * imagSum) * normFactor
magnitude
// ---------- Main loop ----------
// Inputs
i_source = input.source(close, "Source")
i_scale = input.float(10.0, "Scale", minval=0.5, maxval=200.0, step=0.5,
tooltip="Wavelet scale — higher values capture lower frequencies")
i_omega = input.float(6.0, "Omega (Central Frequency)", minval=1.0, maxval=20.0, step=0.5,
tooltip="Morlet central frequency — standard value is 6.0")
// Calculation
cwt_value = cwt(i_source, i_scale, i_omega)
// Plot
plot(cwt_value, "CWT", color.new(color.yellow, 0), 2)
+70
View File
@@ -0,0 +1,70 @@
// The MIT License (MIT)
// © mihakralj
//@version=6
indicator("Discrete Wavelet Transform (DWT)", "DWT", overlay=false, precision=6)
//@function À trous (stationary) Haar DWT decomposition — returns selected component
//@param src Input series to decompose
//@param levels Number of decomposition levels (1-8, each doubles effective window)
//@param output Which component to return: 0 = approximation at deepest level,
// 1-8 = detail coefficient at that level
//@returns Selected wavelet component (approximation or detail at chosen level)
//@optimized O(levels) per bar using Pine native series indexing, no arrays needed
dwt(series float src, simple int levels, simple int output) =>
if levels < 1 or levels > 8
runtime.error("Levels must be between 1 and 8")
if output < 0 or output > levels
runtime.error("Output must be 0 (approximation) or 1-levels (detail)")
float c0 = nz(src, 0.0)
float c1 = (c0 + nz(src[1], c0)) * 0.5
float d1 = c0 - c1
float c2 = levels >= 2 ? (c1 + nz(c1[2], c1)) * 0.5 : c1
float d2 = levels >= 2 ? c1 - c2 : 0.0
float c3 = levels >= 3 ? (c2 + nz(c2[4], c2)) * 0.5 : c2
float d3 = levels >= 3 ? c2 - c3 : 0.0
float c4 = levels >= 4 ? (c3 + nz(c3[8], c3)) * 0.5 : c3
float d4 = levels >= 4 ? c3 - c4 : 0.0
float c5 = levels >= 5 ? (c4 + nz(c4[16], c4)) * 0.5 : c4
float d5 = levels >= 5 ? c4 - c5 : 0.0
float c6 = levels >= 6 ? (c5 + nz(c5[32], c5)) * 0.5 : c5
float d6 = levels >= 6 ? c5 - c6 : 0.0
float c7 = levels >= 7 ? (c6 + nz(c6[64], c6)) * 0.5 : c6
float d7 = levels >= 7 ? c6 - c7 : 0.0
float c8 = levels >= 8 ? (c7 + nz(c7[128], c7)) * 0.5 : c7
float d8 = levels >= 8 ? c7 - c8 : 0.0
float approx = levels == 1 ? c1 : levels == 2 ? c2 : levels == 3 ? c3 :
levels == 4 ? c4 : levels == 5 ? c5 : levels == 6 ? c6 :
levels == 7 ? c7 : c8
float result = output == 0 ? approx :
output == 1 ? d1 : output == 2 ? d2 : output == 3 ? d3 :
output == 4 ? d4 : output == 5 ? d5 : output == 6 ? d6 :
output == 7 ? d7 : d8
result
// ---------- Main loop ----------
// Inputs
i_source = input.source(close, "Source")
i_levels = input.int(4, "Decomposition Levels", minval=1, maxval=8,
tooltip="Number of levels — lookback = 2^levels bars")
i_output = input.int(0, "Output Component", minval=0, maxval=8,
tooltip="0 = approximation (trend), 1-8 = detail at that level (noise/cycles)")
// Calculation
dwt_value = dwt(i_source, i_levels, i_output)
// Plot
plot(dwt_value, "DWT", color.new(color.yellow, 0), 2)
hline(0, "Zero", color=color.gray, linestyle=hline.style_dotted)
+47
View File
@@ -0,0 +1,47 @@
// The MIT License (MIT)
// © mihakralj
//@version=6
indicator("Exponential Distribution CDF (EXPDIST)", "EXPDIST", overlay=false, precision=6)
//@function Computes Exponential Distribution CDF for a normalized price series
//@param source Series to transform
//@param period Lookback period for min-max normalization to [0,1]
//@param lambda Rate parameter (λ > 0); higher = steeper rise toward 1
//@returns CDF value in [0,1]: F(x) = 1 - exp(-λ * x)
//@optimized O(period) per bar for min-max scan; CDF itself is O(1)
expdist(series float source, simple int period, simple float lambda) =>
if period <= 0
runtime.error("Period must be greater than 0")
if lambda <= 0.0
runtime.error("Lambda must be greater than 0")
float minVal = source
float maxVal = source
for i = 1 to period - 1
float v = source[i]
if not na(v)
if v < minVal
minVal := v
if v > maxVal
maxVal := v
float range = maxVal - minVal
float x = range > 0.0 ? (source - minVal) / range : 0.5
x <= 0.0 ? 0.0 : 1.0 - math.exp(-lambda * x)
// ---------- Main loop ----------
// Inputs
i_source = input.source(close, "Source")
i_period = input.int(50, "Lookback Period", minval=2, maxval=5000, tooltip="Min-max normalization window")
i_lambda = input.float(3.0, "Lambda (λ)", minval=0.01, step=0.1, tooltip="Rate parameter; higher = faster rise toward 1")
// Calculation
float result = expdist(i_source, i_period, i_lambda)
// Plot
plot(result, "EXPDIST", color=color.yellow, linewidth=2)
hline(0.5, "Midline", color=color.gray, linestyle=hline.style_dotted)
hline(0.95, "Upper", color=color.red, linestyle=hline.style_dashed)
hline(0.05, "Lower", color=color.green, linestyle=hline.style_dashed)
+127
View File
@@ -0,0 +1,127 @@
// The MIT License (MIT)
// © mihakralj
//@version=6
indicator("F-Distribution CDF (FDIST)", "FDIST", overlay=false, precision=6)
//@function Log-gamma via Lanczos approximation (g=7, 9 coefficients)
//@param z Input value (z > 0)
//@returns ln(Gamma(z))
lnGamma(simple float z) =>
float g = 7.0
array<float> c = array.from(
0.99999999999980993,
676.5203681218851,
-1259.1392167224028,
771.32342877765313,
-176.61502916214059,
12.507343278686905,
-0.13857109526572012,
9.9843695780195716e-6,
1.5056327351493116e-7)
float zz = z - 1.0
float x = array.get(c, 0)
for i = 1 to 8
x += array.get(c, i) / (zz + i)
float t = zz + g + 0.5
0.5 * math.log(2.0 * math.pi) + (zz + 0.5) * math.log(t) - t + math.log(x)
//@function Regularized incomplete beta function I_x(a,b) via continued fraction (Lentz)
//@param x Evaluation point (0 <= x <= 1)
//@param a Shape parameter alpha (a > 0)
//@param b Shape parameter beta (b > 0)
//@returns CDF value P(X <= x) for Beta(a,b)
betaReg(series float x, simple float a, simple float b) =>
if x <= 0.0
0.0
else if x >= 1.0
1.0
else
float lnPfx = a * math.log(x) + b * math.log(1.0 - x) - math.log(a) - lnGamma(a) - lnGamma(b) + lnGamma(a + b)
float front = math.exp(lnPfx)
bool flip = x > (a + 1.0) / (a + b + 2.0)
float xx = flip ? 1.0 - x : x
float aa = flip ? b : a
float bb = flip ? a : b
float lnPfx2 = aa * math.log(xx) + bb * math.log(1.0 - xx) - math.log(aa) - lnGamma(aa) - lnGamma(bb) + lnGamma(aa + bb)
float front2 = math.exp(lnPfx2)
float TINY = 1e-30
float EPS = 1e-10
int MAXITER = 200
float f = TINY
float C = TINY
float D = 0.0
float delta = 0.0
for m = 0 to MAXITER - 1
float d_val = 0.0
int mm = m / 2
if m == 0
d_val := 1.0
else if m % 2 == 0
float mf = float(mm)
d_val := mf * (bb - mf) * xx / ((aa + 2.0 * mf - 1.0) * (aa + 2.0 * mf))
else
float mf = float(mm) + 1.0
d_val := -(aa + mf - 1.0) * (aa + bb + mf - 1.0) * xx / ((aa + 2.0 * mf - 2.0) * (aa + 2.0 * mf - 1.0))
D := 1.0 + d_val * D
if math.abs(D) < TINY
D := TINY
D := 1.0 / D
C := 1.0 + d_val / C
if math.abs(C) < TINY
C := TINY
delta := C * D
f *= delta
if math.abs(delta - 1.0) < EPS
break
float result = front2 * f
flip ? 1.0 - result : result
//@function Computes F-Distribution CDF for a normalized price series
//@param source Series to transform
//@param period Lookback period for min-max normalization
//@param d1 Numerator degrees of freedom (d1 > 0)
//@param d2 Denominator degrees of freedom (d2 > 0)
//@returns CDF value in [0,1]: P(F <= x) via regularized incomplete beta
//@optimized Lentz continued fraction converges in ~10-20 iterations
fdist(series float source, simple int period, simple float d1, simple float d2) =>
if period <= 0
runtime.error("Period must be greater than 0")
if d1 <= 0.0
runtime.error("d1 must be greater than 0")
if d2 <= 0.0
runtime.error("d2 must be greater than 0")
float minVal = source
float maxVal = source
for i = 1 to period - 1
float v = source[i]
if not na(v)
if v < minVal
minVal := v
if v > maxVal
maxVal := v
float range = maxVal - minVal
float x = range > 0.0 ? (source - minVal) / range : 0.5
float safeX = math.max(0.0, x)
float t = d1 * safeX / (d1 * safeX + d2)
betaReg(t, d1 / 2.0, d2 / 2.0)
// ---------- Main loop ----------
// Inputs
i_source = input.source(close, "Source")
i_period = input.int(50, "Lookback Period", minval=2, maxval=5000, tooltip="Min-max normalization window")
i_d1 = input.float(5.0, "d1 (numerator df)", minval=0.1, step=1.0, tooltip="Numerator degrees of freedom")
i_d2 = input.float(5.0, "d2 (denominator df)", minval=0.1, step=1.0, tooltip="Denominator degrees of freedom")
// Calculation
float result = fdist(i_source, i_period, i_d1, i_d2)
// Plot
plot(result, "FDIST", color=color.yellow, linewidth=2)
hline(0.5, "Midline", color=color.gray, linestyle=hline.style_dotted)
hline(0.95, "Upper", color=color.red, linestyle=hline.style_dashed)
hline(0.05, "Lower", color=color.green, linestyle=hline.style_dashed)
+75
View File
@@ -0,0 +1,75 @@
// The MIT License (MIT)
// © mihakralj
//@version=6
indicator("Fast Fourier Transform (FFT)", "FFT", overlay=false, precision=2)
//@function Computes dominant cycle period via DFT with Hanning window
//@param source Series to analyze
//@param windowSize DFT window size (power of 2: 32, 64, 128)
//@param minPeriod Minimum detectable cycle period (>= 2)
//@param maxPeriod Maximum detectable cycle period (<= windowSize/2)
//@returns dominant cycle period in bars
//@optimized O(N * N/2) per bar; N=64 → ~2048 multiply-adds
fft(series float source, simple int windowSize, simple int minPeriod, simple int maxPeriod) =>
if windowSize != 32 and windowSize != 64 and windowSize != 128
runtime.error("Window size must be 32, 64, or 128")
if minPeriod < 2
runtime.error("Min period must be >= 2")
if maxPeriod > windowSize / 2
runtime.error("Max period must be <= windowSize / 2")
int N = windowSize
int halfN = N / 2
float twoPiOverN = 2.0 * math.pi / N
float maxMag = 0.0
int peakBin = 0
float peakMagA = 0.0
float peakMagB = 0.0
int minBin = math.max(1, N / maxPeriod)
int maxBin = math.min(halfN, N / minPeriod)
for k = minBin to maxBin
float re = 0.0
float im = 0.0
float omega_k = twoPiOverN * k
for n = 0 to N - 1
float val = nz(source[n])
float w = 0.5 - 0.5 * math.cos(twoPiOverN * n)
float xw = val * w
float angle = omega_k * n
re += xw * math.cos(angle)
im -= xw * math.sin(angle)
float mag = re * re + im * im
if mag > maxMag
if peakBin > 0
peakMagA := maxMag
maxMag := mag
peakBin := k
else if peakBin > 0 and peakMagB == 0.0
peakMagB := mag
float dominantPeriod = float(N)
if peakBin > 0
float denom = peakMagA + 2.0 * maxMag + peakMagB
float shift = denom > 0.0 ? (peakMagA - peakMagB) / denom : 0.0
dominantPeriod := N / (peakBin + shift)
math.max(float(minPeriod), math.min(float(maxPeriod), dominantPeriod))
// ---------- Main loop ----------
// Inputs
i_source = input.source(close, "Source")
i_window = input.int(64, "Window Size", options=[32, 64, 128], tooltip="DFT window; larger = finer frequency resolution")
i_minP = input.int(4, "Min Period", minval=2, maxval=64, tooltip="Shortest cycle to detect (bars)")
i_maxP = input.int(32, "Max Period", minval=4, maxval=64, tooltip="Longest cycle to detect (bars)")
// Calculation
float period = fft(i_source, i_window, i_minP, i_maxP)
// Plot
plot(period, "Dominant Period", color=color.yellow, linewidth=2)
hline(8, "Fast Cycle", color=color.green, linestyle=hline.style_dashed)
hline(20, "Slow Cycle", color=color.red, linestyle=hline.style_dashed)
+133
View File
@@ -0,0 +1,133 @@
// The MIT License (MIT)
// © mihakralj
//@version=6
indicator("Gamma Distribution CDF (GAMMADIST)", "GAMMADIST", overlay=false, precision=6)
//@function Log-gamma via Lanczos approximation (g=7, 9 coefficients)
//@param z Input value (z > 0)
//@returns ln(Gamma(z))
lnGamma(simple float z) =>
float g = 7.0
array<float> c = array.from(
0.99999999999980993,
676.5203681218851,
-1259.1392167224028,
771.32342877765313,
-176.61502916214059,
12.507343278686905,
-0.13857109526572012,
9.9843695780195716e-6,
1.5056327351493116e-7)
float zz = z - 1.0
float x = array.get(c, 0)
for i = 1 to 8
x += array.get(c, i) / (zz + i)
float t = zz + g + 0.5
0.5 * math.log(2.0 * math.pi) + (zz + 0.5) * math.log(t) - t + math.log(x)
//@function Regularized lower incomplete gamma P(a,x) via series expansion
//@param a Shape parameter (a > 0)
//@param x Evaluation point (x >= 0)
//@returns P(a,x) = gamma(a,x) / Gamma(a)
gammaSeries(series float a, series float x) =>
float EPS = 1e-10
int MAXITER = 200
float ap = a
float sum = 1.0 / a
float del = sum
for n = 1 to MAXITER
ap += 1.0
del *= x / ap
sum += del
if math.abs(del) < math.abs(sum) * EPS
break
float lnPfx = a * math.log(x) - x - lnGamma(a)
math.exp(lnPfx) * sum
//@function Regularized upper incomplete gamma Q(a,x) via continued fraction (Lentz)
//@param a Shape parameter (a > 0)
//@param x Evaluation point (x >= 0)
//@returns Q(a,x) = 1 - P(a,x)
gammaCF(series float a, series float x) =>
float TINY = 1e-30
float EPS = 1e-10
int MAXITER = 200
float b0 = x + 1.0 - a
float C = 1.0 / TINY
float D = b0 < TINY ? 1.0 / TINY : 1.0 / b0
float f = D
for i = 1 to MAXITER
float ai = -float(i) * (float(i) - a)
float bi = x + 2.0 * float(i) + 1.0 - a
D := bi + ai * D
if math.abs(D) < TINY
D := TINY
D := 1.0 / D
C := bi + ai / C
if math.abs(C) < TINY
C := TINY
float delta = C * D
f *= delta
if math.abs(delta - 1.0) < EPS
break
float lnPfx = a * math.log(x) - x - lnGamma(a)
math.exp(lnPfx) * f
//@function Regularized lower incomplete gamma function P(a,x)
//@param a Shape parameter (a > 0)
//@param x Evaluation point (x >= 0)
//@returns CDF value P(X <= x) for Gamma(a, beta)
gammaP(series float a, series float x) =>
if x <= 0.0
0.0
else if x < a + 1.0
gammaSeries(a, x)
else
1.0 - gammaCF(a, x)
//@function Computes Gamma Distribution CDF for a normalized price series
//@param source Series to transform
//@param period Lookback period for min-max normalization
//@param shape Shape parameter alpha (a > 0)
//@param rate Rate parameter beta (b > 0); x is scaled by rate
//@returns Gamma CDF value in [0,1]
gammadist(series float source, simple int period, simple float shape, simple float rate) =>
if period <= 0
runtime.error("Period must be greater than 0")
if shape <= 0.0
runtime.error("Shape must be greater than 0")
if rate <= 0.0
runtime.error("Rate must be greater than 0")
float minVal = source
float maxVal = source
for i = 1 to period - 1
float v = source[i]
if not na(v)
if v < minVal
minVal := v
if v > maxVal
maxVal := v
float range = maxVal - minVal
float x = range > 0.0 ? (source - minVal) / range : 0.5
float scaled = math.max(0.0, x * rate)
gammaP(shape, scaled)
// ---------- Main loop ----------
// Inputs
i_source = input.source(close, "Source")
i_period = input.int(50, "Lookback Period", minval=2, maxval=5000, tooltip="Min-max normalization window")
i_shape = input.float(2.0, "Shape (α)", minval=0.01, step=0.1, tooltip="Shape parameter; α<1 exponential decay, α=1 exponential, α>1 bell-shaped")
i_rate = input.float(3.0, "Rate (β)", minval=0.01, step=0.1, tooltip="Rate parameter; scales normalized x before CDF evaluation")
// Calculation
result = gammadist(i_source, i_period, i_shape, i_rate)
// Plot
plot(result, "GAMMADIST", color=color.yellow, linewidth=2)
hline(0.5, "Midline", color=color.gray, linestyle=hline.style_dotted)
hline(0.95, "Upper", color=color.red, linestyle=hline.style_dashed)
hline(0.05, "Lower", color=color.green, linestyle=hline.style_dashed)
+58
View File
@@ -0,0 +1,58 @@
// The MIT License (MIT)
// © mihakralj
//@version=6
indicator("Inverse Fast Fourier Transform (IFFT)", "IFFT", overlay=true, precision=6)
//@function Spectral filter via forward DFT + frequency zeroing + inverse DFT
//@param source Series to filter
//@param windowSize DFT window size (power of 2: 32, 64, 128)
//@param numHarmonics Number of lowest-frequency harmonics to keep (1..windowSize/2)
//@returns reconstructed (filtered) value at the current bar
//@optimized Forward O(N*H) + inverse O(H); H = numHarmonics
ifft(series float source, simple int windowSize, simple int numHarmonics) =>
if windowSize != 32 and windowSize != 64 and windowSize != 128
runtime.error("Window size must be 32, 64, or 128")
if numHarmonics < 1
runtime.error("Number of harmonics must be >= 1")
int N = windowSize
int halfN = N / 2
int H = math.min(numHarmonics, halfN)
float twoPiOverN = 2.0 * math.pi / N
float dcRe = 0.0
for n = 0 to N - 1
float val = nz(source[n])
float w = 0.5 - 0.5 * math.cos(twoPiOverN * n)
dcRe += val * w
float result = dcRe / N
for k = 1 to H
float re = 0.0
float im = 0.0
float omega_k = twoPiOverN * k
for n = 0 to N - 1
float val = nz(source[n])
float w = 0.5 - 0.5 * math.cos(twoPiOverN * n)
float xw = val * w
float angle = omega_k * n
re += xw * math.cos(angle)
im -= xw * math.sin(angle)
result += 2.0 * re / N
result
// ---------- Main loop ----------
// Inputs
i_source = input.source(close, "Source")
i_window = input.int(64, "Window Size", options=[32, 64, 128], tooltip="DFT window; larger = finer frequency resolution")
i_harmonics = input.int(5, "Harmonics", minval=1, maxval=32, tooltip="Number of lowest-frequency components to keep; fewer = smoother")
// Calculation
float filtered = ifft(i_source, i_window, i_harmonics)
// Plot
plot(filtered, "IFFT", color=color.yellow, linewidth=2)
+67
View File
@@ -0,0 +1,67 @@
// The MIT License (MIT)
// © mihakralj
//@version=6
indicator("Log-Normal Distribution CDF (LOGNORMDIST)", "LOGNORMDIST", overlay=false, precision=6)
//@function Standard normal CDF Φ(z) via Abramowitz & Stegun rational approximation (7.1.26)
//@param z Input value
//@returns Φ(z) = P(Z <= z) for Z ~ N(0,1), accurate to ~1.5e-7
normalCdf(series float z) =>
float P = 0.2316419
float B1 = 0.319381530
float B2 = -0.356563782
float B3 = 1.781477937
float B4 = -1.821255978
float B5 = 1.330274429
float az = math.abs(z)
float t = 1.0 / (1.0 + P * az)
float phi = math.exp(-0.5 * az * az) / math.sqrt(2.0 * math.pi)
float poly = ((((B5 * t + B4) * t + B3) * t + B2) * t + B1) * t
float cdf = 1.0 - phi * poly
z >= 0.0 ? cdf : 1.0 - cdf
//@function Log-Normal Distribution CDF for a normalized price series
//@param source Series to transform
//@param period Lookback period for min-max normalization
//@param mu Location parameter (mean of ln(X))
//@param sigma Scale parameter (std dev of ln(X)), sigma > 0
//@returns Log-normal CDF value in [0,1]
lognormdist(series float source, simple int period, simple float mu, simple float sigma) =>
if period <= 0
runtime.error("Period must be greater than 0")
if sigma <= 0.0
runtime.error("Sigma must be greater than 0")
float minVal = source
float maxVal = source
for i = 1 to period - 1
float v = source[i]
if not na(v)
if v < minVal
minVal := v
if v > maxVal
maxVal := v
float range = maxVal - minVal
float x = range > 0.0 ? (source - minVal) / range : 0.5
float safeX = math.max(1e-10, x)
float z = (math.log(safeX) - mu) / sigma
normalCdf(z)
// ---------- Main loop ----------
// Inputs
i_source = input.source(close, "Source")
i_period = input.int(50, "Lookback Period", minval=2, maxval=5000, tooltip="Min-max normalization window")
i_mu = input.float(0.0, "Mu (μ)", step=0.1, tooltip="Location parameter; mean of ln(X). 0 = centered on geometric mean of [0,1]")
i_sigma = input.float(1.0, "Sigma (σ)", minval=0.01, step=0.1, tooltip="Scale parameter; std dev of ln(X). Lower = steeper S-curve")
// Calculation
float result = lognormdist(i_source, i_period, i_mu, i_sigma)
// Plot
plot(result, "LOGNORMDIST", color=color.yellow, linewidth=2)
hline(0.5, "Midline", color=color.gray, linestyle=hline.style_dotted)
hline(0.95, "Upper", color=color.red, linestyle=hline.style_dashed)
hline(0.05, "Lower", color=color.green, linestyle=hline.style_dashed)
+144
View File
@@ -0,0 +1,144 @@
// The MIT License (MIT)
// © mihakralj
//@version=6
indicator("Poisson Distribution CDF (POISSONDIST)", "POISSONDIST", overlay=false, precision=6)
//@function Log-gamma via Lanczos approximation (g=7, 9 coefficients)
//@param z Input value (z > 0)
//@returns ln(Gamma(z))
lnGamma(simple float z) =>
float g = 7.0
array<float> c = array.from(
0.99999999999980993,
676.5203681218851,
-1259.1392167224028,
771.32342877765313,
-176.61502916214059,
12.507343278686905,
-0.13857109526572012,
9.9843695780195716e-6,
1.5056327351493116e-7)
float zz = z - 1.0
float x = array.get(c, 0)
for i = 1 to 8
x += array.get(c, i) / (zz + i)
float t = zz + g + 0.5
0.5 * math.log(2.0 * math.pi) + (zz + 0.5) * math.log(t) - t + math.log(x)
//@function Regularized lower incomplete gamma P(a,x) via series expansion
//@param a Shape parameter (a > 0)
//@param x Evaluation point (x >= 0)
//@returns P(a,x) = gamma(a,x) / Gamma(a)
gammaSeries(series float a, series float x) =>
float EPS = 1e-10
int MAXITER = 200
float ap = a
float sum = 1.0 / a
float del = sum
for n = 1 to MAXITER
ap += 1.0
del *= x / ap
sum += del
if math.abs(del) < math.abs(sum) * EPS
break
float lnPfx = a * math.log(x) - x - lnGamma(a)
math.exp(lnPfx) * sum
//@function Regularized upper incomplete gamma Q(a,x) via continued fraction (Lentz)
//@param a Shape parameter (a > 0)
//@param x Evaluation point (x >= 0)
//@returns Q(a,x) = 1 - P(a,x)
gammaCF(series float a, series float x) =>
float TINY = 1e-30
float EPS = 1e-10
int MAXITER = 200
float b0 = x + 1.0 - a
float C = 1.0 / TINY
float D = b0 < TINY ? 1.0 / TINY : 1.0 / b0
float f = D
for i = 1 to MAXITER
float ai = -float(i) * (float(i) - a)
float bi = x + 2.0 * float(i) + 1.0 - a
D := bi + ai * D
if math.abs(D) < TINY
D := TINY
D := 1.0 / D
C := bi + ai / C
if math.abs(C) < TINY
C := TINY
float delta = C * D
f *= delta
if math.abs(delta - 1.0) < EPS
break
float lnPfx = a * math.log(x) - x - lnGamma(a)
math.exp(lnPfx) * f
//@function Regularized lower incomplete gamma function P(a,x)
//@param a Shape parameter (a > 0)
//@param x Evaluation point (x >= 0)
//@returns CDF value P(X <= x) for Gamma(a, 1)
gammaP(series float a, series float x) =>
if x <= 0.0
0.0
else if x < a + 1.0
gammaSeries(a, x)
else
1.0 - gammaCF(a, x)
//@function Computes Poisson Distribution CDF for a normalized price series
//@param source Series to transform
//@param period Lookback period for min-max normalization
//@param k Threshold count (non-negative integer); P(X <= k)
//@param lambda_scale Scale factor applied to normalized price to produce lambda
//@returns Poisson CDF value P(X <= k) in [0,1]
//@description The Poisson CDF gives P(X <= k) for X ~ Poisson(lambda).
// The normalized price is mapped to lambda = x * lambda_scale where
// x = (source - min) / (max - min) over the lookback window.
// Uses the identity: P(X <= k) = 1 - P(k+1, lambda) where P(a,x)
// is the regularized lower incomplete gamma function (reused from GAMMADIST).
// When lambda = 0, CDF = 1.0 (degenerate case: all mass at X=0).
// lambda_scale controls the effective range of lambda; higher values
// spread the CDF response across a wider event-rate range.
poissondist(series float source, simple int period, simple int k, simple float lambda_scale) =>
if period <= 0
runtime.error("Period must be greater than 0")
if k < 0
runtime.error("Threshold k must be non-negative")
if lambda_scale <= 0.0
runtime.error("Lambda scale must be greater than 0")
float minVal = source
float maxVal = source
for i = 1 to period - 1
float v = source[i]
if not na(v)
if v < minVal
minVal := v
if v > maxVal
maxVal := v
float range = maxVal - minVal
float x = range > 0.0 ? (source - minVal) / range : 0.5
float lambda = math.max(0.0, x * lambda_scale)
if lambda <= 0.0
1.0
else
1.0 - gammaP(float(k + 1), lambda)
// ---------- Main loop ----------
// Inputs
i_source = input.source(close, "Source")
i_period = input.int(50, "Lookback Period", minval=2, maxval=5000, tooltip="Min-max normalization window")
i_k = input.int(5, "Threshold (k)", minval=0, tooltip="P(X <= k); number of events threshold")
i_lambda_scale = input.float(10.0, "Lambda Scale", minval=0.01, step=0.5, tooltip="Scales normalized price to lambda; higher = wider event-rate range")
// Calculation
result = poissondist(i_source, i_period, i_k, i_lambda_scale)
// Plot
plot(result, "POISSONDIST", color=color.yellow, linewidth=2)
hline(0.5, "Midline", color=color.gray, linestyle=hline.style_dotted)
hline(0.95, "Upper", color=color.red, linestyle=hline.style_dashed)
hline(0.05, "Lower", color=color.green, linestyle=hline.style_dashed)
+1 -6
View File
@@ -275,12 +275,7 @@ public class SigmoidTests
[Fact]
public void Calculate_Span_MatchesStreaming()
{
double[] source = new double[100];
var rng = new Random(42);
for (int i = 0; i < source.Length; i++)
{
source[i] = rng.NextDouble() * 200 - 100;
}
double[] source = new GBM(seed: 42).Fetch(100, DateTime.UtcNow.Ticks, TimeSpan.FromMinutes(1)).CloseValues.ToArray();
double[] spanOutput = new double[source.Length];
Sigmoid.Batch(source.AsSpan(), spanOutput.AsSpan());
@@ -81,15 +81,14 @@ public class SigmoidValidationTests
public void Sigmoid_OutputAlwaysBetweenZeroAndOne()
{
var sigmoid = new Sigmoid();
var rng = new Random(42);
var bars = new GBM(seed: 42).Fetch(1000, DateTime.UtcNow.Ticks, TimeSpan.FromSeconds(1));
for (int i = 0; i < 1000; i++)
{
double x = rng.NextDouble() * 2000 - 1000; // Range [-1000, 1000]
var result = sigmoid.Update(new TValue(DateTime.UtcNow.AddSeconds(i), x), true);
var result = sigmoid.Update(bars.Close[i], true);
Assert.True(result.Value >= 0.0, $"Output {result.Value} should be >= 0 for input {x}");
Assert.True(result.Value <= 1.0, $"Output {result.Value} should be <= 1 for input {x}");
Assert.True(result.Value >= 0.0, $"Output {result.Value} should be >= 0 for input {bars.Close[i].Value}");
Assert.True(result.Value <= 1.0, $"Output {result.Value} should be <= 1 for input {bars.Close[i].Value}");
}
}
@@ -219,12 +218,7 @@ public class SigmoidValidationTests
{
double k = 0.5;
double x0 = 50.0;
double[] source = new double[500];
var rng = new Random(42);
for (int i = 0; i < source.Length; i++)
{
source[i] = rng.NextDouble() * 200 - 50; // Range [-50, 150]
}
double[] source = new GBM(seed: 42).Fetch(500, DateTime.UtcNow.Ticks, TimeSpan.FromSeconds(1)).CloseValues.ToArray();
// Span calculation
double[] spanOutput = new double[source.Length];
+142
View File
@@ -0,0 +1,142 @@
// The MIT License (MIT)
// © mihakralj
//@version=6
indicator("Student's t-Distribution CDF (TDIST)", "TDIST", overlay=false, precision=6)
//@function Natural log of the Gamma function via Lanczos approximation (g=7, 9 coefficients)
//@param z Argument (must be > 0)
//@returns ln(Γ(z))
lnGamma(float z) =>
float g = 7.0
array<float> c = array.from(
0.99999999999980993,
676.5203681218851,
-1259.1392167224028,
771.32342877765313,
-176.61502916214059,
12.507343278686905,
-0.13857109526572012,
9.9843695780195716e-6,
1.5056327351493116e-7)
float zz = z < 0.5 ? 1.0 - z : z - 1.0
float x = array.get(c, 0)
for i = 1 to 8
x += array.get(c, i) / (zz + float(i))
float t = zz + g + 0.5
float logSqrt2Pi = 0.9189385332046727
float lnG = logSqrt2Pi + math.log(t) * (zz + 0.5) - t + math.log(x)
z < 0.5 ? math.log(math.pi / math.sin(math.pi * z)) - lnG : lnG
//@function Regularized incomplete beta function I_x(a, b) via Lentz continued fraction
//@param x Upper integration limit in [0, 1]
//@param a First shape parameter (> 0)
//@param b Second shape parameter (> 0)
//@returns I_x(a, b) in [0, 1]
betaReg(float x, float a, float b) =>
int MAXITER = 200
float EPS = 1e-10
float TINY = 1e-30
float result = 0.0
if x <= 0.0
result := 0.0
else if x >= 1.0
result := 1.0
else
bool flipped = x > (a + 1.0) / (a + b + 2.0)
float xx = flipped ? 1.0 - x : x
float aa = flipped ? b : a
float bb = flipped ? a : b
float logPfx = aa * math.log(xx) + bb * math.log(1.0 - xx)
- math.log(aa)
- lnGamma(aa) - lnGamma(bb) + lnGamma(aa + bb)
float pfx = math.exp(logPfx)
float f = 1.0 + TINY
float C = f
float D = 0.0
for m = 1 to MAXITER
float m2 = 2.0 * float(m)
float numEven = float(m) * (bb - float(m)) * xx /
((aa + m2 - 1.0) * (aa + m2))
D := 1.0 + numEven * D
D := math.abs(D) < TINY ? TINY : D
D := 1.0 / D
C := 1.0 + numEven / C
C := math.abs(C) < TINY ? TINY : C
f *= C * D
float numOdd = -(aa + float(m)) * (aa + bb + float(m)) * xx /
((aa + m2) * (aa + m2 + 1.0))
D := 1.0 + numOdd * D
D := math.abs(D) < TINY ? TINY : D
D := 1.0 / D
C := 1.0 + numOdd / C
C := math.abs(C) < TINY ? TINY : C
float delta = C * D
f *= delta
if math.abs(delta - 1.0) < EPS
break
float raw = pfx * f
result := flipped ? 1.0 - raw : raw
result
//@function Calculates Student's t-Distribution CDF
//@param source Series to evaluate (typically close)
//@param period Lookback period for min-max normalization
//@param df Degrees of freedom (ν > 0)
//@returns CDF value P(T ≤ t) in [0, 1]
//@description The Student's t-distribution CDF is computed via the relation:
// CDF(t; ν) = 1 0.5 × I(ν/(ν+t²), ν/2, 1/2) if t ≥ 0
// CDF(t; ν) = 0.5 × I(ν/(ν+t²), ν/2, 1/2) if t < 0
// where I is the regularized incomplete beta function (Lentz CF).
// The source is min-max normalized over the lookback period, then mapped
// to a t-statistic via linear transform: t = (x 0.5) × tScale where
// tScale = 6.0 maps the [0,1] range to approximately [3, +3].
// Reuses lnGamma (Lanczos 9-coeff) and betaReg (Lentz CF with symmetry flip)
// from BETADIST/FDIST. Stateless pure function — no var state.
// df=1 → Cauchy (heavy tails), df=5 → moderate tails, df→∞ → normal.
// Trading interpretation: CDF near 1.0 = price at top of recent range
// (assuming large df, approaches normal behavior). Heavy tails (low df) make
// the CDF less extreme, reflecting uncertainty about outlier moves.
tdist(series float source, simple int period, simple float df) =>
if period <= 0
runtime.error("Period must be greater than 0")
if df <= 0.0
runtime.error("Degrees of freedom must be greater than 0")
float src = nz(source)
float hi = src
float lo = src
for i = 1 to period - 1
float v = nz(source[i])
hi := math.max(hi, v)
lo := math.min(lo, v)
float range = hi - lo
float x = range == 0.0 ? 0.5 : (src - lo) / range
float tScale = 6.0
float t = (x - 0.5) * tScale
float t2 = t * t
float bx = df / (df + t2)
float ibeta = betaReg(bx, df / 2.0, 0.5)
t >= 0.0 ? 1.0 - 0.5 * ibeta : 0.5 * ibeta
// ---------- Main loop ----------
// Inputs
i_source = input.source(close, "Source")
i_period = input.int(50, "Period", minval=1)
i_df = input.float(5.0, "Degrees of Freedom", minval=0.1, step=0.1)
// Calculation
tdist_value = tdist(i_source, i_period, i_df)
// Plot
plot(tdist_value, "TDIST", color=color.yellow, linewidth=2)
hline(0.5, "Midline", color=color.gray, linestyle=hline.style_dotted)
hline(0.95, "Upper", color=color.red, linestyle=hline.style_dashed)
hline(0.05, "Lower", color=color.green, linestyle=hline.style_dashed)
+68
View File
@@ -0,0 +1,68 @@
// The MIT License (MIT)
// © mihakralj
//@version=6
indicator("Weibull Distribution CDF (WEIBULLDIST)", "WEIBULLDIST", overlay=false, precision=6)
//@function Calculates the Weibull Distribution CDF
//@param source Series to evaluate (typically close)
//@param period Lookback period for min-max normalization
//@param shape Shape parameter k (> 0) — controls distribution form
//@param scale Scale parameter λ (> 0) — scales the normalized price
//@returns CDF value F(x) in [0, 1]
//@description The Weibull distribution CDF has a simple closed form:
// F(x; k, λ) = 1 exp((x/λ)^k) for x ≥ 0
// F(x) = 0 for x < 0
// The source is min-max normalized over the lookback period to [0, 1],
// then treated as the input x to the CDF (no additional scaling needed
// since λ handles the effective range).
// Shape k controls the distribution form:
// k < 1: decreasing failure rate (early failures) — concave CDF
// k = 1: constant failure rate (exponential distribution) — same as EXPDIST
// k = 2: Rayleigh distribution — linear failure rate
// k > 3: approaches normal-like shape — S-curve CDF
// Scale λ controls how quickly CDF rises:
// larger λ → slower rise (more spread), smaller λ → faster saturation
// No special functions needed — only exp and pow. O(period) for min-max scan,
// CDF itself is O(1). Stateless pure function — no var state.
// Trading interpretation: CDF near 1.0 = price at top of recent range,
// near 0.0 = price at bottom. Shape k tunes sensitivity to extremes.
weibulldist(series float source, simple int period, simple float shape, simple float scale) =>
if period <= 0
runtime.error("Period must be greater than 0")
if shape <= 0.0
runtime.error("Shape parameter must be greater than 0")
if scale <= 0.0
runtime.error("Scale parameter must be greater than 0")
float src = nz(source)
float hi = src
float lo = src
for i = 1 to period - 1
float v = nz(source[i])
hi := math.max(hi, v)
lo := math.min(lo, v)
float range = hi - lo
float x = range == 0.0 ? 0.5 : (src - lo) / range
float safeX = math.max(0.0, x)
float ratio = safeX / scale
float raised = math.pow(ratio, shape)
1.0 - math.exp(-raised)
// ---------- Main loop ----------
// Inputs
i_source = input.source(close, "Source")
i_period = input.int(50, "Period", minval=1)
i_shape = input.float(2.0, "Shape (k)", minval=0.01, step=0.1)
i_scale = input.float(0.5, "Scale (λ)", minval=0.01, step=0.1)
// Calculation
weibull_value = weibulldist(i_source, i_period, i_shape, i_scale)
// Plot
plot(weibull_value, "WEIBULLDIST", color=color.yellow, linewidth=2)
hline(0.5, "Midline", color=color.gray, linestyle=hline.style_dotted)
hline(0.95, "Upper", color=color.red, linestyle=hline.style_dashed)
hline(0.05, "Lower", color=color.green, linestyle=hline.style_dashed)