- Added Ztest class to compute the one-sample t-statistic using sample standard deviation with Bessel correction. - Implemented validation tests for Ztest to ensure accuracy against manual calculations and PineScript. - Updated documentation for Ztest, detailing its mathematical foundation, performance profile, and common pitfalls. - Adjusted NDepend badges to reflect changes in code metrics after implementation. - Updated missing indicators report to reflect the completion of statistical indicators, including ZTEST.
5.2 KiB
ZSCORE: Z-Score (Population Standard Score)
"How far from normal is this?" — Every risk manager, every day.
Introduction
The Z-Score measures how many population standard deviations a value lies from the rolling mean over a lookback window. Unlike the related Standardize indicator (which uses sample standard deviation with Bessel's correction), ZSCORE uses population standard deviation, matching the PineScript ta.zscore convention. Output is unbounded, typically ranging from -3 to +3 for normally distributed data. A z-score of 0 means the value equals the window mean; ±2 flags statistical outliers at the 95% level.
Historical Context
The z-score originates from Karl Pearson's work in the 1890s on the theory of statistics. It transforms any distribution into units of standard deviation, making cross-series comparison possible. In trading, z-scores power mean-reversion strategies (enter when |z| > 2, exit when |z| < 0.5), pairs trading (z-score of spread), and anomaly detection. The population variant (N denominator) is standard in PineScript and most trading platforms because the rolling window IS the population of interest — not a sample from a larger population.
Architecture and Physics
1. Core Formula
z = \frac{x - \mu}{\sigma}
where:
\mu = \frac{1}{N} \sum_{i=1}^{N} x_i(population mean over window)\sigma = \sqrt{\frac{1}{N} \sum_{i=1}^{N} (x_i - \mu)^2}(population standard deviation)
2. Computational Form
Using the identity \text{Var}(X) = E[X^2] - (E[X])^2:
\sigma = \sqrt{\frac{\sum x_i^2}{N} - \left(\frac{\sum x_i}{N}\right)^2}
This avoids a two-pass algorithm. One pass computes both \sum x_i and \sum x_i^2.
3. Edge Cases
| Condition | Result |
|---|---|
N < 2 |
0.0 |
\sigma < 10^{-10} |
0.0 (constant data) |
| Input is NaN/Infinity | Substitute last valid value |
| Negative variance (floating-point) | Clamp to 0.0 |
4. Population vs Sample
| Variant | Denominator | Use Case |
|---|---|---|
| ZSCORE (this) | N |
Rolling window IS the population |
| Standardize | N - 1 |
Window is sample from larger population |
Relationship: z_{\text{pop}} = z_{\text{sample}} \cdot \sqrt{\frac{N}{N-1}}
5. State Management
Uses RingBuffer for the sliding window. State rollback via record struct State with _s/_ps pattern for bar correction support.
Mathematical Foundation
Z-Score Derivation
Given a window of N values \{x_1, x_2, \ldots, x_N\}:
\mu = \frac{1}{N} \sum_{i=1}^{N} x_i
\sigma^2 = \frac{1}{N} \sum_{i=1}^{N} (x_i - \mu)^2 = \frac{1}{N} \sum_{i=1}^{N} x_i^2 - \mu^2
z = \frac{x_N - \mu}{\sigma}
Scale Invariance
For any linear transform y = ax + b where a > 0:
z(y) = \frac{(ax + b) - (a\mu + b)}{a\sigma} = \frac{x - \mu}{\sigma} = z(x)
Z-scores are invariant under positive linear transformations. This property makes them ideal for comparing series measured in different units.
Performance Profile
Operation Count (per Update)
| Operation | Count |
|---|---|
| Additions | N (sum scan) |
| Multiplications | N (sumSq scan) |
| Division | 3 |
| Square root | 1 |
| Comparison | 2 |
Complexity
| Method | Time | Space |
|---|---|---|
Update |
O(N) |
O(1) auxiliary |
Batch(Span) |
O(N \cdot P) |
stackalloc or ArrayPool |
Quality Metrics
| Metric | Score |
|---|---|
| Accuracy | 9/10 |
| Numerical stability | 8/10 |
| Memory efficiency | 9/10 |
| SIMD potential | Limited (sequential dependency on current value) |
Validation
| Library | Status | Notes |
|---|---|---|
| Manual | Verified | Known-value tests match hand computation |
| Standardize | Cross-validated | z_{\text{pop}} = z_{\text{sample}} \cdot \sqrt{N/(N-1)} holds |
| PineScript | Formula match | Population stddev, same edge-case handling |
Common Pitfalls
-
Population vs sample confusion. ZSCORE uses N denominator. Standardize uses N-1. The difference matters for small windows: at period=5, the ratio is
\sqrt{5/4} = 1.118, an 11.8% discrepancy. -
Assuming normality. Z-scores measure distance in sigma units but don't guarantee the underlying distribution is normal. Fat-tailed financial returns make |z| > 3 more common than the 0.3% a normal distribution predicts.
-
Constant data edge case. When all values in the window are identical,
\sigma = 0and division is undefined. Implementation returns 0.0. -
Floating-point variance. The formula
E[X^2] - (E[X])^2can produce tiny negative values due to floating-point arithmetic. Clamped to zero before taking square root. -
Warmup period. Requires at least 2 data points for meaningful output. During warmup (
N < 2), returns 0.0. -
NaN propagation. Non-finite inputs are substituted with the last valid value to prevent NaN from contaminating the rolling statistics.
References
- Pearson, K. (1894). "Contributions to the Mathematical Theory of Evolution." Philosophical Transactions of the Royal Society.
- TradingView PineScript Reference: ta.zscore
- Bollinger, J. (2001). Bollinger on Bollinger Bands. McGraw-Hill. (Z-score normalization of Bollinger %B)