Skip to content
Preparing Python in your browser (about 6 MB, first visit only)

ADF Test in Python: Are Prices or Returns Stationary?

Lesson 12 of 16 · Data Manipulation

intermediate Python 15 minADF testaugmented Dickey-Fullerstationarity

The ADF Test: Prices, Returns and the Unit Root

Scene: the data chapter of your dissertation. Your methodology section says "all series are stationary", and your examiner asks how you know. The honest answer is a test, and in finance the default is the augmented Dickey-Fuller (ADF) test. It takes one line in statsmodels. The trouble is reading it: half the students who run it read the p-value backwards. In this lesson you run it on prices, returns and a mean-reverting spread, and you learn to read it the right way round.

What it is: A series is stationary when its mean, variance and autocorrelations do not drift over time, so the past is a fair guide to the future. A unit root is the classic way to fail: a random walk, where each shock is permanent and the series wanders with no level to return to. Stock prices behave like this; their returns do not.

Why it matters

Regress one random walk on another, independent one, and you routinely get a "significant" slope and a high R-squared that mean nothing. Granger and Newbold called this spurious regression in 1974. Testing for a unit root first, and working with returns (differences) when you find one, is what keeps a thesis out of that trap.

The test

text
Δy_t = a + γ·y_(t-1)
       + δ_1·Δy_(t-1) + ... + δ_p·Δy_(t-p) + e_t

H0: γ = 0   unit root: NON-stationary
H1: γ < 0   pulled back to a level: stationary

ADF statistic = γ̂ / std err(γ̂)

The extra lagged differences (the "augmented" part) soak up short-term autocorrelation; statsmodels chooses how many by AIC. The statistic looks like a t-statistic, but under the null it does not follow a t distribution: its critical values are more negative. With a constant and a long sample, the 5% critical value is about -2.86, not -1.65.

Reading the p-value: the null is the unit root

p-valueDecisionPlain English
below 0.05reject H0stationary: no unit root
0.05 or abovecannot reject H0non-stationary: treat it as having a unit root

A large p-value never proves a unit root; it means the data cannot rule one out. That asymmetry is why careful work also runs the KPSS test, whose null is the opposite (stationarity).

What this lesson's data give

Three synthetic series of 750 daily observations: a price that is a random walk with drift (100 rising to about 154), its log returns, and a spread that reverts to zero with persistence 0.9.

seriesADF statp-value5% critverdict
price-0.530.886-2.87non-stationary
returns-27.250.000-2.87stationary
spread-6.600.000-2.87stationary

The spread is the instructive one: it is slow-moving and a chart of it looks lazy, yet the test rejects a unit root firmly. Stationary does not mean flat; it means tethered.

AIC picks 0 extra lags for the price, so its ADF statistic is plain Dickey-Fuller: regress the daily change in price on yesterday's price. The code does that by hand: γ̂ = -0.00131 with t = -0.53, exactly the ADF statistic.

Your Task

The starter runs the test on all three series and prints a verdict for each. The numbers are right, but YOUR TURN marks the verdict rule, which reads the p-value backwards: it calls the random-walk price "stationary".

Predict before you run: which of the three series will flip verdict once the rule is fixed, and which will not? Then correct the comparison in verdict(p).

Related terms in the glossary

Further reading

Where to go deeper. Free means a full, legal copy is online.