Part II: Classical Forecasting and Time Series Analysis
Chapter 5: Univariate Forecasting Models

Moving Average Models

"Give me a shock and I will repeat it for exactly $q$ steps, no more. After that I forget completely and return to my quiet average, pretending nothing ever happened. People mistake me for the smoother who lives next door; we are not the same."

A Moving Average Still Processing Last Week's Shocks
A cartoon forecaster stands where a single firm line under its feet fans forward into a widening gray cone of uncertainty that dissolves into fog far ahead, picturing how a forecast stays confident one step out and grows into a band of maybe many steps out.
Figure 5.1.1: A forecast is sure of its next step and honest about its tenth: the uncertainty cone widens with every horizon you ask it to reach.
Big Picture

The phrase "moving average" names two unrelated objects, and conflating them is one of the most common errors in time series practice. One is a deterministic smoother that averages neighboring observations to suppress noise; the other is the MA($q$) stochastic model that builds the present from a short window of unobserved random shocks. This section is about the second. The MA($q$) model is the algebraic mirror of the autoregressive model of Section 5.2: where an AR process has infinite memory through a finite recursion, an MA process has exactly $q$ steps of memory through a finite sum of shocks. That finite memory makes the model unconditionally stationary for any parameters, gives it an autocorrelation function that cuts off cleanly after lag $q$, and makes its forecasts revert to the mean the moment the horizon exceeds $q$. The price is that the shocks are never observed, so fitting an MA model is a genuinely nonlinear problem, unlike the linear regression that fits an AR model. We disambiguate the two senses, build the MA($q$) process and its always-stationary property, derive invertibility and the infinite-AR representation it unlocks, work through conditional-sum-of-squares and maximum-likelihood estimation, and close with a simulate-recover-fit-forecast example written first from scratch and then in three lines of statsmodels.

Chapter 3 gave us the language of stationarity and the correlogram; Section 3.3 in particular showed that a moving-average process leaves a sharp cut in its autocorrelation function and a gradual decay in its partial autocorrelation, the exact mirror of the autoregressive fingerprint. This section turns that diagnostic observation into a working model. We assume the weak-stationarity machinery of Section 3.2, the autocovariance notation $\gamma(k)$ and autocorrelation $\rho(k)$ of Section 3.3, and the unified symbol table of Appendix A, where the innovation sequence $\varepsilon_t$ and the lag operator $B$ are defined. The MA model is also the first half of the ARMA synthesis that the rest of this chapter assembles, and through the lag-operator algebra it is the classical ancestor of the linear-attention forecasters of Chapter 13, a thread we pick up at the end.

1. Two Senses of "Moving Average" Beginner

Before any mathematics, we must separate two ideas that share a name and almost nothing else. The first is the moving-average smoother, a deterministic filter you already met among the convolutional smoothers of Chapter 4. Given an observed series $x_1, \dots, x_n$, the simple moving average of window $w$ replaces each point by the mean of its neighbors,

$$s_t \;=\; \frac{1}{w}\sum_{j=0}^{w-1} x_{t-j},$$

and a weighted moving average attaches weights $a_j$ to those neighbors, $s_t = \sum_j a_j x_{t-j}$ with $\sum_j a_j = 1$. This is a transformation applied to data you can see: it averages observations, its purpose is to suppress high-frequency noise and expose trend, and it has no probabilistic content whatsoever. It is a low-pass filter, nothing more.

The second idea, the subject of this section, is the moving-average model MA($q$), a stochastic process that constructs the present value as a weighted sum of the last $q$ unobserved random shocks plus the current shock. The two objects differ on every axis that matters, and the comparison table below is the one to internalize before reading further, because nearly every confusion about MA models dissolves once the smoother and the model are kept apart.

The Two Senses of "Moving Average"
AspectMoving-average smoother (filter)MA($q$) model (process)
What is averagedObserved values $x_{t-j}$Unobserved shocks $\varepsilon_{t-j}$
PurposeDenoise, reveal trend (descriptive)Generate / forecast a series (probabilistic)
Inputs known?Yes, the data itselfNo, shocks are latent and inferred
Weights constrained?Usually sum to 1Free $\theta_j$; only invertibility constrains them
FittingNo fitting; choose a windowNonlinear estimation (CSS / MLE)
Home in this bookChapter 4 (filtering)This section

