Files

143 lines
5.1 KiB
Plaintext
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// Licensed under the Apache License, Version 2.0
// © 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)