Files

270 lines
10 KiB
Markdown
Raw Permalink Normal View History

# ADF: Augmented Dickey-Fuller Test — Implementation Plan
> "The most important question in time series analysis isn't what the trend is — it's whether there is one at all."
## 1. Overview
The **Augmented Dickey-Fuller (ADF)** test is the gold-standard unit root test for determining whether a time series is stationary. A **p-value** output makes it directly actionable: low p-value (< 0.05) → reject the null hypothesis of a unit root → series is stationary.
**Location:** `lib/statistics/adf/`
**Output:** p-value ∈ [0.0, 1.0] per bar (rolling window)
**Category:** Statistics (alongside [`Hurst`](../lib/statistics/hurst/Hurst.cs), [`Granger`](../lib/statistics/granger/Granger.cs), [`Cointegration`](../lib/statistics/cointegration/Cointegration.cs))
---
## 2. Historical Context
- **Dickey & Fuller (1979)** introduced the basic DF test: does an AR(1) process have a unit root?
- **Said & Dickey (1984)** extended it to the **Augmented** DF test by including lagged difference terms to handle serial correlation.
- **MacKinnon (1994, 2010)** derived the response surface approximations that allow computation of **p-values** from the non-standard ADF distribution (which is not normal or t-distributed).
- The existing [`Cointegration`](../lib/statistics/cointegration/Cointegration.cs:31) indicator already uses a simplified ADF internally (no-intercept, no augmented lags) for the Engle-Granger two-step test. The new `Adf` indicator will be a **standalone, full-featured** single-input ADF test.
---
## 3. Mathematical Foundation
### 3.1 The ADF Regression
$$\Delta y_t = \alpha + \beta t + \gamma \, y_{t-1} + \sum_{i=1}^{p} \delta_i \, \Delta y_{t-i} + \varepsilon_t$$
Where:
- $\Delta y_t = y_t - y_{t-1}$ (first difference)
- $\alpha$ = constant/drift term (included in "c" and "ct" modes)
- $\beta t$ = linear trend term (included in "ct" mode only)
- $\gamma$ = **coefficient of interest** — if $\gamma = 0$, unit root exists
- $\delta_i$ = coefficients on lagged differences (augmented terms to absorb serial correlation)
- $p$ = number of augmented lags
- $\varepsilon_t$ = white noise error
### 3.2 Regression Types
| Mode | Model | Regressors (k) | Use Case |
|------|-------|----------------|----------|
| `NoConstant` ("nc") | $\Delta y_t = \gamma y_{t-1} + \ldots$ | $1 + p$ | Pure random walk test |
| `Constant` ("c") | $\Delta y_t = \alpha + \gamma y_{t-1} + \ldots$ | $2 + p$ | **Default** — random walk with drift |
| `ConstantAndTrend` ("ct") | $\Delta y_t = \alpha + \beta t + \gamma y_{t-1} + \ldots$ | $3 + p$ | Trend-stationary alternative |
### 3.3 Test Statistic
$$\text{ADF}_t = \frac{\hat{\gamma}}{\text{SE}(\hat{\gamma})}$$
Where $\hat{\gamma}$ and $\text{SE}(\hat{\gamma})$ are obtained from OLS regression:
- $\hat{\boldsymbol{\beta}} = (\mathbf{X}'\mathbf{X})^{-1} \mathbf{X}'\mathbf{y}$
- $\hat{\sigma}^2 = \frac{\text{RSS}}{m - k}$ where $m$ = number of observations, $k$ = number of regressors
- $\text{SE}(\hat{\gamma}) = \sqrt{\hat{\sigma}^2 \cdot [(\mathbf{X}'\mathbf{X})^{-1}]_{jj}}$ where $j$ is the column index of $y_{t-1}$
### 3.4 P-Value (MacKinnon 1994)
The ADF test statistic does **not** follow a standard distribution. MacKinnon (1994) provides polynomial approximations:
1. **Bounds check:**
- If $\text{ADF}_t > \tau_{\max}$ → $p = 1.0$
- If $\text{ADF}_t < \tau_{\min}$ → $p = 0.0$
2. **Polynomial evaluation:**
- If $\text{ADF}_t \leq \tau^*$ (left tail): use `smallp` coefficients (3 terms)
- If $\text{ADF}_t > \tau^*$ (right tail): use `largep` coefficients (4 terms)
3. **P-value:**
$$p = \Phi\left(\sum_{i=0}^{n} c_i \cdot \text{ADF}_t^i\right)$$
Where $\Phi$ is the standard normal CDF, implemented as:
$$\Phi(x) = \frac{1}{2} \operatorname{erfc}\!\left(-\frac{x}{\sqrt{2}}\right)$$
### 3.5 Critical Values (MacKinnon 2010)
$$\text{CV}_\alpha(n) = c_0 + \frac{c_1}{n} + \frac{c_2}{n^2} + \frac{c_3}{n^3}$$
| Regression | Level | $c_0$ | $c_1$ | $c_2$ | $c_3$ |
|-----------|-------|--------|--------|--------|--------|
| Constant | 1% | -3.43035 | -6.5393 | -16.786 | -79.433 |
| Constant | 5% | -2.86154 | -2.8903 | -4.234 | -40.040 |
| Constant | 10% | -2.56677 | -1.5384 | -2.809 | 0 |
**Wolfram-validated:** At $n = 100$: $\text{CV}_{1\%} = -3.43035 + \frac{-6.5393}{100} + \frac{-16.786}{10000} + \frac{-79.433}{1000000} = -3.4975$ ✓
### 3.6 Automatic Lag Selection
Default maximum lag: $p_{\max} = \lfloor 12 \cdot (n/100)^{1/4} \rfloor$
Selection via AIC (Akaike Information Criterion):
$$\text{AIC} = n \ln\!\left(\frac{\text{RSS}}{n}\right) + 2k$$
Iterate from $p = p_{\max}$ down to $p = 0$, select the lag that minimizes AIC.
---
## 4. Architecture & Design
### 4.1 Class Hierarchy
```
AbstractBase (lib/core/AbstractBase.cs)
└── Adf (lib/statistics/adf/Adf.cs)
```
### 4.2 Enum
```csharp
public enum AdfRegression : byte
{
NoConstant = 0, // "nc" — no deterministic terms
Constant = 1, // "c" — intercept only (DEFAULT)
ConstantAndTrend = 2 // "ct" — intercept + linear trend
}
```
### 4.3 Constructor
```csharp
public Adf(int period = 50, int maxLag = 0, AdfRegression regression = AdfRegression.Constant)
```
- `period` ≥ 20 (rolling window of raw prices)
- `maxLag` = 0 → auto via $\lfloor 12(n/100)^{1/4} \rfloor$; otherwise explicit lag count
- `regression` = model specification
### 4.4 State Management
| Field | Type | Purpose |
|-------|------|---------|
| `_buffer` | `RingBuffer` | Rolling window of `period` raw values |
| `_lastValidValue` | `double` | NaN substitution |
| `_p_lastValidValue` | `double` | Bar correction state |
Unlike running-sum indicators, ADF requires **full window recomputation** each update (similar to [`Hurst`](../lib/statistics/hurst/Hurst.cs:23)). No incremental O(1) shortcut is possible due to the matrix regression.
### 4.5 Internal Components
| Component | Method | Purpose |
|-----------|--------|---------|
| **OLS Solver** | `SolveCholesky()` | Cholesky decomposition of X'X, stackalloc for k ≤ 8 |
| **MacKinnon P-Value** | `MacKinnonP()` | Polynomial + `NormCdf()` |
| **Normal CDF** | `NormCdf()` | `0.5 * Math.Erfc(-x * InvSqrt2)` |
| **Polynomial Eval** | `PolyVal()` | Horner's method |
| **Lag Selection** | `SelectLag()` | AIC minimization |
| **ADF Core** | `ComputeAdf()` | Builds design matrix, runs OLS, returns (stat, pValue) |
---
## 5. Performance Profile
### 5.1 Operation Count — Single Value
| Operation | Count | Notes |
|-----------|-------|-------|
| Differences (SUB) | $n - 1$ | Δy computation |
| Matrix fill (MUL/ADD) | $m \times k$ | Design matrix X |
| X'X (MUL/ADD) | $m \times k^2$ | Gram matrix |
| Cholesky (MUL/ADD/SQRT) | $k^3 / 6$ | Decomposition |
| Forward/Back substitution | $k^2$ | Solve |
| RSS (MUL/ADD) | $m \times k$ | Residuals |
| erfc | 1 | P-value |
**Total:** $O(n \times k)$ where $n$ = period, $k$ = 2-8 regressors
### 5.2 Memory
- Stack: `stackalloc double[k*k + k*3 + n]` ≈ 1-2 KB for typical parameters
- Heap: `RingBuffer(period)` — one allocation at construction
---
## 6. File Deliverables
```
lib/statistics/adf/
├── Adf.cs # Main implementation
├── Adf.md # Indicator documentation
├── adf.pine # PineScript v6 reference
├── tests/
│ ├── Adf.Tests.cs # Unit tests (gold standard pattern)
│ └── Adf.Validation.Tests.cs # Cross-validation vs statsmodels
```
```
quantower/statistics/adf/
├── Adf.Quantower.cs # Quantower adapter
├── tests/
│ └── Adf.Quantower.Tests.cs # Adapter tests
```
### 6.1 Method Ordering (Gold Standard)
1. **Constructors**`Adf(period, maxLag, regression)`, event-source, series
2. **Streaming**`Update(TValue, bool)`, `Update(TSeries)`
3. **Static Batch**`Batch(TSeries, ...)`, `Batch(ReadOnlySpan, Span, ...)`
4. **Calculate Bridge**`Calculate(TSeries, ...)``(TSeries, Adf)`
5. **Lifecycle**`Reset()`, `Dispose(bool)`
---
## 7. Validation Strategy
### 7.1 Python Reference
```python
from statsmodels.tsa.stattools import adfuller
import numpy as np
# Stationary series (white noise)
np.random.seed(42)
series = np.random.randn(100)
result = adfuller(series, maxlag=1, regression='c')
# Expected: p-value ≈ 0.0 (very small)
# Non-stationary series (random walk)
rw = np.cumsum(np.random.randn(100))
result = adfuller(rw, maxlag=1, regression='c')
# Expected: p-value ≈ 0.5-1.0
```
### 7.2 Test Cases
| Test | Input | Expected |
|------|-------|----------|
| White noise | `randn(100)` seed=42 | p-value < 0.05 |
| Random walk | `cumsum(randn(100))` | p-value > 0.05 |
| Known ADF stat | stat=-3.5, regression="c" | p = `Φ(poly(coefs, -3.5))` |
| Edge: period=20 | Minimum valid period | Works without error |
| Edge: NaN input | Series with NaN | Uses last valid value |
| Edge: constant series | All same value | p-value = NaN or 1.0 |
### 7.3 Cross-validation Tolerance
- Against statsmodels: $\epsilon < 10^{-6}$ for p-value
- Against MacKinnon critical values: $\epsilon < 10^{-4}$
---
## 8. Risk Assessment
| Risk | Severity | Mitigation |
|------|----------|------------|
| Breaking changes | ✅ None | New indicator, no existing API modified |
| Ill-conditioned X'X | Medium | Cholesky with diagonal check; return NaN if rank-deficient |
| Numerical drift | Low | Full recomputation each bar (no running sums to drift) |
| Performance | Low | O(n×k) acceptable for statistical test; not a hot-path indicator |
| Python parity | Medium | Comprehensive validation tests against statsmodels |
---
## 9. Future Considerations
- **KPSS test** — complementary stationarity test (opposite null hypothesis)
- **Cointegration refactoring** — could internally delegate ADF computation to `Adf` class
- **Phillips-Perron test** — non-parametric alternative to ADF
- **Zivot-Andrews test** — structural break unit root test
---
## 10. References
1. Dickey, D.A. & Fuller, W.A. (1979). "Distribution of the Estimators for Autoregressive Time Series with a Unit Root." *JASA*, 74(366), 427-431.
2. Said, S.E. & Dickey, D.A. (1984). "Testing for Unit Roots in ARMA Models of Unknown Order." *Biometrika*, 71(3), 599-607.
3. MacKinnon, J.G. (1994). "Approximate Asymptotic Distribution Functions for Unit-Root and Cointegration Tests." *JBES*, 12(2), 167-176.
4. MacKinnon, J.G. (2010). "Critical Values for Cointegration Tests." Queen's Economics Working Paper No. 1227.
5. Hamilton, J.D. (1994). *Time Series Analysis*. Princeton University Press.