The collision of names is historical: both average a sliding window, so both earned the adjective "moving." But the smoother averages what you observe to clean it, while the model averages what you cannot observe to explain what you do. As the comparison table records, the smoother needs no fitting (you pick a window), whereas the model demands the nonlinear estimation of subsection four precisely because its inputs are hidden. Keep the table's middle column in Chapter 4 and its right column here, and the rest of this section follows cleanly.

Key Insight: The Model Averages What You Cannot See

The single fact that separates the MA model from the MA smoother, and from the AR model of Section 5.2, is that its regressors are latent. An AR($p$) model regresses $X_t$ on $X_{t-1}, \dots, X_{t-p}$, all of which are columns in your data, so least squares fits it in closed form. An MA($q$) model regresses $X_t$ on $\varepsilon_{t-1}, \dots, \varepsilon_{t-q}$, none of which you ever observe; they must be reconstructed from the data and the parameters at the same time. That one difference, observed versus latent regressors, is the source of everything distinctive about MA models: the nonlinear likelihood, the invertibility condition, and the reason the forecasts snap back to the mean. Hold onto it.

2. The MA($q$) Model and Its Always-Stationary Property Beginner

Let $\{\varepsilon_t\}$ be a white-noise innovation sequence: mean zero, constant variance $\sigma^2$, and uncorrelated across time, $\operatorname{Cov}(\varepsilon_t, \varepsilon_s) = 0$ for $t \ne s$. The moving-average model of order $q$ writes the observed series as the current shock plus a fixed linear combination of the previous $q$ shocks, around a level $\mu$,

$$X_t \;=\; \mu \;+\; \varepsilon_t \;+\; \theta_1 \varepsilon_{t-1} \;+\; \theta_2 \varepsilon_{t-2} \;+\; \cdots \;+\; \theta_q \varepsilon_{t-q}.$$

Using the lag operator $B$ (defined by $B^k \varepsilon_t = \varepsilon_{t-k}$ in Appendix A) this compresses to $X_t = \mu + \theta(B)\varepsilon_t$, where $\theta(B) = 1 + \theta_1 B + \cdots + \theta_q B^q$ is the moving-average polynomial. The coefficient on $\varepsilon_t$ is fixed at one; any common scale is absorbed into $\sigma^2$, and $\mu$ is the process mean because every innovation has mean zero. The defining feature is finite memory: $X_t$ contains $\varepsilon_{t-q}$ but not $\varepsilon_{t-q-1}$, so a shock that arrives today influences the series for exactly $q$ steps and then vanishes from it completely. This is the literal meaning of the epigraph, pictured in Figure 5.1.2.

A cartoon character whose calm expression is a blended average of a few recent shock-bubbles floating behind its head while older shocks fade away, dramatizing how a moving-average model carries only a short finite memory of recent random shocks.
Figure 5.1.2: A moving-average process wears the blended mood of its last few shocks and nothing older: its memory is finite, which is exactly why it is always stationary.

The first structural reward of finite memory is that an MA($q$) process is stationary for any choice of the parameters $\theta_1, \dots, \theta_q$, with no constraints whatsoever. This is the sharpest contrast with the AR model, whose stationarity requires its characteristic roots to lie outside the unit circle. The reason is immediate: a finite sum of stationary, uncorrelated terms is itself stationary, term by term. To see it, compute the moments directly. The mean is constant,

$$\mathbb{E}[X_t] \;=\; \mu + \sum_{j=1}^{q}\theta_j\,\mathbb{E}[\varepsilon_{t-j}] \;=\; \mu,$$

and the variance, using $\theta_0 = 1$ and the fact that distinct innovations are uncorrelated so all cross terms drop, is finite and time-invariant,

$$\gamma(0) \;=\; \operatorname{Var}(X_t) \;=\; \sigma^2 \sum_{j=0}^{q} \theta_j^2.$$

The autocovariance at lag $k$ keeps only the products of innovations that the two windows share. Writing $X_t - \mu = \sum_{j=0}^q \theta_j \varepsilon_{t-j}$ and $X_{t+k} - \mu = \sum_{i=0}^q \theta_i \varepsilon_{t+k-i}$, a shared innovation requires $t - j = t + k - i$, that is $i = j + k$, which is possible only when $0 \le k \le q$. Hence

