mirror of
https://github.com/mihakralj/QuanTAlib.git
synced 2026-07-30 02:27:43 +00:00
81 lines
3.1 KiB
Plaintext
81 lines
3.1 KiB
Plaintext
// Licensed under the Apache License, Version 2.0
|
||
// © mihakralj
|
||
//@version=6
|
||
indicator("Normal Distribution CDF (NORMDIST)", "NORMDIST", overlay=false, precision=6)
|
||
|
||
//@function Computes Normal Distribution CDF for a normalized price series
|
||
//@param source Series to transform
|
||
//@param period Lookback period for z-score normalization
|
||
//@param mu Mean parameter (0.0 for standard normal after z-score)
|
||
//@param sigma Standard deviation parameter (1.0 for standard normal after z-score)
|
||
//@returns CDF value in [0,1]: Φ(z) = 0.5 × (1 + erf(z / √2))
|
||
//@optimized O(period) per bar for mean/variance scan; CDF itself is O(1)
|
||
normdist(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")
|
||
|
||
// Compute rolling mean and standard deviation over lookback
|
||
float sum = 0.0
|
||
float sumSq = 0.0
|
||
int count = 0
|
||
for i = 0 to period - 1
|
||
float v = source[i]
|
||
if not na(v)
|
||
sum += v
|
||
sumSq += v * v
|
||
count += 1
|
||
|
||
float result = 0.5
|
||
if count >= 2
|
||
float mean = sum / count
|
||
float variance = (sumSq / count) - (mean * mean)
|
||
float stddev = variance > 0.0 ? math.sqrt(variance) : 0.0
|
||
|
||
// Z-score: normalize source relative to its own rolling distribution
|
||
float z = stddev > 0.0 ? (source - mean) / stddev : 0.0
|
||
|
||
// Apply user-specified mu/sigma shift: z_final = (z - mu) / sigma
|
||
float z_final = (z - mu) / sigma
|
||
|
||
// Approximate erf via Abramowitz & Stegun (max error < 1.5e-7)
|
||
// erf(x) = 1 - (a1*t + a2*t^2 + a3*t^3) * exp(-x^2)
|
||
// where t = 1 / (1 + 0.47047 * |x|)
|
||
float x = z_final / math.sqrt(2.0)
|
||
float ax = math.abs(x)
|
||
float t = 1.0 / (1.0 + 0.47047 * ax)
|
||
float t2 = t * t
|
||
float t3 = t2 * t
|
||
|
||
float a1 = 0.3480242
|
||
float a2 = -0.0958798
|
||
float a3 = 0.7478556
|
||
|
||
float erfApprox = 1.0 - (a1 * t + a2 * t2 + a3 * t3) * math.exp(-(ax * ax))
|
||
float erf = x >= 0.0 ? erfApprox : -erfApprox
|
||
|
||
// CDF: Φ(z) = 0.5 * (1 + erf(z / sqrt(2)))
|
||
result := 0.5 * (1.0 + erf)
|
||
|
||
result
|
||
|
||
// ---------- Main loop ----------
|
||
|
||
// Inputs
|
||
i_source = input.source(close, "Source")
|
||
i_period = input.int(50, "Lookback Period", minval=2, maxval=5000, tooltip="Rolling window for z-score normalization")
|
||
i_mu = input.float(0.0, "Mu (μ)", step=0.1, tooltip="Mean shift parameter (0 = standard normal)")
|
||
i_sigma = input.float(1.0, "Sigma (σ)", minval=0.01, step=0.1, tooltip="Scale parameter (1 = standard normal)")
|
||
|
||
// Calculation
|
||
float result = normdist(i_source, i_period, i_mu, i_sigma)
|
||
|
||
// Plot
|
||
plot(result, "NORMDIST", color=color.yellow, linewidth=2)
|
||
hline(0.5, "Midline", color=color.gray, linestyle=hline.style_dotted)
|
||
hline(0.975, "Upper 2σ", color=color.red, linestyle=hline.style_dashed)
|
||
hline(0.025, "Lower 2σ", color=color.green, linestyle=hline.style_dashed)
|
||
hline(0.841, "Upper 1σ", color=color.orange, linestyle=hline.style_dashed)
|
||
hline(0.159, "Lower 1σ", color=color.teal, linestyle=hline.style_dashed)
|