Calibration and Parameter Estimation in Models › Maximum Likelihood Estimation for Time-Series Models
Quantitative FinanceAll chapters 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:
where:
- : return at time
- : mean return
- : return innovation (residual), the unexpected component of the return
- : conditional variance, the variance of returns conditional on past information
- : independent standard-normal innovation, independent of the past information set
- : GARCH parameters controlling the variance dynamics
The conditional variance depends on three components. The baseline variance provides a floor that prevents variance from collapsing to zero. The recent market shock (the ARCH term) allows large return surprises to increase future variance immediately. The previous period's variance (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, and the unconditional variance is . When 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 observations is derived from the assumption that innovations are conditionally normal:
where:
- : conditional log density of the observed returns, viewed as a function of the parameters
- : number of observations
- : conditional variance at time , computed recursively from the GARCH equation
- : residual at time , equal to
- : 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.
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()# 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.xGARCH(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.