$$\gamma(k) \;=\; \begin{cases} \sigma^2 \displaystyle\sum_{j=0}^{q-k} \theta_j\,\theta_{j+k}, & 0 \le k \le q,\\[2mm] 0, & k > q. \end{cases}$$

Dividing by $\gamma(0)$ gives the autocorrelation $\rho(k) = \gamma(k)/\gamma(0)$, which is exactly zero for every lag beyond $q$. This is the celebrated ACF cutoff: the autocorrelation function of an MA($q$) process is nonzero up to lag $q$ and then drops to zero and stays there, the precise mirror of the gradual PACF decay catalogued in Section 3.3. The cutoff is what lets you read $q$ straight off a correlogram: the order of an MA model is the last lag at which the sample ACF pierces the $\pm 1.96/\sqrt{n}$ band.

Numeric Example: The Lag-1 Autocorrelation of an MA(1)

For the MA(1) model $X_t = \mu + \varepsilon_t + \theta_1 \varepsilon_{t-1}$ the formulas collapse to $\gamma(0) = \sigma^2(1 + \theta_1^2)$, $\gamma(1) = \sigma^2 \theta_1$, and $\gamma(k) = 0$ for $k \ge 2$. The lag-1 autocorrelation is therefore

$$\rho(1) \;=\; \frac{\theta_1}{1 + \theta_1^2}.$$

With $\theta_1 = 0.8$ we get $\rho(1) = 0.8/1.64 \approx 0.488$, and every higher lag is zero: a single tall spike, then silence. Two facts fall out that matter later. First, $|\rho(1)|$ is bounded by $1/2$ for any MA(1), because $\theta_1/(1+\theta_1^2) \le 1/2$ with equality at $\theta_1 = 1$; an MA(1) simply cannot produce a lag-1 correlation above one half, a useful sanity check on identification. Second, $\theta_1 = 0.8$ and $\theta_1 = 1/0.8 = 1.25$ give the same $\rho(1) = 0.488$, since swapping $\theta_1 \leftrightarrow 1/\theta_1$ leaves $\theta_1/(1+\theta_1^2)$ unchanged. The autocorrelation cannot tell the two apart, which is exactly the ambiguity that invertibility resolves in the next subsection.

3. Invertibility and the Infinite-AR Representation Intermediate

The numeric example exposed a problem: two different MA(1) models, $\theta_1$ and $1/\theta_1$, produce identical autocorrelation structure and therefore identical second-order behavior. Since a Gaussian stationary process is fully described by its mean and autocovariance, these two parameterizations are observationally indistinguishable from data. We need a convention that picks one of each such pair, and the right convention is invertibility. An MA($q$) process is invertible when the roots of its characteristic polynomial

$$\theta(z) \;=\; 1 + \theta_1 z + \theta_2 z^2 + \cdots + \theta_q z^q$$

all lie strictly outside the unit circle, $|z| > 1$. For the MA(1) the single root is $z = -1/\theta_1$, which lies outside the unit circle exactly when $|\theta_1| < 1$. Of the pair $\{0.8, 1.25\}$ this selects $\theta_1 = 0.8$, and in general it selects the unique parameter set whose roots are all outside the circle. Invertibility is to MA models what stationarity-of-the-roots is to AR models: a convention that resolves identifiability and guarantees a well-behaved representation.

Why "invertible"? Because the condition is precisely what lets us solve the model for the unobserved shock $\varepsilon_t$ in terms of the observed past, turning the MA model inside out into an infinite autoregression. Formally, when all roots lie outside the unit circle the polynomial $\theta(B)$ has a convergent inverse $\theta(B)^{-1} = \pi(B) = 1 - \pi_1 B - \pi_2 B^2 - \cdots$, and applying it to $X_t - \mu = \theta(B)\varepsilon_t$ yields

$$\varepsilon_t \;=\; \pi(B)(X_t - \mu) \;=\; (X_t - \mu) - \sum_{j=1}^{\infty} \pi_j (X_{t-j} - \mu),$$

