mirror of
https://github.com/mihakralj/QuanTAlib.git
synced 2026-08-18 02:28:05 +00:00
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:
@@ -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)
|
||||
@@ -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)
|
||||
@@ -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)
|
||||
@@ -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)
|
||||
@@ -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)
|
||||
@@ -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)
|
||||
@@ -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)
|
||||
@@ -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)
|
||||
@@ -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)
|
||||
@@ -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)
|
||||
@@ -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)
|
||||
@@ -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];
|
||||
|
||||
@@ -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)
|
||||
@@ -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)
|
||||
Reference in New Issue
Block a user