Quant500 Academy

2. Fitting an AR in Python: order with AIC and BIC

Level 9: Time Series - ARIMA and GARCH

The statsmodels library provides the autoregressive model ready to use. The modern class is AutoReg (the old AR and statsmodels.tsa.arima_model.ARIMA are deprecated; if you see code using them, update it).

We start from the S&P 500 log returns and make a split that respects the temporal ordering. No train_test_split with shuffle=True: in time series, shuffling is cheating.

import numpy as np
import yfinance as yf
from statsmodels.tsa.api import AutoReg

df = yf.download('^GSPC', start='2016-01-01', end='2022-12-30')['Close']
Re = np.log(df).diff().dropna()      # log returns

X = Re.values
train_size = int(len(X) * 0.66)
train, test = X[1:train_size], X[train_size:]

Fitting an AR with 30 lags and reading the summary:

max_lag = 30

model = AutoReg(train, lags=max_lag, trend='n').fit()
print(model.summary())

aic_value = model.aic
print('AIC value:', aic_value)

Two details that matter more than they look:

Before choosing the order it is worth looking at the residuals of the fitted model:

_ = tsplot(model.resid, lags=max_lag)   # series, ACF, PACF, QQ and probplot

If the residuals look like discrete white noise, the model has captured the linear structure. If autocorrelation remains, the order is insufficient.

Order selection by grid search. Instead of fixing p by eye, we sweep the possible orders and keep the one with the lowest AIC:

max_lag = 30
best_aic = np.inf
best_order = 0
best_model = None

for lag in range(1, max_lag + 1):
    model = AutoReg(train, lags=lag, trend='n').fit()
    if model.aic < best_aic:
        best_aic = model.aic
        best_order = lag
        best_model = model

print('Best AR order:', best_order)
print('Best AIC:', best_aic)
print(best_model.summary())

Note that the loop refits the model from scratch at every iteration: each AR(lag) is a different regression, not a nested model being extended.

The same scheme works with BIC (the Bayesian criterion) by replacing result.aic with result.bic. The practical difference: the BIC penalizes extra parameters more harshly because its penalty grows with the logarithm of the sample size. With long financial series, the BIC almost always picks a smaller order than the AIC. In trading that is usually a virtue: fewer parameters means less overfitting to the noise in the sample and a more robust strategy when the market regime changes.

An honest warning: choosing the order by AIC on the training set and then measuring the error on the test set is correct. Choosing it by looking at the test error and then presenting that same error as the backtest result is not: that is selection bias, and it is one of the most common ways of inflating results without realizing it.

To go deeper into this lesson

The rest of Quant500

What you are learning, applied to the index every day:

The model portfolio, weekly in your inbox

Every Saturday, its week in one minute: how it went, the best and the worst, and which insiders bought with their own money. Plus, the day the model is rebuilt, which stocks come in and which go out. A simulation, not advice. At most one email a week, no ads, no account; one-click unsubscribe. Made by one person: Pol Gracia.

A model portfolio with simulated results, not a personalized recommendation. Your email is used for this only: privacy.

Behind the Academy and the rest of Quant500 there is one person: Pol Gracia Serrahima.