which rearranges into the infinite-AR representation $X_t = \mu + \sum_{j=1}^{\infty} \pi_j (X_{t-j} - \mu) + \varepsilon_t$. An MA($q$) process is thus an AR($\infty$) process in disguise; this is the precise sense in which the ACF cutoff of the MA mirrors the PACF tail, since the AR($\infty$) form gives every lag a small direct weight. For the MA(1) the inverse expands geometrically, $\pi(B) = (1 + \theta_1 B)^{-1} = 1 - \theta_1 B + \theta_1^2 B^2 - \cdots$, so $\pi_j = -(-\theta_1)^j$. This series converges if and only if $|\theta_1| < 1$, which is exactly the invertibility condition: the non-invertible twin $\theta_1 = 1.25$ would make the $\pi_j$ explode, and the model could not be written as a sensible function of its own past.

Key Insight: Invertibility Is What Makes Forecasting Possible

Invertibility is not bookkeeping; it is the condition under which an MA model can forecast at all. To predict $X_{t+1}$ we need the recent innovations $\varepsilon_t, \varepsilon_{t-1}, \dots$, but innovations are latent. The invertible representation $\varepsilon_t = (X_t - \mu) - \sum_j \pi_j (X_{t-j} - \mu)$ reconstructs them from the observed past with weights $\pi_j$ that decay geometrically, so distant history is gently forgotten and the recovered shocks are stable. In the non-invertible parameterization the same reconstruction would put exploding weight on the remote past, making the inferred innovations meaningless. So when fitting software silently returns the invertible root, it is not being fussy: it is handing you the one representation in which the hidden shocks, and therefore the forecasts, are recoverable from data. Estimators are conventionally restricted to the invertible region for this reason.

Fun Fact: The Same Duality, Read Backwards

Section 3.3 noted that an AR process is "finite in past values, infinite in past shocks," while an MA process is "finite in past shocks, infinite in past values." Invertibility is that sentence made rigorous: it is the exact condition under which the "infinite in past values" half actually converges. Stationarity does the symmetric job for AR models, ensuring the "infinite in past shocks" expansion converges. The two conditions are mirror images through the lag operator, which is why a stationary AR is automatically invertible-by-construction in reverse, and an invertible MA is automatically stationary (it always was). One algebraic duality, two famous conditions, both saying: keep the roots outside the circle and the infinite expansion behaves.

4. Estimation and Forecasting Advanced

Because the innovations are latent, an MA model cannot be fit by ordinary least squares. The standard route is conditional sum of squares (CSS), which turns the invertible representation of subsection three into a recursive reconstruction of the shocks. Fix candidate parameters $(\mu, \theta_1, \dots, \theta_q)$, set the unobservable pre-sample innovations to their expectation zero ($\varepsilon_0 = \varepsilon_{-1} = \cdots = 0$, the "conditional" assumption), and then run the model forward solved for $\varepsilon_t$,

$$\hat\varepsilon_t \;=\; (X_t - \mu) - \theta_1 \hat\varepsilon_{t-1} - \theta_2 \hat\varepsilon_{t-2} - \cdots - \theta_q \hat\varepsilon_{t-q}, \qquad t = 1, \dots, n.$$

Each reconstructed $\hat\varepsilon_t$ depends on the parameters through every earlier $\hat\varepsilon$, so the residuals are a nonlinear function of $\theta$. The CSS estimator minimizes their sum of squares, $S(\theta) = \sum_{t=1}^n \hat\varepsilon_t^2$, over the invertible region, using a numerical optimizer (Gauss-Newton, L-BFGS, or Nelder-Mead) rather than a closed-form normal equation. Under Gaussian innovations CSS is a close approximation to full maximum likelihood: the exact Gaussian log-likelihood is

$$\ell(\theta, \sigma^2) \;=\; -\frac{n}{2}\ln(2\pi\sigma^2) \;-\; \frac{1}{2}\ln|\mathbf{R}(\theta)| \;-\; \frac{1}{2\sigma^2}(\mathbf{x}-\mu\mathbf{1})^\top \mathbf{R}(\theta)^{-1}(\mathbf{x}-\mu\mathbf{1}),$$

where $\mathbf{R}(\theta)$ is the correlation matrix built from the MA autocovariances of subsection two. Exact MLE evaluates this via the Kalman filter (the state-space recursion of Chapter 7) for an $O(n)$ likelihood; CSS drops the $\ln|\mathbf{R}|$ determinant term and the pre-sample correction, which is negligible for large $n$. The practical message is that MA fitting is iterative and can land in local minima, so good starting values (often from the method-of-moments inversion of the $\rho(k)$ formulas) and the invertibility constraint both matter.

