mirror of
https://github.com/mihakralj/QuanTAlib.git
synced 2026-08-03 03:47:42 +00:00
130 lines
6.3 KiB
Plaintext
130 lines
6.3 KiB
Plaintext
|
|
// This Pine Script™ code is subject to the terms of the Mozilla Public License 2.0
|
|||
|
|
// https://mozilla.org/MPL/2.0/
|
|||
|
|
// © QuanTAlib
|
|||
|
|
|
|||
|
|
//@version=6
|
|||
|
|
indicator("ADF - Augmented Dickey-Fuller Test", shorttitle="ADF", overlay=false, precision=4)
|
|||
|
|
|
|||
|
|
// ─── Inputs ────────────────────────────────────────────────────────────
|
|||
|
|
int period = input.int(50, "Period", minval=20, maxval=500)
|
|||
|
|
int maxLag = input.int(0, "Max Lag (0=auto)", minval=0, maxval=10)
|
|||
|
|
string regModel = input.string("Constant", "Regression", options=["NoConstant", "Constant", "ConstantAndTrend"])
|
|||
|
|
|
|||
|
|
// ─── Helper: OLS solve 2×2 (constant + gamma, no lags for simplicity) ──
|
|||
|
|
// Full ADF with Cholesky is too complex for Pine; this is a reference only.
|
|||
|
|
// For production use, the C# implementation is authoritative.
|
|||
|
|
|
|||
|
|
adf_pvalue(float[] data, int p, int mLag, string reg) =>
|
|||
|
|
int n = array.size(data)
|
|||
|
|
if n < 20
|
|||
|
|
1.0
|
|||
|
|
else
|
|||
|
|
// Compute first differences
|
|||
|
|
int nd = n - 1
|
|||
|
|
float[] dy = array.new_float(nd, 0.0)
|
|||
|
|
for i = 0 to nd - 1
|
|||
|
|
array.set(dy, i, array.get(data, i + 1) - array.get(data, i))
|
|||
|
|
|
|||
|
|
// Simple ADF: regress dy on y_{t-1} (constant model, no lags)
|
|||
|
|
// X = [1, y_{t-1}], Y = dy_t
|
|||
|
|
float sx = 0.0, sy = 0.0, sxx = 0.0, sxy = 0.0, syy = 0.0
|
|||
|
|
int startIdx = math.max(mLag, 0)
|
|||
|
|
int nObs = nd - startIdx
|
|||
|
|
if nObs < 3
|
|||
|
|
1.0
|
|||
|
|
else
|
|||
|
|
for t = startIdx to nd - 1
|
|||
|
|
float x = array.get(data, t) // y_{t-1} in level
|
|||
|
|
float y = array.get(dy, t)
|
|||
|
|
sx += x
|
|||
|
|
sy += y
|
|||
|
|
sxx += x * x
|
|||
|
|
sxy += x * y
|
|||
|
|
syy += y * y
|
|||
|
|
|
|||
|
|
// OLS for constant model: [alpha, gamma]
|
|||
|
|
float xbar = sx / nObs
|
|||
|
|
float ybar = sy / nObs
|
|||
|
|
float denom = sxx - sx * sx / nObs
|
|||
|
|
if math.abs(denom) < 1e-15
|
|||
|
|
1.0
|
|||
|
|
else
|
|||
|
|
float gamma = (sxy - sx * sy / nObs) / denom
|
|||
|
|
float alpha = ybar - gamma * xbar
|
|||
|
|
|
|||
|
|
// RSS
|
|||
|
|
float rss = 0.0
|
|||
|
|
for t = startIdx to nd - 1
|
|||
|
|
float x = array.get(data, t)
|
|||
|
|
float y = array.get(dy, t)
|
|||
|
|
float resid = y - alpha - gamma * x
|
|||
|
|
rss += resid * resid
|
|||
|
|
|
|||
|
|
float s2 = rss / (nObs - 2)
|
|||
|
|
if s2 <= 0
|
|||
|
|
1.0
|
|||
|
|
else
|
|||
|
|
float se = math.sqrt(s2 / denom)
|
|||
|
|
if se <= 1e-15
|
|||
|
|
1.0
|
|||
|
|
else
|
|||
|
|
float tStat = gamma / se
|
|||
|
|
|
|||
|
|
// MacKinnon (1994) p-value for N=1, regression-dependent
|
|||
|
|
float tauStar = reg == "NoConstant" ? -1.04 : reg == "ConstantAndTrend" ? -2.89 : -1.61
|
|||
|
|
float tauMin = reg == "NoConstant" ? -19.04 : reg == "ConstantAndTrend" ? -16.18 : -18.83
|
|||
|
|
float tauMax = reg == "NoConstant" ? 1e308 : reg == "ConstantAndTrend" ? 0.70 : 2.74
|
|||
|
|
|
|||
|
|
if tStat < tauMin
|
|||
|
|
0.0
|
|||
|
|
else if tStat > tauMax
|
|||
|
|
1.0
|
|||
|
|
else
|
|||
|
|
float poly = 0.0
|
|||
|
|
if tStat <= tauStar
|
|||
|
|
// Small-p coefficients (regression-dependent)
|
|||
|
|
if reg == "NoConstant"
|
|||
|
|
poly := 0.6344 + 1.2378 * tStat + 0.032496 * tStat * tStat
|
|||
|
|
else if reg == "ConstantAndTrend"
|
|||
|
|
poly := 3.2512 + 1.6047 * tStat + 0.049588 * tStat * tStat
|
|||
|
|
else
|
|||
|
|
poly := 2.1659 + 1.4412 * tStat + 0.038269 * tStat * tStat
|
|||
|
|
else
|
|||
|
|
// Large-p coefficients (regression-dependent)
|
|||
|
|
float t2 = tStat * tStat
|
|||
|
|
float t3 = t2 * tStat
|
|||
|
|
if reg == "NoConstant"
|
|||
|
|
poly := 0.4797 + 0.93557 * tStat - 0.06999 * t2 + 0.033066 * t3
|
|||
|
|
else if reg == "ConstantAndTrend"
|
|||
|
|
poly := 2.5261 + 0.61654 * tStat - 0.37956 * t2 - 0.060285 * t3
|
|||
|
|
else
|
|||
|
|
poly := 1.7339 + 0.93202 * tStat - 0.12745 * t2 - 0.010368 * t3
|
|||
|
|
|
|||
|
|
// Normal CDF via A&S 7.1.26 erf → Φ(x) = 0.5·(1 + erf(x/√2))
|
|||
|
|
int sgn = poly < 0 ? -1 : 1
|
|||
|
|
float z = math.abs(poly) * 0.70710678118654752
|
|||
|
|
float tt = 1.0 / (1.0 + 0.3275911 * z)
|
|||
|
|
float tt2 = tt * tt
|
|||
|
|
float tt3 = tt2 * tt
|
|||
|
|
float tt4 = tt3 * tt
|
|||
|
|
float tt5 = tt4 * tt
|
|||
|
|
float erfcZ = (0.254829592 * tt - 0.284496736 * tt2 + 1.421413741 * tt3 - 1.453152027 * tt4 + 1.061405429 * tt5) * math.exp(-z * z)
|
|||
|
|
float erfZ = 1.0 - erfcZ
|
|||
|
|
float result = 0.5 * (1.0 + sgn * erfZ)
|
|||
|
|
result
|
|||
|
|
|
|||
|
|
// ─── Main ──────────────────────────────────────────────────────────────
|
|||
|
|
float[] window = array.new_float(0)
|
|||
|
|
for i = 0 to period - 1
|
|||
|
|
array.push(window, close[period - 1 - i])
|
|||
|
|
|
|||
|
|
float pval = adf_pvalue(window, period, maxLag, regModel)
|
|||
|
|
|
|||
|
|
// ─── Plot ──────────────────────────────────────────────────────────────
|
|||
|
|
plot(pval, "ADF p-value", color=color.blue, linewidth=2)
|
|||
|
|
hline(0.01, "1% significance", color=color.red, linestyle=hline.style_dotted)
|
|||
|
|
hline(0.05, "5% significance", color=color.orange, linestyle=hline.style_dashed)
|
|||
|
|
hline(0.10, "10% significance", color=color.gray, linestyle=hline.style_dotted)
|
|||
|
|
|
|||
|
|
bgcolor(pval < 0.05 ? color.new(color.green, 90) : na)
|