Calibration and Parameter Estimation in Models › Maximum Likelihood Estimation for Time-Series Models

Michael BrenndoerferDecember 13, 20257 min read
Quantitative FinanceAll chapters
Part 8 of 15Econometrics and Statistical Modeling
Chapter 45Calibration and Parameter Estimation in Models › Maximum Likelihood Estimation for Time-Series Models
Reading section 11 of 17 · Maximum Likelihood Estimation for Time-Series Models · 7 minSection 11 of 17 · 7 min

Book contents

Section 11 of 17 · 7 min read

Calibration and Parameter Estimation in Models › Maximum Likelihood Estimation for Time-Series ModelsOne section · Sections in this chapter11 / 17 ↓
Reading tools Definitions & inline links

Choose your expertise level to adjust how many terms are explained. Beginners see more tooltips, experts see fewer to maintain reading flow. Hover over underlined terms for instant definitions.

Article links

Make inline references clickable

While calibration to market prices is essential for pricing derivatives, statistical estimation from historical data remains important for risk modeling and forecasting. Let's demonstrate MLE for the GARCH(1,1) model, building on our discussion in the chapter on modeling volatility.

GARCH(1,1) models time-varying conditional variance. It captures volatility clustering: large-magnitude returns of either sign tend to follow large-magnitude returns, while small-magnitude returns tend to follow small-magnitude returns. The model specifies:

rt=μ+ϵt,ϵt=σtzt,zt∼iidN(0,1)σt2=ω+αϵt−12+βσt−12\begin{aligned} r_t &= \mu + \epsilon_t, \quad \epsilon_t = \sigma_t z_t, \quad z_t \stackrel{iid}{\sim} N(0,1) \\ \sigma_t^2 &= \omega + \alpha \epsilon_{t-1}^2 + \beta \sigma_{t-1}^2 \end{aligned}

where:

  • rtr_t: return at time tt
  • μ\mu: mean return
  • ϵt\epsilon_t: return innovation (residual), the unexpected component of the return
  • σt2\sigma_t^2: conditional variance, the variance of returns conditional on past information
  • ztz_t: independent standard-normal innovation, independent of the past information set
  • ω,α,β\omega, \alpha, \beta: GARCH parameters controlling the variance dynamics

The conditional variance σt2\sigma_t^2 depends on three components. The baseline variance ω\omega provides a floor that prevents variance from collapsing to zero. The recent market shock ϵt−12\epsilon_{t-1}^2 (the ARCH term) allows large return surprises to increase future variance immediately. The previous period's variance σt−12\sigma_{t-1}^2 (the GARCH term) captures the persistence of volatility over time. Together, these components enable the model to capture volatility clustering, where high-volatility periods tend to persist.

Under the usual finite-second-moment assumptions, α+β<1\alpha+\beta<1 and the unconditional variance is ω/(1−α−β)\omega/(1-\alpha-\beta). When α+β\alpha+\beta is close to one from below, variance shocks decay slowly but geometrically. This is persistent behavior, not mathematical long memory. Strict stationarity has a different condition and should not be identified solely with the finite-variance inequality.

The log-likelihood for TT observations is derived from the assumption that innovations are conditionally normal:

ℓ(θ)=−T2log⁡(2π)−12∑t=1T[log⁡(σt2)+ϵt2σt2]\ell(\theta) = -\frac{T}{2}\log(2\pi) - \frac{1}{2}\sum_{t=1}^{T}\left[\log(\sigma_t^2) + \frac{\epsilon_t^2}{\sigma_t^2}\right]

where:

  • ℓ(θ)\ell(\theta): conditional log density of the observed returns, viewed as a function of the parameters
  • TT: number of observations
  • σt2\sigma_t^2: conditional variance at time tt, computed recursively from the GARCH equation
  • ϵt\epsilon_t: residual at time tt, equal to rt−μr_t - \mu
  • π\pi: mathematical constant pi

The log-variance term discourages unnecessarily large variances, while the squared standardized residual penalizes variances too small for the observed innovation. Their balance fits a statistically plausible variance path under the assumed model. Tracking the unobserved variance accurately is a separate question to assess on simulations or validation data.