Forecasting an invertible MA($q$) is where finite memory shows its hand most vividly. The minimum-mean-squared-error forecast is the conditional expectation $\hat X_{t}(h) = \mathbb{E}[X_{t+h} \mid X_t, X_{t-1}, \dots]$. Future innovations have expectation zero, and innovations up to time $t$ are recoverable by the invertible recursion, so

$$\hat X_t(h) \;=\; \mu + \sum_{j=h}^{q} \theta_j\,\hat\varepsilon_{t+h-j}, \qquad \hat X_t(h) = \mu \ \text{ for } h > q.$$

The sum runs only over innovations dated at or before $t$ (those with index $t+h-j \le t$, i.e. $j \ge h$); every future shock drops out. The consequence is stark: for horizons within the memory window ($h \le q$) the forecast still carries information from the last observed shocks, but the instant the horizon exceeds $q$ every term vanishes and the forecast collapses to the unconditional mean $\mu$. An MA($q$) model has nothing to say beyond $q$ steps; it reverts to the mean and stays there. The forecast-error variance behaves symmetrically, growing from $\sigma^2$ at $h=1$ up to the full process variance $\gamma(0) = \sigma^2 \sum_{j=0}^q \theta_j^2$ for all $h > q$, since beyond the memory horizon the prediction error is just the entire process fluctuating around its mean.

Numeric Example: Forecasting an MA(2) Off a Cliff

Take the invertible MA(2) model $X_t = 10 + \varepsilon_t + 0.5\,\varepsilon_{t-1} - 0.3\,\varepsilon_{t-2}$ with $\sigma^2 = 1$, and suppose the two most recent reconstructed shocks are $\hat\varepsilon_t = 2.0$ and $\hat\varepsilon_{t-1} = -1.0$. The one-step forecast uses both stored shocks: $\hat X_t(1) = 10 + 0.5\,\hat\varepsilon_t - 0.3\,\hat\varepsilon_{t-1} = 10 + 0.5(2.0) - 0.3(-1.0) = 11.3$. The two-step forecast keeps only the shock dated $t$ (the term with $j = 2$): $\hat X_t(2) = 10 - 0.3\,\hat\varepsilon_t = 10 - 0.3(2.0) = 9.4$. The three-step forecast has no surviving shock at all, so $\hat X_t(3) = 10 = \mu$, and every horizon beyond that is also $10$. The forecast-error variances climb $\sigma^2 = 1$, then $\sigma^2(1 + 0.5^2) = 1.25$, then $\sigma^2(1 + 0.5^2 + 0.3^2) = 1.34$ for $h \ge 3$, freezing at the process variance. Two informative forecasts, then a flat line at the mean: that cliff is the signature of finite memory.

5. Worked Example: Simulate, Recover, Fit, Forecast Advanced

We now put the whole section to work on the finance running dataset of Chapter 1: daily log returns of an equity index, the canonical case where MA terms earn their keep because microstructure effects (bid-ask bounce, non-synchronous trading) induce a short, sharp lag-1 autocorrelation in returns that an MA(1) captures exactly. We simulate a realistic MA(1)/MA(2) return series, confirm the ACF cutoff predicted in subsection two, fit the model from scratch with a CSS estimator, and produce the mean-reverting forecast of subsection four. Code 5.1.1 simulates the process and recovers its ACF cutoff.

import numpy as np

def simulate_ma(n, thetas, mu=0.0, sigma=1.0, rng=None, burn=200):
    """Simulate X_t = mu + e_t + sum_j theta_j e_{t-j}, an MA(q) process."""
    rng = rng or np.random.default_rng(0)
    q = len(thetas)
    e = rng.normal(0.0, sigma, size=n + burn + q)   # innovations, with burn-in
    theta = np.concatenate(([1.0], np.asarray(thetas, float)))   # [1, theta_1..theta_q]
    x = np.empty(n + burn)
    for t in range(q, n + burn):
        x[t] = mu + np.dot(theta, e[t - np.arange(q + 1)])  # current + q past shocks
    return x[burn:]

def sample_acf(x, max_lag):
    """Sample ACF rho_hat(k), k = 0..max_lag, divisor n (as in Section 3.3)."""
    x = np.asarray(x, float) - np.mean(x)
    g0 = np.dot(x, x) / len(x)
    return np.array([np.dot(x[:len(x) - k], x[k:]) / len(x) / g0
                     for k in range(max_lag + 1)])

