Mean reversion strategies (Bollinger fades, RSI extremes, pairs, range trading) all rest on one assumption: that prices tend to come back to an average. Trend-following rests on the opposite one. The Augmented Dickey-Fuller test is the classic statistical tool to check which of the two the data supports.
This indicator by RicardoSantos runs the ADF test on a moving window and plots the test statistic against its critical value in a separate panel. When the statistic drops below the critical value, the recent sample behaves like a stationary, mean-reverting series. When it stays above, the series looks like a random walk or a trend, and fading moves is a weaker bet.
A series with a unit root has no anchor: each shock stays in the price, and the series wanders. A stationary series keeps pulling back toward its mean. The Dickey-Fuller regression measures that pull:
The test statistic is that coefficient divided by its standard error. It does not follow a normal distribution, so it is compared with the critical values published by MacKinnon (2010) for a model with a constant and no trend, adjusted for the number of observations. Below the critical value, the unit root hypothesis is rejected at the chosen confidence level.
On every bar the indicator takes the last Length values, builds the regression, solves it by least squares and computes the statistic and the critical value.
A single green cross is weak evidence: the test is run on every bar, so some crossings happen by chance. Persistent stretches under the critical value are more meaningful than isolated ones.
//---------------------------------------------------------------
// PRC_ADF Test
// version = 0
// 27.10.2026
// Iván González @ www.prorealcode.com
// Author: RicardoSantos
// Sharing ProRealTime knowledge
//--------------------------------------------------------------------//
DEFPARAM DRAWONLASTBARONLY = true
// ===================== INPUTS =====================
// Source: the indicator's "Apply to" setting (close by default)
lookback = 100 // Length: number of points in the moving sample (min 2)
nLag = 0 // Maximum lag: lagged differences added to the regression (min 0)
confLevel = 90 // Confidence level of the critical value: 90, 95 or 99
showInfo = 1 // 1 = show the info box, 0 = hide it
src = customclose
// ===================== SETTINGS CHECK =====================
lkb = max(2, round(lookback))
nLg = max(0, round(nLag))
lagOk = 1
IF nLg >= lkb / 2 - 2 THEN
lagOk = 0
ENDIF
nObs = lkb - nLg - 1
nCol = 2 + nLg
nReg = 1 + nLg
once tauADF = undefined
once crit = undefined
adfOk = 0
// ===================== ADF REGRESSION =====================
// dy(i) = b0 * y(i+1) + sum(bn * dy(i+n)) + c, solved by least squares.
// The intercept is removed by centring every column, so only the slopes
// go through the normal equations.
IF lagOk = 1 AND barindex >= lkb - 1 THEN
FOR i = 0 TO lkb - 1 DO
$dat[i] = src[i]
NEXT
// means of the dependent variable and of each regressor
meanY = 0
FOR c = 0 TO nReg - 1 DO
$mX[c] = 0
NEXT
FOR i = 0 TO nObs - 1 DO
meanY = meanY + $dat[i] - $dat[i + 1]
$mX[0] = $mX[0] + $dat[i + 1]
IF nLg > 0 THEN
FOR c = 1 TO nLg DO
$mX[c] = $mX[c] + $dat[i + c] - $dat[i + c + 1]
NEXT
ENDIF
NEXT
meanY = meanY / nObs
FOR c = 0 TO nReg - 1 DO
$mX[c] = $mX[c] / nObs
NEXT
// normal equations on centred columns: A * b = v (augmented matrix in $aug)
nAug = nReg + 1
FOR c = 0 TO nReg * nAug - 1 DO
$aug[c] = 0
NEXT
FOR i = 0 TO nObs - 1 DO
$rw[0] = $dat[i + 1] - $mX[0]
IF nLg > 0 THEN
FOR c = 1 TO nLg DO
$rw[c] = $dat[i + c] - $dat[i + c + 1] - $mX[c]
NEXT
ENDIF
yC = $dat[i] - $dat[i + 1] - meanY
FOR r = 0 TO nReg - 1 DO
rv = $rw[r]
FOR c = 0 TO nReg - 1 DO
$aug[r * nAug + c] = $aug[r * nAug + c] + rv * $rw[c]
NEXT
$aug[r * nAug + nReg] = $aug[r * nAug + nReg] + rv * yC
NEXT
NEXT
// Gauss-Jordan elimination with partial pivoting
solveOk = 1
FOR p = 0 TO nReg - 1 DO
best = p
bestV = abs($aug[p * nAug + p])
IF p < nReg - 1 THEN
FOR r = p + 1 TO nReg - 1 DO
IF abs($aug[r * nAug + p]) > bestV THEN
best = r
bestV = abs($aug[r * nAug + p])
ENDIF
NEXT
ENDIF
IF bestV <= 0.0000000001 THEN
solveOk = 0
BREAK
ENDIF
IF best <> p THEN
FOR c = 0 TO nReg DO
tmpV = $aug[p * nAug + c]
$aug[p * nAug + c] = $aug[best * nAug + c]
$aug[best * nAug + c] = tmpV
NEXT
ENDIF
pv = $aug[p * nAug + p]
FOR c = 0 TO nReg DO
$aug[p * nAug + c] = $aug[p * nAug + c] / pv
NEXT
FOR r = 0 TO nReg - 1 DO
IF r <> p THEN
fct = $aug[r * nAug + p]
IF fct <> 0 THEN
FOR c = 0 TO nReg DO
$aug[r * nAug + c] = $aug[r * nAug + c] - fct * $aug[p * nAug + c]
NEXT
ENDIF
ENDIF
NEXT
NEXT
IF solveOk = 1 THEN
// slopes and intercept
icpt = meanY
FOR c = 0 TO nReg - 1 DO
$cf[c] = $aug[c * nAug + nReg]
icpt = icpt - $cf[c] * $mX[c]
NEXT
// mean square error of the regression and spread of the predictor
sum1 = 0
sum2 = 0
FOR i = 0 TO nObs - 1 DO
yHat = icpt + $cf[0] * $dat[i + 1]
IF nLg > 0 THEN
FOR c = 1 TO nLg DO
yHat = yHat + $cf[c] * ($dat[i + c] - $dat[i + c + 1])
NEXT
ENDIF
resd = $dat[i] - $dat[i + 1] - yHat
sum1 = sum1 + resd * resd
dxV = $dat[i + 1] - $mX[0]
sum2 = sum2 + dxV * dxV
NEXT
sum1 = sum1 / (nObs - nCol)
IF sum2 > 0 AND sum1 >= 0 THEN
stdErr = sqrt(sum1 / sum2)
IF stdErr > 0 THEN
tauADF = $cf[0] / stdErr
adfOk = 1
ENDIF
ENDIF
ENDIF
// MacKinnon (2010) critical value, model with constant and no trend
nSq = nObs * nObs
nCu = nObs * nObs * nObs
IF confLevel = 99 THEN
crit = -3.43035 - 6.5393 / nObs - 16.786 / nSq - 79.433 / nCu
ELSIF confLevel = 95 THEN
crit = -2.86154 - 2.8903 / nObs - 4.234 / nSq - 40.040 / nCu
ELSE
crit = -2.56677 - 1.5384 / nObs - 2.809 / nSq
ENDIF
ENDIF
// ===================== COLOURS =====================
// below the critical value (mean reverting): green
// above: white to yellow as |statistic| approaches |critical value|
tauR = 255
tauG = 255
tauB = 255
tauA = 0
IF adfOk = 1 THEN
IF tauADF < crit THEN
tauR = 122
tauG = 245
tauB = 77
tauA = 255
ELSIF tauADF > crit THEN
gT = min(1, max(0, abs(tauADF) / abs(crit)))
tauR = 255 - 10 * gT
tauG = 255 - 32 * gT
tauB = 255 - 178 * gT
tauA = 255
ENDIF
ENDIF
BACKGROUNDCOLOR(100, 65, 107)
// ===================== INFO BOX =====================
IF islastbarupdate AND showInfo = 1 THEN
rowH = 18
chW = 6.2
nameW = 20 * chW + 14
valW = 9 * chW + 14
tW = nameW + valW
tH = 3 * rowH
tL = 12
tT = 12 + tH
cxN = tL + nameW / 2
cxV = tL + nameW + valW / 2
IF adfOk = 1 AND tauADF < crit THEN
frR = 122
frG = 245
frB = 77
ELSE
frR = 200
frG = 77
frB = 245
ENDIF
DRAWRECTANGLE(tL, tT, tL + tW, tT - tH) ANCHOR(BOTTOMLEFT, XSHIFT, YSHIFT) COLOURED(frR, frG, frB, 255) FILLCOLOR(250, 237, 245, 255)
y0 = tT - rowH * 0.5
y1 = tT - rowH * 1.5
y2 = tT - rowH * 2.5
DRAWTEXT("Test Statistic", cxN, y0, monospaced, bold, 10) ANCHOR(BOTTOMLEFT, XSHIFT, YSHIFT) COLOURED(0, 0, 0, 255)
DRAWTEXT("#confLevel#% Critical Value", cxN, y1, monospaced, bold, 10) ANCHOR(BOTTOMLEFT, XSHIFT, YSHIFT) COLOURED(0, 0, 0, 255)
DRAWTEXT("Mean Reverting?", cxN, y2, monospaced, bold, 10) ANCHOR(BOTTOMLEFT, XSHIFT, YSHIFT) COLOURED(0, 0, 0, 255)
IF lagOk = 0 THEN
DRAWTEXT("Lag too large", cxV, y0, monospaced, bold, 10) ANCHOR(BOTTOMLEFT, XSHIFT, YSHIFT) COLOURED(200, 0, 0, 255)
ELSIF adfOk = 1 THEN
tauTxt = round(tauADF * 10000) / 10000
critTxt = round(crit * 10000) / 10000
DRAWTEXT("#tauTxt#", cxV, y0, monospaced, bold, 10) ANCHOR(BOTTOMLEFT, XSHIFT, YSHIFT) COLOURED(0, 0, 0, 255)
DRAWTEXT("#critTxt#", cxV, y1, monospaced, bold, 10) ANCHOR(BOTTOMLEFT, XSHIFT, YSHIFT) COLOURED(0, 0, 0, 255)
IF tauADF < crit THEN
DRAWTEXT("Yes", cxV, y2, monospaced, bold, 10) ANCHOR(BOTTOMLEFT, XSHIFT, YSHIFT) COLOURED(0, 0, 0, 255)
ELSE
DRAWTEXT("No", cxV, y2, monospaced, bold, 10) ANCHOR(BOTTOMLEFT, XSHIFT, YSHIFT) COLOURED(0, 0, 0, 255)
ENDIF
ELSE
DRAWTEXT("NaN", cxV, y0, monospaced, bold, 10) ANCHOR(BOTTOMLEFT, XSHIFT, YSHIFT) COLOURED(0, 0, 0, 255)
DRAWTEXT("NaN", cxV, y1, monospaced, bold, 10) ANCHOR(BOTTOMLEFT, XSHIFT, YSHIFT) COLOURED(0, 0, 0, 255)
DRAWTEXT("No", cxV, y2, monospaced, bold, 10) ANCHOR(BOTTOMLEFT, XSHIFT, YSHIFT) COLOURED(0, 0, 0, 255)
ENDIF
ENDIF
RETURN 0 COLOURED(147, 149, 151) AS "Zero", crit COLOURED(200, 77, 245) AS "Critical value", tauADF COLOURED(tauR, tauG, tauB, tauA) STYLE(point, 2) AS "Test statistic"