Univariate Time Series
Single-series models in two movements. First the conditional mean: Box–Jenkins SARIMA forecasting, and the change-point → Markov-switching ladder — a Bayesian AR with mean and variance shifts and a Markov-switching AR — that lets the dynamics break. Then the conditional variance, a full volatility arc: GARCH, asymmetric GJR-GARCH with Value-at-Risk backtesting, GARCH-in-mean (the risk–return tradeoff), stochastic volatility, realized volatility (HAR and Realized GARCH from intraday data), and regimes vs long memory in volatility persistence — capped by an application that prices options under GARCH and stochastic volatility. Each is built from scratch and cross-checked across Python and R.
Stationarity, and why two tests rather than one. Nearly everything below assumes the series is stationary — that its statistical behaviour does not drift, so the mean and the autocorrelations mean the same thing at the start of the sample as at the end. A trending series is not, and fitting one as though it were produces a model that mistakes the trend for memory. The usual repair is differencing: model the changes rather than the levels. Deciding whether that is needed is what the two tests do, and the reason both appear is that they are set up in opposite directions. The ADF test has non-stationarity as its null, so rejecting it is evidence for stationarity; the KPSS test has stationarity as its null, so rejecting that is evidence against. Running one gives you a result you cannot fully interpret — failing to reject a null is not evidence for it — while running both lets the two agree, which is genuine evidence, or disagree, which is a warning that the series is borderline. The other diagnostics recur too: the ACF plots the correlation of the series with itself at each lag, which is how a seasonal spike is told apart from a damped cycle, and the Ljung–Box test asks whether any autocorrelation is left in the residuals, which is the standard check that a fitted model has taken out everything it should.
What volatility actually is. Seven of the ten examples below are about volatility, so it is worth being plain about the word. A daily financial return splits into a predictable part, which is almost nothing — tomorrow’s direction is very close to unforecastable, and one of the examples below is an honest demonstration of just how close — and an unpredictable part, which is almost everything. Volatility is the scale of that unpredictable part: not which way the price will move, but how far. The empirical fact that generates this entire arc is that the scale is not constant. It moves, and it moves in a particular way — large days cluster with large days, calm with calm. That is volatility clustering, and its consequence is the asymmetry the whole section lives off: the size of tomorrow’s move is forecastable even though its direction is not.
Which is why so much machinery points at it. Direction is where the money is and cannot be predicted; magnitude can be, and it is what almost everything downstream is denominated in. A risk limit is a statement about volatility. So is a bank’s capital requirement. So, very nearly entirely, is an option price — which is why the arc ends by pricing options. The models differ mainly in where they get the current level from: GARCH computes it from past returns by a fixed recursion, stochastic volatility treats it as a hidden state with noise of its own, and realized volatility measures it directly by adding up squared intraday returns. The forecasting tournament near the end asks which wins, and the answer is worth knowing in advance: the choice of model matters much less than whether the intraday data was used at all.
Persistence, and how to read it as a half-life. The volatility arc keeps reporting one number — the persistence, α+β, and it is nearly always around 0.99. That value is easier to think about as a half-life: if a volatility shock decays by a factor α+β each day, half of it survives for log(½) / log(α+β) days. At 0.99 that is about 69 days; at 0.997, the figure most of these fits report, it is 231 days, most of a trading year. That is what near-unit-root persistence means in practice: a shock to volatility today is still half-present the better part of a year later, which is why the question of whether that persistence is real or an artefact gets a whole example of its own.
How a forecast is scored. Three loss measures appear and they are not interchangeable. RMSE is the root-mean-square error in the units of the series, and MAPE the mean absolute error as a percentage, which makes it comparable across series of different size — both are for forecasts of the level. Volatility forecasts need something else, because the thing being forecast is never observed: you compare a predicted variance against a noisy proxy for the realised one, and a symmetric loss like RMSE will systematically favour models that under-predict. QLIKE is the standard fix — a loss that penalises proportional error rather than absolute, so being wrong by a factor of two costs the same whether volatility is high or low, and which stays honest under a noisy proxy. It is the score the realized-volatility verdict rests on, and lower is better.
Value-at-Risk, and what backtesting it means. Two examples end in VaR: the loss threshold a portfolio should exceed only 1% of days (or 5%). It is a quantile of the predicted return distribution, so it tests the whole model at once — the volatility forecast and the tail shape together. Backtesting asks two separate questions of the realised exceedances. Were there the right number of them? That is Kupiec’s unconditional-coverage test. And were they spread out, or did they arrive in clusters? That is Christoffersen’s independence test — a model can take exactly 1% of hits and still be useless if they all land in one bad fortnight, which is precisely what happens to the models that ignore volatility clustering. Expected Shortfall answers the question VaR does not: given that the threshold was breached, how bad was it on average?
The fact the volatility arc is built on, and the asymmetry that runs through it
The top panel is the whole premise. It is the daily S&P 500 series these examples fit — 5,523 days from 1987 to 2009 — with the fitted conditional volatility drawn as a ±2σ band. The returns themselves have no visible pattern, and that is the point: their direction is unforecastable. Their scale plainly is not. The band breathes, tightening for years at a time and blowing out in 1987 and 2008, and over the sample the fitted volatility ranges from 0.44% to 8.38% a day — a factor of 19. Every model in the second movement is a different proposal for how that band should be drawn.
The three lower panels are one fact in three costumes, and the section keeps meeting it. In GARCH it is a kink: the news-impact curve says a shock of −2 raises tomorrow's variance to 1.82 where a symmetric model gives 1.59, so bad news counts for more. In stochastic volatility it is a correlation — take the fitted volatility path and compare each day's return against the next day's change in log-volatility, and the relationship is strongly negative, −0.62 here, which is exactly the ρ ≈ −0.5 to −0.7 the SV example reports from a completely separate estimation. And in option prices it is the skew: simulating this same fitted model forward 21 days and pricing calls across strikes gives downside protection an implied volatility 2.8 points above upside, where the identical model with γ set to zero gives a near-flat 0.2. That is the whole logic of the options example in miniature — the leverage parameter estimated from returns is what puts a price on crash insurance.
Both numbers are worth pausing on, because neither was put there by hand. The −0.62 and the 2.8-point skew are computed here from the GJR-t coefficients the notebooks fit, and they land on the figures two other examples arrive at by different routes — the SV page's ρ, and the options page's observation that a returns-fit skew comes out around 2.5 points against the market's 7. Three estimations, one asymmetry.
How the ten examples fit together
Two movements. The first three let the mean break; the remaining seven model the variance, and that second group is itself a small arc — build it, extend it, adjudicate between the extensions, then use it for something. Read the trunk of each group first.
I · The conditional mean
Where is the series going, and is the rule allowed to change?
SARIMABox–Jenkins forecasting, and five series ranging from almost perfectly forecastable (CO₂, MAPE 0.2%) to essentially not (GNP growth) Mean & variance shiftsthe break dates become parameters — the model finds them rather than being told Markov-switching ARbreaks that recur: recession and expansion as states a chain moves betweenII · The conditional variance
How large will tomorrow’s move be — and what is that worth?
Build it, from returns alone
GARCHthe workhorse: volatility as a recursion in past shocks. Fitted five ways to show they are one posterior GJR asymmetry + VaRbad news moves volatility 18× more than good — then put it to work sizing risk GARCH-in-meandoes volatility get paid for? The honest answer is: probably, weakly Stochastic volatilitythe rival paradigm — volatility as a hidden state with its own noise, so it gets a credible bandOr measure it directly
Realized volatilityintraday data turns volatility from something inferred into something observed — HAR and Realized GARCHThen adjudicate, and apply
Regimes vs long memorywhy is persistence always ≈0.99? Two rival explanations, deliberately confounded, put to a contest Option pricingthe payoff: the same leverage parameter, in three guises, is what tilts a smile into a skewTwo threads run the length of the second movement and are worth watching for. One is persistence: every fit reports α+β ≈ 0.99, and the regimes-vs-long-memory example exists because two quite different stories explain that. The other is asymmetry — GJR’s γ, stochastic volatility’s ρ and Heston’s correlation are one fact about markets written three ways, and the options page is where that finally has a price attached to it.
Seasonal ARIMA (SARIMA)
Seasonal time-series modelling and forecasting — the Box–Jenkins SARIMA
framework, comparing the frequentist workflow (statsmodels / pmdarima and R's
forecast::auto.arima, with automatic Hyndman–Khandakar order selection and the
Kalman-filter likelihood) against a Bayesian structural alternative — a linear
trend + monthly seasonal effects + AR(1) errors fit by PyMC/NUTS, giving full
posterior-predictive forecasts. The two encode seasonality differently — SARIMA by seasonal
differencing (stochastic, drifting), the structural model by fixed monthly dummies
(deterministic) — both valid when the seasonal shape is stable. Five examples span the range and
build the ARIMA → changepoint → regime-switching ladder:
AirPassengers (multiplicative seasonality, ~8–9% MAPE), sunspots
(cyclicality — a variable ~11-year period from AR complex roots, a damping forecast, and a
Bayesian posterior over the cycle length), Mauna Loa CO₂ (additive trend +
seasonality, near-perfect ~0.3% MAPE), US GNP growth (the weak, irregular,
barely-forecastable business cycle — the linear benchmark for Markov-switching), and the
Nile (a structural break / intervention, with a Bayesian changepoint that
estimates the 1899 break year itself). Cross-checked across statsmodels, PyMC, and R.
Bayesian AR with Mean and Variance Shifts
A Python implementation of the McCulloch & Tsay (1993) 4-block Gibbs sampler for an AR(p) model with piecewise-constant mean and innovation variance. The key reparametrisation — raw AR residual rt ≈ κμt + et — reduces within-segment inference to conjugate Normal–Normal (mean) and Inverse-Gamma–Normal (variance) problems. Single-site sweeps on the shift indicators compare marginal likelihoods of a merge vs. a split for every active time point. Applied to: Section 1 a synthetic AR(2) with a planted mean shift (detected at P = 0.88) and variance shift; Section 2 monthly US retail gasoline price changes (1978–1991) — reproduces M&T's AR(2) coefficients ≈ (0.91, −0.30), no mean shift, and all five variance-shift episodes (Iranian Revolution, 1981 OPEC peak, 1982, 1986 oil collapse, 1989); Section 3 the full 1976–2026 BLS series, recovering variance regimes through the 2022 Russia–Ukraine invasion and the live 2026 Iran/Hormuz crisis; Section 3b a log-return refit showing the ~2000 volatility increase is real but 3–4× smaller than the nominal cent analysis suggests.
View example →Bayesian Markov-Switching AR
A Python implementation of the Hamilton (1989) K-regime switching-intercept AR(p) model via two Bayesian routes: a from-scratch Forward-Filter Backward-Sampling (FFBS) Gibbs sampler (pure NumPy) and a NumPyro NUTS fit that marginalizes the discrete states with the Hamilton forward filter. FFBS draws the entire state path S1:T jointly each sweep — exact joint draw via the Markov factorization — and recovers smoothed regime probabilities by averaging sampled paths. Regimes are identified by relabelling each sweep so intercepts are ascending. Applied to: Section 1 synthetic AR(2) with planted regimes (88.7% classification); Section 2 Hamilton's quarterly US real GNP (1951Q2–1984Q4) — intercepts −0.27 / 1.05 matching Hamilton's −0.36 / 1.16, NBER recession classification 95% (FFBS) / 91% (NumPyro); frequentist cross-validation on US gasoline log-returns with Python statsmodels and R MSwM — three independent implementations agree on AR(2) dynamics and the ~2000 volatility transition to within rounding, completing a three-way validation with the McCulloch–Tsay change-point model.
View example →Bayesian GARCH
Volatility modelling for financial returns — where SARIMA models the mean, GARCH
models the moving variance (volatility clustering). The same GARCH(1,1) is fit on daily
S&P 500 returns (1987–2009) across a full trio, and — in the from-scratch notebook —
five ways: the classical MLE plus four Bayesian methods that predate today's
HMC/NUTS samplers (Gauss–Hermite quadrature, importance sampling,
Metropolis–Hastings, and griddy Gibbs), which all recover one
posterior (α+β ≈ 0.993). The dimension-general module also spans GJR leverage and Normal/Student-t
innovations; adding fat tails gives ν ≈ 6.3 and a decisive Normal-vs-t Bayes factor
(log BF ≈ +199) read straight off the Gauss–Hermite marginal likelihood. Cross-checked against
PyMC/NUTS and R (rugarch ML + bayesGARCH MCMC) — four engines, one answer.
Bayesian Asymmetric GARCH (GJR) & Value-at-Risk
The asymmetric extension of GARCH: GJR-GARCH
adds a leverage term so bad news raises volatility more than good news.
The same five from-scratch estimators fit the extended model on S&P 500 returns; leverage is
large and unanimous (γ ≈ 0.13 — a negative shock moves next-day volatility ~15× more than a
positive one), drawn directly by the Engle–Ng news-impact curve. Because Gauss–Hermite returns
each marginal likelihood, the four models rank by Bayes factor — GARCH-N → +t →
+leverage → GJR-t (best). Cross-checked against PyMC/NUTS and R
(rugarch gjrGARCH/eGARCH). A fourth notebook applies the volatility models to
Value-at-Risk and backtests them through the 2008 crisis (Kupiec &
Christoffersen): Historical Simulation fails the independence test, RiskMetrics' Gaussian tail
under-covers, and only GARCH-t / GJR-t pass cleanly at the 1% level.
Bayesian GARCH-in-Mean — the Risk-Return Tradeoff
Does higher volatility earn a higher expected return? GARCH-in-Mean (Engle,
Lilien & Robins 1987) puts the conditional volatility into the mean equation, so the
new coefficient λ is the price of risk. Because the mean feeds
back into the variance recursion, the likelihood is a genuine forward loop (no vectorised filter).
On daily S&P 500 returns the premium has the right sign but is statistically weak — the classic
risk-return "puzzle": λ > 0 everywhere yet no interval excludes zero. Two
refinements sharpen it — fat tails absorb much of the apparent premium (it halves
under Student-t), and lower frequency helps (monthly λ is ~10× larger, a
signal-to-noise effect). Estimated four ways across three engines — from-scratch MLE + Metropolis,
PyMC/NUTS, and R rugarch (archm=TRUE) — all agreeing, extending the
GARCH / GJR family.
Bayesian Stochastic Volatility
The parameter-driven alternative to GARCH: in a stochastic-volatility model the
log-variance is its own latent AR(1) with an independent shock, so — unlike a
GARCH filter — the fitted volatility path carries a genuine posterior credible band.
The from-scratch notebook builds the Kim–Shephard–Chib (1998) sampler by hand,
exposing that SV is a state-space model: a 7-component mixture linearises the log-squared
returns, and the whole log-variance path is drawn in one block by the Kalman filter + backward
sampling (FFBS). Three engines agree on the S&P 500 posterior (φ ≈ 0.987,
ση ≈ 0.15) — from-scratch, NumPyro/NUTS, and R's stochvol (Kastner ASIS).
Extensions add SV-t (fat tails, ν ≈ 9) and leverage (ρ ≈ −0.6, the
SV analogue of GJR γ > 0), ranked by WAIC. Together with
GARCH and GJR, the
arc recovers the same three stylized facts — persistence, fat tails, leverage — written three ways.
Realized Volatility — HAR & Realized GARCH
Where GARCH and SV infer volatility from daily returns, realized volatility measures it from intraday prices (summing squared high-frequency returns). A three-phase
arc: (1) the realized measures and their two stylized facts — long memory and
log-normality; (2) Corsi's HAR-RV, a 4-parameter regression of log-RV on its
daily/weekly/monthly averages that beats a fitted GARCH out-of-sample — built three
ways (from-scratch / PyMC / R highfrequency), with HAR-CJ (jumps) and HARQ
(measurement error) extensions; and (3) Realized GARCH (Hansen–Huang–Shek 2012),
the formal bridge, plus a forecasting tournament. The verdict: realized-measure
models beat returns-only ones (GARCH,
SV) by ~25% on QLIKE — using intraday data matters more than the
model class.
Volatility Persistence: Regimes vs Long Memory
Every GARCH fit reports the same near-unit-root persistence (α+β ≈ 0.997) — but why?
Two rival explanations, which Diebold & Inoue (2001) proved are observationally confounded:
long memory (FIGARCH, fractional integration — the persistence
is real, decaying hyperbolically) vs regime switching (MS-GARCH —
the persistence is a spurious artifact of shifts in the volatility level). Both are fit from
scratch (FIGARCH by fractional differencing; MS-GARCH via the Haas–Mittnik–Paolella spec, Hamilton
filter + Kim smoother), each with an R package cross-check (rugarch,
MSGARCH). The verdict is both, but the persistence is long memory:
d = 0.52 (genuine long memory, matching the realized-volatility
evidence), and allowing regimes does not dissolve the within-regime persistence — the
regimes add a business-cycle layer on top, but the near-unit-root persistence is real.
Option Pricing under GARCH & Stochastic Volatility
The application phase of the volatility arc — the same models that fit equity returns now price options, replicating three seminal papers. Heston–Nandi (2000): a GARCH with an exponential-affine generating function, so options price in closed form by Fourier inversion (martingale identity to machine precision). Duan (1995): Monte-Carlo GARCH pricing via the locally risk-neutral valuation relationship — validates the closed form, then prices a path-dependent Asian option no closed form can. Heston (1993): the continuous-time stochastic-vol closed form via the characteristic function, reducing to Black–Scholes as vol-of-vol → 0. The unifying thread is the leverage parameter: the γ that GJR-GARCH found in returns and the ρ of stochastic volatility are the same asymmetry that tilts a symmetric option smile into the equity-index skew.
View example →