rng = np.random.default_rng(42)
# Finance framing: a daily log-return series with a single negative microstructure lag.
returns = simulate_ma(n=1000, thetas=[0.6], mu=0.0004, sigma=0.011, rng=rng)
acf = sample_acf(returns, max_lag=6)
band = 1.96 / np.sqrt(len(returns))
print(f"95% band: +/- {band:.3f}")
for k in range(1, 7):
    flag = "  <-- significant" if abs(acf[k]) > band else ""
    print(f"  lag {k}: rho_hat = {acf[k]:+.3f}{flag}")
Code 5.1.1: Simulating an MA(1) daily-return series and reading its ACF. The single significant spike at lag 1 followed by silence is the cutoff derived in subsection two; the band reuses the Bartlett $\pm 1.96/\sqrt{n}$ threshold of Section 3.3.
95% band: +/- 0.062
  lag 1: rho_hat = +0.434  <-- significant
  lag 2: rho_hat = +0.013
  lag 3: rho_hat = -0.028
  lag 4: rho_hat = +0.021
  lag 5: rho_hat = -0.006
  lag 6: rho_hat = +0.019
Output 5.1.1: The sample ACF spikes at lag 1 ($\hat\rho(1) \approx 0.434$, close to the theoretical $0.6/1.36 = 0.441$) and falls inside the band at every lag beyond, the empirical cutoff that names $q = 1$. Higher lags are statistical noise, exactly as the MA(1) autocovariance formula predicts.

Having recovered the order, we fit the MA(1) from scratch. Code 5.1.2 implements the conditional-sum-of-squares estimator of subsection four directly: it reconstructs the latent innovations by the invertible recursion, sums their squares, and hands that objective to a scalar optimizer constrained to the invertible region $|\theta_1| < 1$.

from scipy.optimize import minimize_scalar

def css_objective(theta1, x, mu):
    """Conditional sum of squares for an MA(1): reconstruct shocks, return sum e^2."""
    eps_prev = 0.0                       # pre-sample innovation set to its mean, zero
    ssq = 0.0
    for xt in x:
        eps = (xt - mu) - theta1 * eps_prev   # invertible recursion solves for e_t
        ssq += eps * eps
        eps_prev = eps
    return ssq

mu_hat = returns.mean()                  # MA(q) mean is the sample mean
# Optimize CSS over the invertible interval (-1, 1); MA fitting is genuinely nonlinear.
res = minimize_scalar(css_objective, bounds=(-0.99, 0.99),
                      args=(returns, mu_hat), method="bounded")
theta1_hat = res.x
sigma2_hat = res.fun / len(returns)      # residual variance estimate
print(f"from-scratch CSS:  theta1_hat = {theta1_hat:+.3f}   "
      f"sigma2_hat = {sigma2_hat:.2e}   (true theta1 = 0.600)")
Code 5.1.2: A from-scratch conditional-sum-of-squares fit of the central model. The recursion eps = (xt - mu) - theta1 * eps_prev is the invertible inversion of subsection three, and minimize_scalar performs the nonlinear search that no closed-form least squares can replace because the regressor eps_prev is itself a function of the parameter.
from-scratch CSS:  theta1_hat = +0.598   sigma2_hat = 1.21e-04   (true theta1 = 0.600)
Output 5.1.2: The CSS estimate $\hat\theta_1 = 0.598$ recovers the true $0.6$ to within sampling error, and $\hat\sigma^2 \approx 1.21\times10^{-4}$ matches the simulated $0.011^2 = 1.21\times10^{-4}$. The from-scratch nonlinear estimator works, but it took an iterative optimizer to get here, not a regression.

Forty-odd lines of recursion and optimization above exist so the machinery is transparent. In practice the same fit, with exact maximum likelihood, standard errors, and forecasting, is three lines of statsmodels. Code 5.1.3 fits the identical model as an ARIMA(0,0,1) and produces the mean-reverting forecast.

from statsmodels.tsa.arima.model import ARIMA

# MA(1) is ARIMA(p=0, d=0, q=1); exact MLE via the Kalman filter (Chapter 7).
model = ARIMA(returns, order=(0, 0, 1), trend="c").fit()
print(model.summary().tables[1])         # const (mu) and ma.L1 (theta1) with std errors

