Out-of-sample volatility forecasting and Value-at-Risk backtesting for 14 currencies against the US dollar, on daily FRED data from 2000 through mid-2026. GARCH, EGARCH and GJR-GARCH models (Normal and Student-t innovations) are compared to a RiskMetrics/EWMA benchmark with proxy-robust loss functions, Diebold-Mariano tests, and Kupiec/Christoffersen coverage backtests.
This began as a follow-on to my graduate time-series coursework (applied-time-series, Project 2), which fit GARCH to exchange-rate levels via sparse PCA and selected among models with information criteria but never evaluated the forecasts out of sample. This repo redoes the analysis the way a practitioner would: on returns, on current data, judged out of sample.
Full analysis: WRITEUP.md (PDF).
- Forecast accuracy. Every GARCH-family model beats the RiskMetrics/EWMA benchmark on out-of-sample QLIKE. Plain GARCH and leverage-aware GJR-GARCH lead, splitting along an interpretable line: plain GARCH wins for the calm majors, GJR-GARCH for currencies with volatility asymmetry.
- Tail risk. For 1% Value-at-Risk, the innovation distribution is decisive: Student-t GARCH hits 0.94% realized breaches against a 1% target and passes coverage tests in 12 of 14 currencies, while normal-innovation EWMA over-breaches by 68% and is rejected in 10 of 14.
- Levels vs. returns. Sparse PCA on levels produced components with a unit root (ADF p = 0.52–0.83), so GARCH-on-levels was never well-posed; the same method on returns yields stationary components (ADF p = 0.000) with genuine volatility clustering.
| File / dir | Purpose |
|---|---|
fetch_data.py |
Pull the FRED H.10 daily FX snapshot into data/ (reproducible) |
data.py |
Load the snapshot; build percent log-returns |
models.py |
GARCH/EGARCH/GJR fits (Normal & t) + ICOMP; validated fixed-parameter variance recursions |
evaluation.py |
Expanding-window OOS forecasts, QLIKE/MSE, Diebold-Mariano, Kupiec/Christoffersen VaR |
run_oos.py |
Compute & cache per-currency forecast panels |
aggregate.py |
Cross-currency loss / DM / VaR result tables |
sparse_pca_returns.py |
Sparse PCA on returns + GARCH horse race on the components |
sparse_pca_ipm.py |
Inverse-power-method sparse PCA (see ATTRIBUTION) |
figures.py |
All figures |
verify.py |
Sanity checks (persistence, DM anti-symmetry, VaR consistency, reproducibility) |
data/ |
Committed FRED snapshot + pull metadata |
output/ |
Cached forecast panels, result tables, figures |
pip install -r requirements.txt
python3 fetch_data.py # refresh the FRED snapshot (or use the committed one)
python3 run_oos.py --all # per-currency expanding-window forecasts (~4 min)
python3 aggregate.py # cross-currency loss / DM / VaR tables
python3 sparse_pca_returns.py # sparse-PCA-on-returns section
python3 figures.py all # all figures
python3 verify.py # verification checksThe committed data/ snapshot fixes every number to the 2026-07-09 FRED pull.
FX rates: Federal Reserve H.10 via FRED. Methods draw on H. Bozdogan's ICOMP information-complexity criterion, Patton (2011) for proxy-robust volatility loss functions, and Diebold & Mariano (1995), Kupiec (1995) and Christoffersen (1998) for the forecast and coverage tests. See ATTRIBUTION.md.
