Files
QuanTAlib/lib/numerics/betadist/betadist.pine
T

125 lines
4.6 KiB
Plaintext
Raw Normal View History

// Licensed under the Apache License, Version 2.0
// © 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)