fc = model.get_forecast(steps=5)         # forecast 5 steps ahead
print("\nstep   forecast      se")
for h, (m, s) in enumerate(zip(fc.predicted_mean, fc.se_mean), start=1):
    print(f"  {h}   {m:+.5f}   {s:.5f}")
Code 5.1.3: The library pair for Codes 5.1.2 and 5.1.1. The roughly forty lines of from-scratch CSS estimation, ACF computation, and forecasting collapse to three statsmodels calls (an order-of-magnitude reduction in line count), and the library additionally delivers exact Kalman-filter MLE, parameter standard errors, the invertibility-constrained optimizer, and the forecast-error variances, none of which the toy estimator computed.
==============================================================================
                 coef    std err          z      P>|z|      [0.025      0.975]
------------------------------------------------------------------------------
const          0.0004    0.000      1.07     0.285      -0.000       0.001
ma.L1          0.5996    0.025     24.18     0.000       0.551       0.648
==============================================================================

step   forecast      se
  1   +0.00185   0.01101
  2   +0.00040   0.01283
  3   +0.00040   0.01283
  4   +0.00040   0.01283
  5   +0.00040   0.01283
Output 5.1.3: The library recovers $\hat\theta_1 = 0.5996$ (matching the from-scratch $0.598$) and forecasts one informative step ($+0.00185$, carrying the last reconstructed shock) before every horizon $h \ge 2$ reverts to the estimated mean $\mu \approx 0.0004$, with the forecast standard error freezing at the full process standard deviation. This is the forecast cliff of subsection four, observed live.
Library Shortcut: MA Models Off the Shelf

The from-scratch CSS estimator of Code 5.1.2 exists to make the nonlinear fitting concrete; in production you never write it. An MA($q$) model is fit as statsmodels.tsa.arima.model.ARIMA(y, order=(0, 0, q)), which performs exact maximum-likelihood estimation through the Kalman filter, enforces invertibility, and returns parameter standard errors, information criteria, and forecast intervals from one .fit() call. The Nixtla stack reduces it further: statsforecast offers a vectorized ARIMA that fits thousands of series in parallel, and AutoARIMA (the pmdarima/sktime idiom) selects $q$ automatically by the ACF-cutoff-plus-information-criteria search of Chapter 5, so you need not even read the correlogram by hand. The library internally manages the latent-shock reconstruction, the invertible-root selection that resolves the $\theta_1 \leftrightarrow 1/\theta_1$ ambiguity of subsection two, the determinant term that CSS drops, and the mean-reversion bookkeeping in the forecast. Roughly forty lines become three. Beware the sign convention: statsmodels writes $\theta$ with a plus sign as we do, but some texts and R packages use $1 - \theta_1 B$, so a published $\theta$ may appear negated; always check the package's polynomial convention before comparing coefficients.

Practical Example: The Index Fund That Read Its Own Microstructure

Who: A quantitative analyst on the execution desk of an asset manager, modeling daily log returns of a small-cap equity index to time end-of-day rebalancing trades.

Situation: The raw daily returns showed a small but stubbornly significant lag-1 autocorrelation of about $0.18$, with the ACF clean at every higher lag, a textbook MA(1) fingerprint of the kind Output 5.1.1 displays.

Problem: A colleague insisted the lag-1 correlation was a genuine momentum signal and wanted to trade on it directly, predicting tomorrow's return from today's. The analyst suspected the autocorrelation was a microstructure artifact (bid-ask bounce in a thinly traded index), not a tradable forecast of future direction.

Dilemma: Both stories fit the single ACF spike. An AR(1) momentum process and an MA(1) microstructure process can produce a similar lag-1 correlation, yet they imply opposite trading conclusions: AR(1) persistence is exploitable, MA(1) noise-echo is not.

Decision: She fit both an AR(1) and an MA(1) (the latter exactly as in Code 5.1.3), compared the PACF against the ACF using the duality of Section 3.3, and examined the multi-step forecasts.

How: The PACF, not just the ACF, also cut off after lag 1 rather than decaying, and the MA(1) achieved a marginally better information criterion. Decisively, the MA(1) forecast reverted to the mean after a single step (the cliff of Output 5.1.3), meaning the model itself asserted there was no exploitable signal beyond the immediate next day, and even that one-step edge was inside transaction costs.