In[30]:
Code
def garch_log_likelihood(params, returns):
    """Conditional Gaussian NLL up to the parameter-independent constant.

    Initialize with the unconditional variance; no stationary marginal density
    for the first observation is included.
    """
    returns = np.asarray(returns, dtype=float)
    if returns.ndim != 1 or returns.size == 0 or not np.all(np.isfinite(returns)):
        raise ValueError("Require a nonempty finite one-dimensional return array")
    params = np.asarray(params, dtype=float)
    if params.shape != (4,) or not np.all(np.isfinite(params)):
        return np.inf
    mu, omega, alpha, beta = params
    if omega <= 0 or min(alpha, beta) < 0 or alpha + beta >= 1:
        return np.inf
    eps = returns - mu
    sigma2 = np.empty(len(returns))
    sigma2[0] = omega / (1 - alpha - beta)
    for t in range(1, len(returns)):
        sigma2[t] = omega + alpha * eps[t - 1] ** 2 + beta * sigma2[t - 1]
    if not np.all(np.isfinite(sigma2)) or not np.all(sigma2 > 0):
        return np.inf
    # Conditional negative log-likelihood without T*log(2*pi)/2.
    value = 0.5 * np.sum(np.log(sigma2) + eps**2 / sigma2)
    return float(value) if np.isfinite(value) else np.inf


# Generate synthetic daily GARCH returns (252 trading days per year convention).
np.random.seed(42)
T_sim = 2000

# True parameters
mu_true = 0.0005
omega_true = 0.00001
alpha_true_g = 0.08
beta_true_g = 0.88

# Simulate
returns_sim = np.zeros(T_sim)
sigma2_sim = np.zeros(T_sim)
sigma2_sim[0] = omega_true / (1 - alpha_true_g - beta_true_g)
# One period is a trading day; draw the initial observation as well.
returns_sim[0] = mu_true + np.sqrt(sigma2_sim[0]) * np.random.normal()

for t in range(1, T_sim):
    sigma2_sim[t] = (
        omega_true
        + alpha_true_g * (returns_sim[t - 1] - mu_true) ** 2
        + beta_true_g * sigma2_sim[t - 1]
    )
    returns_sim[t] = mu_true + np.sqrt(sigma2_sim[t]) * np.random.normal()
In[31]:
Code
# MLE estimation
x0_garch = [0.0, 0.00002, 0.05, 0.90]
bounds_garch = [
    (-0.01, 0.01),  # mu
    (1e-8, 0.001),  # omega
    (0.001, 0.5),  # alpha
    (0.5, 0.999),  # beta
]

# Scale differently sized coordinates before finite-difference optimization.
garch_scale = np.array([0.001, 0.00001, 0.1, 1.0])
scaled_bounds = [
    (lower / scale, upper / scale)
    for (lower, upper), scale in zip(bounds_garch, garch_scale)
]
garch_candidates = []
for start in (x0_garch, [0.001, 0.00002, 0.10, 0.80]):
    fit = minimize(
        lambda scaled: garch_log_likelihood(scaled * garch_scale, returns_sim),
        np.asarray(start) / garch_scale,
        method="L-BFGS-B",
        bounds=scaled_bounds,
        options={"ftol": 1e-12, "gtol": 1e-6, "maxiter": 2000},
    )
    if fit.success and np.isfinite(fit.fun) and np.all(np.isfinite(fit.x)):
        fit.x = fit.x * garch_scale
        if fit.x[2] + fit.x[3] < 1:
            garch_candidates.append(fit)
if not garch_candidates:
    raise RuntimeError("No successful finite feasible GARCH likelihood fit")
result_garch = min(garch_candidates, key=lambda fit: fit.fun)
print(f"Accepted scaled local starts: {len(garch_candidates)}/2")
print(f"Best conditional NLL (constant omitted): {result_garch.fun:.8f}")

mu_cal_g, omega_cal, alpha_cal_g, beta_cal_g = result_garch.x
Out[32]:
Console
GARCH(1,1) Local Likelihood-Fit Results:
--------------------------------------------------
Parameter |   True     | Estimated  |  Error
--------------------------------------------------
mu        |  0.000500 |  0.001231 | 0.000731
omega     | 0.0000100| 0.0000186 | 0.0000086
alpha     |  0.080000 |  0.078642 | 0.001358
beta      |  0.880000 |  0.837991 | 0.042009

Persistence (α + β): 0.9166
Unconditional vol: 23.74% annualized

These are local likelihood-fit estimates, with numerical diagnostics reported alongside the parameter discrepancies. A successful stopping flag does not prove a likelihood maximum or accurate recovery of every generating parameter. Compare objective values across scaled starts, then assess conditional-volatility tracking separately. Each simulated period is a trading day; annualization uses an explicit 252-trading-day convention.

Out[34]:
Visualization
Time series plot of 2000 simulated GARCH returns in percent, showing alternating calm and turbulent periods consistent with volatility clustering.
Simulated GARCH returns showing volatility clustering. The series displays characteristic periods of high and low variance.
Out[36]:
Visualization
Time series plot comparing true GARCH conditional volatility as a dashed line against the MLE-estimated conditional volatility as a solid line over 2000 simulated periods.
Estimated and generating GARCH conditional volatility on this synthetic sample. The comparison describes variance-path tracking, separately from parameter recovery and optimizer optimality.