ARIMA Models for Time Series Forecasting in Trading

ARIMA time series forecasting is the baseline every more complex price model must beat, and the diagnostic that most efficiently tells you when a series is unforecastable. For professional quants the value of ARIMA is not that it predicts prices — it usually cannot — but that its structure (lag polynomials, invertibility, information criteria, residual whitening) is the grammar of linear time-series modeling, and its honest failure on liquid prices is itself a tradeable conclusion. This article covers the model in operator form, the estimation and identification that go wrong in practice, and where ARIMA actually earns its keep.

ARIMA in lag-operator form

Write L for the lag operator (L*yt = y{t-1}). An ARIMA(p, d, q) model is

phi(L) * (1 - L)^d * y_t = c + theta(L) * e_t

phi(L)   = 1 - phi_1*L - ... - phi_p*L^p        (AR polynomial)
theta(L) = 1 + theta_1*L + ... + theta_q*L^q    (MA polynomial)

This compact form makes the constraints explicit, and the constraints are where practitioners stumble:

  • Stationarity requires the roots of phi(L) = 0 to lie outside the unit

circle. An AR(1) with phi_1 = 0.99 is technically stationary but behaves like a near-unit-root process — long memory, slow reversion, treacherous inference.

  • Invertibility requires the roots of theta(L) = 0 outside the unit circle, so

the MA part has a convergent AR(infinity) representation. Without it the parameters are not identified — different theta give the same autocovariances.

  • Parameter redundancy. If phi(L) and theta(L) share a near-common root, the

model is over-parameterized and the optimizer wanders. This is why high-order ARMA fits are unstable and auto_arima can return nonsense.

The (1 - L)^d factor is the integration order — the number of differences that move the level into stationary space.

Setting d: test, do not guess

d is fixed by stationarity testing, not by minimizing fit. Over-differencing is a real cost: differencing an already-stationary series introduces a unit root in the MA polynomial (theta gets a root on the unit circle), which is non-invertible and inflates forecast variance. The discipline is the smallest d that achieves stationarity.

from statsmodels.tsa.stattools import adfuller, kpss

def choose_d(series, dmax=2, signif=0.05):
    x = series.dropna()
    for d in range(dmax + 1):
        adf_p = adfuller(x, autolag="AIC")[1]
        kpss_p = kpss(x, regression="c", nlags="auto")[1]
        if adf_p < signif and kpss_p > signif:   # both agree: stationary
            return d
        x = x.diff().dropna()
    return dmax

For liquid price series this returns d = 1 almost always, because price changes are close to stationary while levels are not. Returns themselves are typically d = 0.

Identification: ACF/PACF, and why information criteria mislead

After fixing d, identify p and q. The classical reading: the PACF cuts off at lag p for a pure AR(p); the ACF cuts off at lag q for a pure MA(q); both decaying implies mixed ARMA. In practice the patterns on differenced financial data are ambiguous — there is rarely a clean cutoff because there is little structure to find.

ACF behaviorPACF behaviorSuggested model
decays graduallycuts off after lag pAR(p)
cuts off after lag qdecays graduallyMA(q)
decaysdecaysmixed ARMA(p,q)
no significant lagsno significant lagswhite noise (d too high or random walk)

Automated selection minimizes AIC or BIC, but two cautions: AIC is not consistent (it over-selects order asymptotically), while BIC is consistent but can underfit in small samples. More importantly, in-sample IC is a weak proxy for out-of-sample forecast accuracy on near-random-walk data — a lower AIC frequently buys you nothing live. Use IC to shortlist, then validate out of sample.

A complete, honest workflow

The single most important element is the benchmark. A random walk's optimal mean-forecast of tomorrow is today's value; any model that cannot beat that is adding noise. Evaluate strictly out of sample and compare the RMSE, ideally with a Diebold-Mariano test so you know whether the difference is statistically real or luck.

import numpy as np
import pandas as pd
from statsmodels.tsa.arima.model import ARIMA

def evaluate_arima(prices, order=(1, 1, 1), test_size=60):
    train, test = prices.iloc[:-test_size], prices.iloc[-test_size:]

    # Walk-forward one-step forecasts (no look-ahead): refit-free re-anchoring
    history = list(train)
    preds = []
    for t in range(len(test)):
        res = ARIMA(history, order=order).fit()
        preds.append(res.forecast(steps=1)[0])
        history.append(test.iloc[t])          # append the realized value

    preds = np.array(preds)
    actual = test.values
    naive = np.array(history[-test_size-1:-1])   # yesterday's price

    rmse = np.sqrt(np.mean((preds - actual) ** 2))
    naive_rmse = np.sqrt(np.mean((naive - actual) ** 2))
    return dict(arima_rmse=rmse, naive_rmse=naive_rmse, skill=1 - rmse / naive_rmse)

The skill number — one minus the ratio of model RMSE to naive RMSE — is positive only when ARIMA beats the random walk. For liquid prices it hovers around zero or goes negative once you account for estimation. That is not a bug; it is the market telling you the conditional mean of price changes is approximately a martingale difference.

Residual diagnostics: the part people skip