Result: The desk did not trade the autocorrelation as momentum. Instead they used the MA(1) to clean the return series before feeding it to their volatility model, removing the spurious lag-1 echo so the GARCH estimates of Chapter 6 were no longer biased by microstructure noise.

Lesson: A single ACF spike does not name a strategy. The mean-reverting forecast of an MA model is itself a diagnostic: when a model insists the future beyond $q$ steps equals the unconditional mean, it is telling you the autocorrelation is a memory of past shocks, not a window onto future ones.

Research Frontier: The Moving Average Inside the Transformer (2024 to 2026)

The MA model is far from retired; its finite-memory linear-filter view has become a design principle in deep sequence modeling. The structured state-space models of Chapter 13, S4 (Gu, Goel, and Re, 2022) and its 2023 to 2024 successors Mamba and Mamba-2 (Gu and Dao), compute outputs as long convolutions of past inputs, an MA($\infty$) with learned, structured kernels that the selective-scan mechanism makes input-dependent. The 2024 to 2025 line of explicitly moving-average-augmented transformers makes the link direct: the MEGA architecture (Ma et al., 2023) inserts an exponential moving average into the attention block to inject the inductive bias of recency that bare attention lacks, and several 2024 long-horizon forecasters (building on the DLinear and PatchTST results of 2023) report that a simple learned moving-average decomposition of trend and residual rivals far larger models. Meanwhile the foundation forecasters Chronos, TimesFM, and Moirai are routinely benchmarked against ARIMA baselines whose MA terms remain hard to beat on short, noisy financial series, and their residuals are audited with the Ljung-Box test of Section 3.3 for leftover MA structure. The ninety-year-old idea that the present is a short window of past shocks now lives, learned and structured, inside the architectures meant to replace it.

Exercise 5.1.1: Two Names, One Window Conceptual

A colleague computes a 5-point moving average of a stock price and announces "I have fit an MA(5) model." Using the comparison table of subsection one, explain in three or four sentences exactly what is wrong with this statement: identify which object the colleague actually built, what is averaged in each of the two senses, and why no parameters were estimated in what the colleague did but five would be estimated in a true MA(5). Then state what the genuine MA(5) model equation would look like.

Exercise 5.1.2: Derive and Verify the MA(2) Autocorrelations Conceptual

For the MA(2) process $X_t = \varepsilon_t + \theta_1 \varepsilon_{t-1} + \theta_2 \varepsilon_{t-2}$ with $\sigma^2 = 1$, derive closed forms for $\rho(1)$ and $\rho(2)$ from the autocovariance formula of subsection two, and confirm $\rho(k) = 0$ for $k \ge 3$. Evaluate them at $\theta_1 = 0.5$, $\theta_2 = -0.3$ (the model of the subsection-four numeric example). Then explain, using subsection three, why a different invertible $(\theta_1, \theta_2)$ pair could in principle share the same $(\rho(1), \rho(2))$, and why invertibility selects only one of them.

Exercise 5.1.3: From-Scratch CSS for an MA(2) Coding

Extend the from-scratch CSS estimator of Code 5.1.2 from MA(1) to MA(2): reconstruct $\hat\varepsilon_t = (X_t - \mu) - \theta_1 \hat\varepsilon_{t-1} - \theta_2 \hat\varepsilon_{t-2}$ and minimize the sum of squares over $(\theta_1, \theta_2)$ with scipy.optimize.minimize (no longer a scalar problem). Simulate the MA(2) of the subsection-four numeric example, fit it, and compare your estimates and forecasts against statsmodels ARIMA(y, order=(0,0,2)). Confirm that your five-step forecast reverts to $\mu$ after exactly two steps, and report whether your CSS estimate or the library's exact MLE lands closer to the truth.

Exercise 5.1.4: When Is an MA Model the Wrong Choice? Open-Ended

The mean-reverting-after-$q$-steps forecast is a feature for some series and a liability for others. Identify a real-world series for which an MA($q$) model's flat-line long-horizon forecast is genuinely appropriate, and one for which it is dangerously wrong, and justify each from the data-generating mechanism rather than from fit statistics. Then discuss how you would diagnose, from the sample ACF and PACF alone, whether a series wants an MA model, an AR model (Section 5.2), or the mixed ARMA model the rest of this chapter builds. There is no single correct answer; argue from the cutoff-versus-decay duality of Section 3.3.