Add new moving average implementations: LTMA, MCNMA, NLMA, NMA, NYQMA, RAIN, and TRAMA

- LTMA (Linear Trend Moving Average): Introduces a predictive moving average using dual cascaded EMAs for trend estimation.
- MCNMA (McNicholl EMA): Implements a zero-lag TEMA using a cascaded EMA structure for enhanced responsiveness.
- NLMA (Non-Lag Moving Average): Utilizes a damped cosine kernel to achieve reduced lag in moving averages.
- NMA (Natural Moving Average): Adapts smoothing based on volatility profiles using a square-root kernel.
- NYQMA (Nyquist Moving Average): Applies the Nyquist-Shannon theorem to prevent aliasing in cascaded moving averages.
- RAIN (Rainbow Moving Average): Combines multiple SMA layers with weighted averages for multi-scale smoothing.
- TRAMA (Trend Regularity Adaptive Moving Average): Adapts smoothing based on the frequency of new highs and lows in price data.
This commit is contained in:
Miha Kralj
2026-02-20 21:40:32 -08:00
parent cbeefc9d64
commit 90d5638008
121 changed files with 9595 additions and 6315 deletions
+95
View File
@@ -0,0 +1,95 @@
# NORMDIST: Normal Distribution CDF
The Normal Distribution CDF transforms a z-score normalized price into the cumulative distribution function of the Gaussian distribution, producing an output in $[0, 1]$. Unlike other distribution indicators in this library that use min-max normalization, NORMDIST computes a rolling mean and standard deviation over the lookback window, converting the raw price to a z-score, then applies optional $\mu$ and $\sigma$ parameters for further shaping. The result represents the probability that a standard normal random variable would fall at or below the observed z-score. This makes NORMDIST a direct percentile ranking under the assumption of normally distributed returns, with the output naturally centered at 0.5 when the price is at its rolling mean.
## Historical Context
The normal distribution was discovered independently by Abraham de Moivre (1733) as a limit of the binomial distribution, and by Carl Friedrich Gauss (1809) in the context of astronomical measurement errors. Pierre-Simon Laplace (1812) proved the central limit theorem, establishing that sums of independent random variables converge to the normal distribution regardless of the underlying distribution.
In finance, the normal distribution assumption for asset returns was formalized by Harry Markowitz (1952) in Modern Portfolio Theory and Louis Bachelier (1900) in his thesis on speculation. Despite well-known departures (fat tails, skewness, volatility clustering), the normal CDF remains the most widely used probability transform in quantitative finance. It underpins the Black-Scholes formula, Value-at-Risk calculations, and the Sharpe ratio.
The z-score normalization approach used here is more statistically grounded than the min-max normalization used by other distribution indicators: it captures the rolling distributional properties (mean, variance) of the price series rather than just the range. This means NORMDIST adapts to both the level and the volatility of the price, making readings directly interpretable as "number of standard deviations from the mean."
## Architecture and Physics
The computation follows a three-phase pipeline:
**Phase 1: Rolling statistics** computes the mean and standard deviation over the lookback window using a single-pass algorithm:
$$\bar{x} = \frac{1}{n}\sum_{i=0}^{n-1} x_i, \quad s = \sqrt{\frac{1}{n}\sum_{i=0}^{n-1} x_i^2 - \bar{x}^2}$$
NaN values are excluded from the count. If fewer than 2 valid values exist, the output defaults to 0.5.
**Phase 2: Z-score with parameter adjustment** converts the price to a z-score relative to the rolling distribution, then applies the user-specified shift and scale:
$$z = \frac{x - \bar{x}}{s}, \quad z_{\text{final}} = \frac{z - \mu}{\sigma}$$
With defaults $\mu = 0, \sigma = 1$, $z_{\text{final}} = z$ (standard z-score). Increasing $\sigma$ compresses the CDF curve (less sensitive to deviations); shifting $\mu$ moves the midpoint away from the rolling mean.
**Phase 3: Error function approximation** evaluates $\Phi(z)$ using the Abramowitz and Stegun formula (7.1.26) with 3 polynomial terms in the exponential approximation of `erf`:
$$\text{erf}(x) \approx 1 - (a_1 t + a_2 t^2 + a_3 t^3) \cdot e^{-x^2}$$
where $t = 1/(1 + 0.47047|x|)$. The CDF is then $\Phi(z) = 0.5(1 + \text{erf}(z/\sqrt{2}))$.
**Accuracy**: The 3-term Abramowitz-Stegun approximation achieves maximum error of $\sim 2.5 \times 10^{-5}$, sufficient for indicator applications. For higher precision, the 5-term version (used in LOGNORMDIST) reduces error to $\sim 1.5 \times 10^{-7}$.
## Mathematical Foundation
The standard normal PDF and CDF:
$$\phi(z) = \frac{1}{\sqrt{2\pi}} e^{-z^2/2}$$
$$\Phi(z) = \frac{1}{2}\left(1 + \text{erf}\!\left(\frac{z}{\sqrt{2}}\right)\right) = \int_{-\infty}^{z} \phi(t)\,dt$$
The **error function**:
$$\text{erf}(x) = \frac{2}{\sqrt{\pi}} \int_0^x e^{-t^2}\,dt$$
**Abramowitz and Stegun 3-term approximation**:
$$\text{erf}(x) \approx 1 - (a_1 t + a_2 t^2 + a_3 t^3) e^{-x^2}, \quad t = \frac{1}{1 + 0.47047\,|x|}$$
with $a_1 = 0.3480242$, $a_2 = -0.0958798$, $a_3 = 0.7478556$.
**Z-score normalization** (population standard deviation, not sample):
$$z = \frac{x - \bar{x}}{s}, \quad s = \sqrt{\frac{\sum x_i^2}{n} - \left(\frac{\sum x_i}{n}\right)^2}$$
**Key CDF values**: $\Phi(0) = 0.5$, $\Phi(1) \approx 0.841$, $\Phi(2) \approx 0.977$, $\Phi(-1) \approx 0.159$, $\Phi(-2) \approx 0.023$.
**Parameter constraints**: `period` $> 0$, $\sigma > 0$, $\mu \in \mathbb{R}$. Output is bounded $[0, 1]$.
```
NORMDIST(source, period, mu, sigma):
// Phase 1: rolling statistics
sum = 0; sumSq = 0; count = 0
for i = 0 to period-1:
if not NaN(source[i]):
sum += source[i]
sumSq += source[i]^2
count += 1
if count < 2: return 0.5
mean = sum / count
variance = sumSq/count - mean^2
stddev = sqrt(max(0, variance))
// Phase 2: z-score with parameter adjustment
z = stddev > 0 ? (source - mean) / stddev : 0
z_final = (z - mu) / sigma
// Phase 3: erf approximation -> CDF
x = z_final / sqrt(2)
t = 1 / (1 + 0.47047 * |x|)
erf = 1 - (0.3480242*t + (-0.0958798)*t^2 + 0.7478556*t^3) * exp(-x^2)
if x < 0: erf = -erf
return 0.5 * (1 + erf)
```
## Resources
- Gauss, C.F. "Theoria Motus Corporum Coelestium." 1809.
- Abramowitz, M. & Stegun, I. "Handbook of Mathematical Functions." NBS Applied Mathematics Series 55, 1964. Formulas 7.1.25-7.1.28.
- Markowitz, H. "Portfolio Selection." Journal of Finance, 1952.
- Johnson, N.L., Kotz, S. & Balakrishnan, N. "Continuous Univariate Distributions, Vol. 1." Wiley, 1994.
- Hart, J.F. et al. "Computer Approximations." Wiley, 1968.
+80
View File
@@ -0,0 +1,80 @@
// The MIT License (MIT)
// © 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)