A well-specified ARIMA leaves residuals that are white noise. Test it formally with Ljung-Box on the residuals (autocorrelation) and on squared residuals (heteroskedasticity / ARCH effects):

from statsmodels.stats.diagnostic import acorr_ljungbox

def residual_checks(res, lags=10):
    r = pd.Series(res.resid).dropna()
    lb = acorr_ljungbox(r, lags=[lags], return_df=True)["lb_pvalue"].iloc[0]
    arch = acorr_ljungbox(r**2, lags=[lags], return_df=True)["lb_pvalue"].iloc[0]
    return dict(resid_white=lb > 0.05, no_arch=arch > 0.05)

The recurring finding on returns: residuals pass the level Ljung-Box (no mean structure left) but fail the squared-residual test — there is leftover volatility clustering. That is the signal to bolt a GARCH model onto the ARIMA mean (the ARIMA-GARCH pairing), because the forecastable content of returns lives in the second moment, not the first.

Seasonality and exogenous drivers

Pure ARIMA models a series in terms of its own past. Two extensions matter for real trading data. SARIMA adds seasonal AR/MA/differencing terms at a seasonal lag s — relevant for intraday volume (daily and weekly cycles), electricity and commodity demand, and any series with calendar structure. Writing it (p,d,q)(P,D,Q)_s, the seasonal differencing (1 - L^s)^D removes a repeating pattern the same way ordinary differencing removes a trend; ignoring it leaves strong autocorrelation at the seasonal lag that contaminates p/q identification.

ARIMAX / regression with ARIMA errors lets you add exogenous predictors X while modeling the residual autocorrelation with an ARIMA structure. This is the right frame when you have a genuine driver — a lead-lag relationship, a macro release, a related instrument — and want the autocorrelation handled without contaminating the regression coefficients. The trap is treating a contemporaneous X as available when it is not: if Xt is only known after you must trade on yt, the model is using future information, and the apparent skill evaporates live. Exogenous regressors must be lagged to their true availability.

Forecast intervals, not just points

A point forecast on near-random-walk data is almost useless; the interval carries the information. ARIMA forecast variance grows with horizon, and for a d=1 model the interval widens roughly with sqrt(h) — the model is honestly saying it knows less the further out you look. Two cautions: the intervals assume the residuals are homoskedastic and Gaussian, both false for returns (volatility clusters, tails are fat), so nominal 95% intervals under-cover in turbulent periods. If you need calibrated intervals for risk, model the variance separately with GARCH and combine, or use empirical residual quantiles rather than the Gaussian formula.

Where ARIMA actually pays

The model is the wrong tool for raw liquid prices and the right tool for constructed, mean-reverting series:

  • Cointegrated spreads. The spread of a

cointegrated pair has genuine, exploitable autocorrelation; ARIMA (or its AR(1)/Ornstein-Uhlenbeck special case) models the reversion speed directly and the half-life maps to holding period.

  • Volatility and volume. Series with real persistence — realized

volatility, volume, order-flow imbalance — have ACF structure ARIMA captures cleanly.

  • As a baseline and a filter. ARIMA is the bar a

machine-learning model must clear out of sample; if your LSTM cannot beat ARIMA(1,1,1), it is not earning its complexity. And fitting ARIMA to a candidate series and finding insignificant coefficients is a cheap, rigorous way to confirm it is a random walk before you invest in modeling it.

Small-sample identification pitfalls

ARIMA identification is more fragile than the clean ACF/PACF story suggests, and the fragility is worst exactly where data is scarce. The confidence bands drawn on ACF/PACF plots are the +/- 1.96/sqrt(T) white-noise bounds, so with a short sample they are wide and a genuine lag-1 autocorrelation can sit inside them undetected — you under-fit. Conversely, on a long sample, multiple-testing across many lags guarantees some spuriously "significant" spikes — you over-fit. Neither plot is a substitute for out- of-sample validation.

Parameter estimates inherit this instability. Near-cancelling AR and MA roots make the likelihood surface flat, so two very different (p,q) specifications can fit almost identically and the optimizer's choice between them is essentially noise. The practical discipline is parsimony enforced by BIC (which penalizes parameters more aggressively than AIC and is consistent), confirmation that the roots are comfortably inside the unit circle, and a refusal to chase the last decimal of in-sample fit on data that is mostly a random walk. When two parsimonious models forecast equivalently out of sample, prefer the smaller one — it will be more stable when you refit on new data.

Honest limits

ARIMA is linear, single-regime, and targets the conditional mean. Each of those is a binding constraint on financial data: predictability is largely nonlinear and interactive, relationships shift across regimes, and the mean of liquid price changes is close to unforecastable while the variance is not. Multi-step forecasts collapse toward the unconditional mean within a few horizons, so it is useless for long-horizon price prediction. None of this makes it worthless — it makes it a precise instrument for a narrow job: a baseline, a diagnostic, and a clean mean model for series that actually mean-revert. Point it at spreads and volatility, always benchmark against the random walk with an out-of-sample, walk-forward test, check the residuals (including the squares), and treat a flat result as the model honestly reporting an efficient market rather than a failure to tune.

#ARIMA #time series #forecasting #stationarity #quant finance