Lesson 2.4: Time series: stationarity and autocorrelation
Learn stationarity, autocorrelation, the AR(1) model and half-life, the Dickey-Fuller unit root test, and why returns look random but volatility does not.
By the end of this lesson you will be able to say what it means for a time series to be stationary, compute and read an autocorrelation function with its significance band, describe the AR(1) model and work out its stability and half-life, run the logic of a Dickey–Fuller test for a unit root and know how weak it is when the series is slow, and explain why returns show almost no autocorrelation while squared returns show a lot. You will show that a series with a coefficient of 0.9 has a half-life of 6.6 days and a long-run standard deviation 2.3 times its daily shock, that a standard unit root test rejects a coefficient of 0.95 only 45 per cent of the time with 250 observations, and that overlapping five-day returns show a lag-one autocorrelation near 0.8 that is purely a product of how they were built. You should have read Lessons 2.2 and 2.3, whose volatility clustering and spreads we now examine properly. The mathematics is averages, products, a regression slope and a power. This course is education about how markets and research work, not investment advice.
In Lesson 2.3 we kept using a phrase without explaining it: a spread that is "stable" and returns to a level. In Lesson 1.3 we assumed that daily returns are roughly independent from one day to the next, so that variance grows with the number of days. Both statements are claims about the time structure of a series: how today's value relates to yesterday's. Time-series analysis supplies the vocabulary and the tests. Most research errors in this area share a cause: applying a method that works for stable data to data that is not.
Time series and stationarity
A time series is a list of values in time order, such as daily closing prices. Order matters, which is what separates it from the ordinary data of Lesson 2.1 and 2.2: today's value is not an independent draw. It depends on the past, and that dependence is what we try to describe, and sometimes to trade.
A series is stationary (in the usual, "weak" sense) if its statistical behaviour does not change over time:
1. The mean is constant.
2. The variance is constant.
3. The correlation between two values depends only on how far apart they are, not on when they occur.
A stationary series fluctuates around a fixed level with a fixed spread, and what you learn from one stretch of it applies to another. That is why stationarity matters: averages, correlations, regressions and forecasts only mean something if the data are stationary, because otherwise the quantity you estimate is not a fixed number. Lesson 2.3's spurious regression came from trending, non-stationary series.
Series · Stationary? · CommentThe price of a share · No · It wanders, with no fixed level. Its variance grows with time
The logarithm of the price · No · Same, on a log scale
The daily return or log return · Roughly yes · Fixed mean near zero. The variance changes over time, but is broadly stable over long samples
The change in a price · Roughly yes · Same as the return, without scaling
The spread of a cointegrated pair · Yes, by definition · It reverts to a level
A yield or an interest rate · Debatable · It reverts slowly, and over a few years it looks like a trend
The standard cure for a non-stationary price is to difference it: take the change, or the log return, which turns a wandering series into a stable one. That is why quants model returns, not prices. The price is the running total of the returns, so nothing is lost.
Autocorrelation: how a series relates to its own past
The autocorrelation at lag k, written ρₖ, is the correlation between the series and itself k steps earlier:
ρₖ = the average of (xₜ − mean) × (xₜ₋ₖ − mean) ÷ the variance
It lies between −1 and 1. A positive ρ₁ means a high value tends to be followed by a high value (momentum or persistence), and a negative ρ₁ means a high value tends to be followed by a low one (reversal). The plot of ρₖ against k is the autocorrelation function, or ACF.
Worked example 1. Take twelve observations of a series: 0.5, 0.9, 0.3, −0.4, −0.8, −0.2, 0.4, 0.7, 0.1, −0.5, −0.7 and 0.2. The mean is 0.04. Multiplying each deviation from the mean by the previous one and adding gives 1.517, and the sum of squared deviations is 3.409, so ρ₁ = 1.517 ÷ 3.409 = 0.44. At lag 2 the value is −0.38. The series seems to roll in waves: high for a few days, then low. But is 0.44 real?
Is an autocorrelation significant?
If a series is pure noise, with no dependence, the sample autocorrelation at any lag is itself random, with a standard deviation of about 1 ÷ √n. A rough 95 per cent band is therefore ±1.96 ÷ √n: values inside the band are consistent with no autocorrelation. For the twelve points above, 1 ÷ √12 = 0.29 and the band is ±0.57. The value 0.44 is inside it, only 1.5 standard errors from zero, so a sample of twelve cannot tell a genuine wave from chance. With 2,500 daily observations the band shrinks to ±0.04, and even a small autocorrelation of 0.05 becomes visible. You need a long sample to find small effects, a point we made about means in Lesson 1.3. When a chart shows many lags, remember that one in twenty will fall outside the 95 per cent band by chance alone.
Random walks and white noise
Two reference series help you recognise what you are looking at.
White noise is a series of independent draws with fixed mean and variance. All its autocorrelations are zero. It is stationary and unpredictable.
A random walk is the running total of white noise: xₜ = xₜ₋₁ + εₜ. Each value is the previous one plus a fresh shock. It is the standard model for a price, and it is not stationary.
The ACF distinguishes the two. For a simulated random walk of 500 steps, the autocorrelations of the levels are 0.98 at lag 1, 0.89 at lag 5, 0.78 at lag 10 and 0.57 at lag 20: they are very high and decay very slowly, the signature of a series with no fixed level. The autocorrelations of the changes are 0.05, 0.04 and 0.00 at lags 1, 5 and 10: they are close to zero, as they are for white noise. A slow-decaying ACF in the levels is the first warning that a series needs differencing before you use it.
The AR(1) model and the half-life
The simplest model of a series that comes back to a level is the first-order autoregressive model, AR(1):
xₜ = φ xₜ₋₁ + εₜ
where φ ("phi") is a coefficient and ε is white noise with standard deviation σ. Each value is a fraction φ of the previous one plus a new shock. Several results follow.
Stationarity needs |φ| < 1. If φ is above one the series explodes. If φ = 1 it is a random walk. If φ is between −1 and 1 the effect of a shock fades.
The autocorrelation at lag k is φᵏ. It decays geometrically: for φ = 0.9 it is 0.90, 0.81, 0.73, 0.66, 0.59 at lags 1 to 5.
The long-run variance is σ² ÷ (1 − φ²), which is bigger than the shock's variance because shocks accumulate before they fade.
The half-life of a shock, the time for its effect to halve, is ln 0.5 ÷ ln φ.
Worked example 2. Take σ = 1. The table shows what φ does.
φ · Long-run standard deviation · Autocorrelation at lag 5 · Half-life (periods)0.50 · 1.15 · 0.03 · 1.0
0.90 · 2.29 · 0.59 · 6.6
0.95 · 3.20 · 0.77 · 13.5
0.99 · 7.09 · 0.95 · 69.0
As φ approaches one, the series becomes slower, wanders further and takes much longer to forget a shock. The half-life of 6.6 for φ = 0.9 is the same number as the spread with a daily closing rate of 10 per cent in Lesson 2.3, because b = φ − 1. A trader who sees a stationary series with a half-life of 69 periods has something that is stationary on paper and almost a random walk in practice.
Move φ below and see how the memory, the long-run spread and the half-life respond.
The unit root and the Dickey–Fuller test
The line between a stable series and a random walk is φ = 1, called a unit root. We often need to know which side we are on: it decides whether a spread can be traded as mean-reverting and whether a regression is valid. The Dickey–Fuller test, introduced by David Dickey and Wayne Fuller in 1979, does this. Subtract xₜ₋₁ from both sides of the AR(1) equation:
Δxₜ = γ xₜ₋₁ + εₜ, where γ = φ − 1
This is a regression of the change in the series on its previous level. If γ = 0 there is a unit root, and if γ is negative the series reverts. The test is: estimate γ, form its t-ratio (γ divided by its standard error) and compare it with a critical value. The hypothesis tested, the null, is the dull one: a unit root, a random walk.
There is a catch that trips up beginners. Under the null the t-ratio does not follow the usual t distribution, because the regressor is itself non-stationary. It follows a different distribution, tabulated by Dickey and Fuller, which is shifted to the left. With a constant in the regression and a large sample, the 5 per cent critical value is about −2.86, not the −1.65 or −1.96 of an ordinary one-sided or two-sided test. A t-ratio of −2.0, which an ordinary table would call significant, is not significant here.
Worked example 3. We simulated two series of 250 observations with the same random draws. The first is an AR(1) with φ = 0.9. The second is a random walk, φ = 1. The Dickey–Fuller regression gives a t-ratio of −3.25 for the first and −2.13 for the second. Compare with −2.86: the first rejects the unit root, correctly identifying a stationary series, and the second does not, correctly. In practice the test is run in an "augmented" form, which adds lagged changes as regressors to soak up autocorrelation in the noise, and a statistics package does it in one line.
The test is weak when the series is slow
The test has a serious limitation. When φ is close to one, the series looks so much like a random walk that the test cannot tell the difference without a lot of data. We simulated 4,000 series of 250 observations for each value of φ and ran the test at 5 per cent each time:
True φ · Share of tests that reject a unit root0.80 · 100%
0.90 · 97%
0.95 · 45%
0.98 · 12%
1.00 (a random walk) · 5% (the false alarm rate)
A series with φ = 0.95, a half-life of 13.5 periods and a perfectly good mean-reversion, passes as stationary less than half of the time with a year of daily data. At φ = 0.98 the test almost never rejects. So "we could not reject a unit root" does not prove a series is a random walk, and "we rejected it" in a short sample deserves doubt as well, since you may have tested a lot of series. A spread must also be judged on its economics: why should it come back?
Returns look random, but their size does not
Now apply the ACF to what markets actually do. Across liquid markets, the autocorrelation of daily returns is very close to zero: knowing yesterday's return tells you almost nothing about today's. If it were large, traders would profit and remove it. But there is a second series hidden in the same data, the squared return, which measures the size of the move whatever its direction. Squared returns are strongly autocorrelated, which is the volatility clustering of Lesson 2.2: a big move, up or down, tends to be followed by another big move.
We simulated 5,000 daily returns from a simple volatility model with clustering, which gives a kurtosis of 3.6, and computed both ACFs. The standard error band is ±0.028.
Lag · 1 · 2 · 3 · 4 · 5 · 6 · 7 · 8 · 9 · 10Returns · 0.00 · −0.02 · 0.01 · 0.03 · 0.00 · 0.00 · 0.01 · −0.01 · −0.02 · −0.01
Squared returns · 0.19 · 0.16 · 0.17 · 0.15 · 0.15 · 0.13 · 0.11 · 0.15 · 0.08 · 0.08
The returns show nothing outside the band, apart from a borderline value at lag 4. The squared returns are far outside it at every lag, and decay only slowly. The direction is unpredictable, but the size is predictable, and that is what volatility models, option prices and risk systems use.
Does a small autocorrelation pay?
Even an autocorrelation that is statistically significant may not be tradeable. Suppose daily returns have ρ₁ = 0.05 and a standard deviation of 1 per cent. After a day of +2 per cent, the expected next-day return is 0.05 × 2 = 0.10 per cent, or 10 basis points. If the round-trip cost of trading, from Lesson 1.4, is 5 to 10 basis points, the edge is gone. To detect ρ₁ = 0.05 at all you need n with 1 ÷ √n well below 0.05, about 1,600 days or more, as the band of ±1.96 ÷ √n is 0.05 when n = 1,537. A significant effect in a long sample may be too small to trade, and a tradeable effect in a short sample may be noise. Both facts are why this part of the course stresses costs and sample size.
A related tool is the variance ratio. If daily returns are independent, the variance of q-day returns is q times the daily variance, the square-root rule of Lesson 1.3. The ratio VR(q) of the actual q-day variance to q times the daily one is 1 for a random walk, above 1 when returns trend (positive autocorrelation) and below 1 when they revert. With ρ₁ = 0.1, for example, VR(2) = 1 + ρ₁ = 1.1. A ratio far from 1 is evidence against a random walk.
Traps when you work with time series
Overlapping returns create fake autocorrelation. Computing a five-day return every day makes consecutive values share four of five days. We built five-day sums from 2,000 independent daily returns, with no dependence at all, and the autocorrelations at lags 1 to 4 were 0.79, 0.61, 0.42 and 0.24, close to the theoretical 0.8, 0.6, 0.4 and 0.2. Nothing is predictable here: it is arithmetic. Use non-overlapping returns, or correct the standard errors.
Differencing too much. Differencing a series that is already stationary adds artificial negative autocorrelation.
Looking at the ACF of prices. A slowly decaying ACF of a price level says nothing about momentum. It is the mark of a trend.
Reading too much into one lag. Out of 20 lags, one will cross a 95 per cent band by chance.
Using a short sample. The significance band is ±1.96 ÷ √n, and the Dickey–Fuller test has low power.
Ignoring structural breaks. A series that was stationary before a regulatory change may not be afterwards, and tests can mistake a break for a unit root.
The code below computes the ACF, simulates the AR(1) and its half-life, runs a Dickey–Fuller test by hand and shows the overlapping-returns illusion.
python
import numpy as np
def acf(z, k):
z = z - z.mean()
return (z[k:] z[:-k]).sum() / (z z).sum()
worked example 1: twelve observations
s = np.array([0.5, 0.9, 0.3, -0.4, -0.8, -0.2, 0.4, 0.7, 0.1, -0.5, -0.7, 0.2])
print("rho1", round(acf(s, 1), 2), "rho2", round(acf(s, 2), 2), "1.96/sqrt(n)", round(1.96 / np.sqrt(len(s)), 2))
AR(1) facts
for phi in (0.5, 0.9, 0.95, 0.99):
print(phi, "long-run sd", round(1 / np.sqrt(1 - phi 2), 2), "half-life", round(np.log(0.5) / np.log(phi), 1))
Dickey-Fuller regression of the change on the previous level, with a constant
def dft(x):
dx, xl = np.diff(x), x[:-1]
X = np.columnstack([np.ones(len(xl)), xl])
beta = np.linalg.lstsq(X, dx, rcond=None)[0]
e = dx - X @ beta
cov = (e @ e / (len(dx) - 2)) np.linalg.inv(X.T @ X)
return beta[1] / np.sqrt(cov[1, 1])
rng = np.random.defaultrng(5)
ar = np.zeros(250)
for t in range(1, 250):
ar[t] = 0.9 ar[t - 1] + rng.standardnormal()
rw = np.cumsum(np.random.defaultrng(5).standardnormal(250))
print("AR(0.9) t-ratio", round(dft(ar), 2), "random walk t-ratio", round(dft(rw), 2), "5% cut-off about -2.86")
overlapping five-day returns built from independent daily returns
r = np.random.defaultrng(8).normal(0, 1, 2000)
five = np.convolve(r, np.ones(5), "valid")
print("daily acf lag 1", round(acf(r, 1), 3))
print("overlapping five-day acf, lags 1-6", [round(acf(five, k), 2) for k in range(1, 7)])
optional: the augmented test in statsmodels
try:
from statsmodels.tsa.stattools import adfuller
print("adfuller on the AR series:", adfuller(ar)[:2])
except ImportError:
print("install statsmodels for adfuller")
Common mistakes
Modelling prices as if they were stationary. Difference them, or use returns.
Using ordinary t-tables for a unit root test. The critical value is about −2.86, not −1.96.
Reading "not rejected" as "a random walk". The test has little power when the series is slow, as the table of rejection rates shows.
Trusting a single autocorrelation outside the band. One lag in twenty will be outside by chance.
Using overlapping returns without correcting. The autocorrelation they show is built in.
Confusing predictable size with predictable direction. Squared returns are predictable. Returns hardly are.
Ignoring costs when an autocorrelation is found. An autocorrelation of 0.05 on 1 per cent volatility is worth about 10 basis points on a 2 per cent day.
Treating a long half-life as mean reversion you can trade. A half-life of 60 periods is a random walk for any practical purpose.
Key takeaways
A stationary series has a constant mean, variance and lag structure. Averages, correlations and regressions only mean something for stationary data. Prices are not stationary, returns are roughly so.
The autocorrelation at lag k is the correlation of a series with its own value k steps earlier. Pure noise gives values within ±1.96 ÷ √n: with 12 points that is ±0.57, with 2,500 it is ±0.04.
A random walk has an ACF in levels near 1 that decays slowly (0.98 at lag 1, 0.57 at lag 20 in our simulation), and near zero for its changes.
The AR(1) model xₜ = φ xₜ₋₁ + ε is stationary when |φ| < 1, has autocorrelation φᵏ at lag k, a long-run variance σ² ÷ (1 − φ²) and a half-life ln 0.5 ÷ ln φ: 6.6 periods when φ = 0.9, 13.5 when φ = 0.95, 69 when φ = 0.99.
The Dickey–Fuller test regresses the change on the previous level and compares the t-ratio with a critical value near −2.86. With 250 observations it detects φ = 0.9 in 97 per cent of cases, φ = 0.95 in 45 per cent and φ = 0.98 in 12 per cent.
Daily returns have almost no autocorrelation, but squared returns do (0.19 at lag 1 in our simulation): direction is hard to predict, but the size of moves is not.
An autocorrelation of 0.05 on a day of +2 per cent predicts +0.10 per cent, about the cost of trading it. Overlapping returns create autocorrelation that is only arithmetic: 0.79 at lag 1 for five-day sums.
Check yourself
1. A series of 400 daily values has an autocorrelation of 0.08 at lag 1. Is it significant at the 95 per cent level?
The band is ±1.96 ÷ √400 = ±0.098. The value 0.08 is inside it, so it cannot be told from noise.
2. An AR(1) series has φ = 0.8 and shocks with a standard deviation of 2. What is its long-run standard deviation and its half-life?
Long-run standard deviation = 2 ÷ √(1 − 0.64) = 2 ÷ 0.6 = 3.33. Half-life = ln 0.5 ÷ ln 0.8 = 3.1 periods.
3. A Dickey–Fuller regression on a spread gives γ = −0.06 with a standard error of 0.03. Does it reject a unit root at 5 per cent?
The t-ratio is −2.0. The critical value is about −2.86, so no. A plain t-test would have called it significant, which is the trap.
4. You compute 10-day returns every day from a series of independent daily returns. What autocorrelation do you expect at lag 1, and does it mean anything?
About 0.9, because consecutive 10-day returns share nine of ten days. It means nothing: it is created by the overlap.
5. Daily returns of an index have an autocorrelation of −0.03 at lag 1, statistically significant in 20 years of data. Why might it still not be tradeable?
After a fall of 2 per cent the expected rebound is 0.03 × 2 = 0.06 per cent, 6 basis points, which is about the same as a round-trip cost, so the edge disappears after costs.
6. A trader says: "I could not reject a unit root in this spread, so it is a random walk and cannot be traded." What is wrong?
The test has little power for slow series. A spread with φ = 0.95 is rejected only 45 per cent of the time at 250 observations, so failing to reject proves little. The trader should look at the half-life, the economics and a longer sample.
Try this
Set aside forty-five minutes. Use at least five years of daily closing prices for an index.
1. Plot the price, the log return and the squared log return. Say which look stationary.
2. Compute the autocorrelation of the price at lags 1, 5, 10 and 20, and of the daily return at lags 1 to 10. Compute the significance band for your sample size and mark which values are outside it.
3. Compute the autocorrelation of the squared returns at lags 1 to 10 and compare with the returns. Describe the difference in one sentence.
4. Run a Dickey–Fuller test, by hand or with a library, on the price and on the return. Compare the t-ratios with −2.86.
5. Compute five-day overlapping returns, and their autocorrelation at lags 1 to 6. Then use non-overlapping five-day returns, and compare.
6. Take a spread from Lesson 2.3, or invent one, estimate φ by regressing the change on the previous level, and compute the half-life. Use the AR(1) simulator to see what it implies.
7. Write a short note: which series are stationary, whether there is any predictable structure in returns and in their size, and whether any autocorrelation you found would survive costs of 5 basis points.
A good result is a page with the ACFs and their bands, the test statistics against their cut-offs, and a plain statement of what is predictable, what is not and what is too small to trade. This completes the second module. The next module turns from the mathematics to the practice of research: where market data comes from, how it is cleaned and adjusted, and the biases it hides.
Read more
Time Series Analysis by James Hamilton is the standard graduate text, and Analysis of Financial Time Series by Ruey Tsay applies the tools to returns and volatility. David Dickey and Wayne Fuller's 1979 paper "Distribution of the Estimators for Autoregressive Time Series with a Unit Root" in the Journal of the American Statistical Association is the source of the test. Andrew Lo and Craig MacKinlay's 1988 paper "Stock Market Prices Do Not Follow Random Walks" introduced the variance-ratio test and is a good example of the method at work. For the statsmodels functions mentioned above, see the library's documentation for adfuller and acf.
Tags: Quant Trader Time series Stationarity Autocorrelation Dickey-Fuller