QTQuant Terminal
D1-03D1·intermediate·~20 min

Stationarity and unit roots — when statistics on a time series mean anything

time-seriesstationarityunit-rootadf-testspurious-regression

▸ Pretest — guess, even if you don't know

You regress Stock A's price on Stock B's price over five years of daily data and get R-squared 0.85 with a tiny p-value. What have you learned?

Stationarity: the license to estimate

Every statistic you compute on a time series — a mean, a volatility, an autocorrelation, a regression beta — is an average over time. Averaging over time only estimates something meaningful if the thing being estimated holds still. That requirement has a name. A series is weakly stationary if:

  1. its mean is constant over time,
  2. its variance is constant (and finite) over time,
  3. the covariance between xtx_t and xtkx_{t-k} depends only on the lag kk, not on tt.

The trading question stationarity answers: is the past statistically like the future? If yes, history is a sample you can learn from. If no, your carefully estimated parameters describe a regime that no longer exists, and a model fit on the series produces garbage forecasts with confident-looking standard errors.

Prices are not stationary; returns roughly are

From D1-02, a random walk's variance is tσ2t\sigma^2 — it grows without bound, violating condition 2, and any drift violates condition 1. Prices, being approximately random walks, are non-stationary. Their first difference, though — returns — has roughly constant mean near zero, roughly constant unconditional variance, and autocorrelations that depend only on lag. Returns are approximately weakly stationary.

This is the general recipe: a series whose differences are stationary is called integrated of order one, and differencing (np.diff, or log-price to log-return) is the standard transformation that turns it into something you may legitimately compute statistics on.

Two honest caveats. Volatility clustering (D1-01) means the conditional variance of returns moves around, so returns are not iid — weak stationarity is a workable approximation, not a law. And markets have regimes: a mean and volatility estimated in 2017 described 2017. Stationarity is always an assumption you argue for, never a fact you observe.

Spurious regression: the classic trap

Why exactly does regressing price on price blow up? Both series wander (that is what random walks do), and over any given window two wandering series will drift in some loosely shared way — up-up, down-down, or up-down — so the regression finds a strong "relationship" in-sample. Worse, the regression residuals are themselves non-stationary, which violates the assumptions behind OLS standard errors (A2-06): the t-statistics and p-values are simply wrong, and R-squared stays high no matter how much data you add. Granger and Newbold's simulated independent walks produced t>2|t| > 2 about 75% of the time.

The trap wears many trading costumes: "my indicator is 0.9 correlated with the S&P," "these two coins move together" — computed on levels, all meaningless. The fix is mechanical: difference first. Regress returns on returns, changes on changes. (There is one important exception — sometimes a combination of two non-stationary prices is genuinely stationary, which is called cointegration and is the respectable foundation of pairs trading. D1-04 builds the model for that spread.)

Unit roots and the ADF test

Write the suspicion formally with next lesson's model: xt=ϕxt1+εtx_t = \phi x_{t-1} + \varepsilon_t. If ϕ=1\phi = 1, this is exactly a random walk — the series is said to have a unit root and is non-stationary. If ϕ<1|\phi| < 1, shocks decay and the series is stationary. The Augmented Dickey–Fuller (ADF) test asks which world you are in. Read it carefully, because the direction trips people up:

In practice it is one line (shown for orientation — the exercise runtime here is numpy-only, so run this locally):

from statsmodels.tsa.stattools import adfuller
stat, pvalue, *_ = adfuller(x)   # H0: unit root (non-stationary)

Typical output: SPY prices, p around 0.6 — cannot reject the unit root. SPY returns, p far below 0.01 — stationary. One warning: the ADF test has low power near the boundary, so it often cannot tell ϕ=0.98\phi = 0.98 (slowly mean-reverting, tradeable) from ϕ=1\phi = 1 (random walk, not tradeable). Failing to reject is absence of evidence, not proof of a unit root — and that ambiguity is exactly where many pairs trades go to die.

Try it

A crude but instructive stationarity check: a series whose mean drifts fails the most basic requirement.

▮ EXERCISE · d1-03-ex1

Implement two functions. difference(x) returns the first difference of x, using np.diff. is_mean_stable(x) is a crude stationarity check: split x into a first half of len(x) // 2 points and a second half with the rest, and return True when the absolute gap between the two half means is at most 0.5 times the sample standard deviation of the whole series, np.std with ddof=1. Real work uses the ADF test; this builds the intuition that a stationary series has no drifting mean.

⧉ Review card
State the three conditions of weak stationarity and why they matter.
Constant mean, constant finite variance, and autocovariance between x_t and x_t-k depending only on the lag k. They are the license to estimate: any statistic averaged over time only means something if the quantity holds still over time.
⧉ Review card
Why are prices non-stationary while returns are roughly stationary?
Prices are approximately random walks: their variance grows like t times sigma squared, unbounded, and drift moves the mean. Returns — the first difference — have roughly constant mean and unconditional variance. Caveat: volatility clustering means returns are stationary only as a working approximation.
⧉ Review card
What is spurious regression and what is the rule that prevents it?
Regressing one non-stationary series on another produces high R-squared and significant t-statistics even for independent random walks (Granger-Newbold 1974), because the residuals are non-stationary and OLS standard errors break. Rule: difference first — regress returns on returns, never prices on prices (unless testing cointegration).
⧉ Review card
What are the null hypothesis and reading of the ADF test?
Null: the series has a unit root, i.e. is non-stationary like a random walk. Small p-value rejects the null — evidence of stationarity. Large p-value means you failed to reject, not that a unit root is proven; the test has low power near phi close to 1.

Teach it

Teach spurious regression to an imaginary junior analyst who just proudly showed you a levels-on-levels regression with R-squared 0.9. In your own words: why do two independent random walks look related, what exactly breaks in the OLS standard errors, and what one-line rule should they follow from now on? Two minutes, out loud.

Predict before the next lesson

Next: autoregressive models (D1-04). Before then, predict: you want a model of a series that gets pulled back toward a long-run mean instead of wandering forever. How would you write next value as a function of the current value plus noise — and what coefficient value would turn your model back into a random walk?

◈ Calibration check

Could you define weak stationarity, explain to a colleague why regressing prices on prices is invalid, and state exactly what the ADF null hypothesis is?

1 = guessing · 5 = could teach it

⏻ End of lesson

Mark it read to book its 4 review cards into your deck.

Sources & further reading