Skip to content

BUG: Use the expected variance path in RiskMetrics2006 forecasts - #885

Closed
raashish1601 wants to merge 2 commits into
bashtage:mainfrom
raashish1601:rm2006-analytic-forecast
Closed

raashish1601 wants to merge 2 commits into
bashtage:mainfrom
raashish1601:rm2006-analytic-forecast

Conversation

@raashish1601

Copy link
Copy Markdown
Contributor

The analytic multi-step forecast for RiskMetrics2006 repeats the one-step forecast for every horizon. That is only right for a single EWMA. RiskMetrics2006 is a weighted sum of EWMA components with different smoothing parameters, and each component is updated with the squared residual, whose expected value is the combined variance:

E[s2_k(t+h+1)] = mu_k E[s2_k(t+h)] + (1 - mu_k) sum_j w_j E[s2_j(t+h)]

So the expected variance drifts toward the long components as the horizon grows, and the flat forecast disagrees with the simulation and bootstrap forecasts of the same model. With a ConstantMean model fit on 2,000 simulated GJR-GARCH returns, the analytic forecast was 1.4% below the simulated one (100,000 paths) at h=2 and 8.3% below at h=10. With this change the two agree to within simulation noise (largest gap 0.2% over 10 horizons).

The change computes the EWMA components at each forecast origin (as the simulation forecast already does) and iterates the recursion above. The one-step forecast is unchanged. EWMAVariance keeps its flat forecast, which is exact for a single EWMA.

test_rm2006_forecast asserted that every horizon equals the one-step forecast, so I changed it to check the recursion, computed independently from the residuals. arch/tests/univariate/test_variance_forecasting.py passes, and so do the RiskMetrics2006 cases in test_forecast.py (I ran the rest of the univariate suite only partly, since it is slow without the compiled extensions here). black, isort, ruff and flake8 report nothing new on the changed lines.

@codecov

codecov Bot commented Oct 8, 2026 •

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 99.61%. Comparing base (c7de951) to head (cfc8de5).
⚠️ Report is 2 commits behind head on main.

Additional details and impacted files
@@           Coverage Diff           @@
##             main     #885   +/-   ##
=======================================
  Coverage   99.61%   99.61%           
=======================================
  Files          85       85           
  Lines       19811    19835   +24     
  Branches     1588     1591    +3     
=======================================
+ Hits        19735    19759   +24     
  Misses         41       41           
  Partials       35       35           
Flag Coverage Δ
adder 99.60% <100.00%> (+<0.01%) ⬆️
subtractor 99.60% <100.00%> (+<0.01%) ⬆️

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@raashish1601

Copy link
Copy Markdown
Contributor Author

The only failing job is LinuxTest python_311_cython_coverage, which Azure cancelled with "Job cancelled due to worker timeout" during the test step. All the other Linux and Windows jobs and both codecov checks passed.

@bashtage bashtage left a comment

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not completely sure this is the right fix, even if it is internally consistent.

Comment thread arch/univariate/volatility.py Outdated
components = components.T[start + 1 :]
for i in range(1, horizon):
forecasts[:, i] = forecasts[:, 0]
sigma2 = components @ w

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I fully agree that this is wrong -- however, the paper suggests that forecasts should be reweighted combinations of the final component values, rather than iterating in the usual GARCH method. It is possible these are equivalent, but it is not obvious at a single look.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

They are the same thing. Let s(t) be the vector of component values at the forecast origin and A = diag(mu) + (1 - mu) w'. Taking expectations of the component update gives E[s(t+j)] = A^j s(t), so the h-step forecast is w' A^(h-1) s(t) = w(h)' s(t) with w(h)' = w' A^(h-1). That is a reweighted combination of the final component values, with w(1) = w and w(h+1) = mu * w(h) + w * sum((1 - mu) * w(h)). The rows of A sum to one, so the weights do too.

This is the form in eq. 15 of Zumbach (arXiv:0901.2275), where the forward variance comes from the conditional expectation of the process equations and the weights from a recursion in mu_k (RM2006 is the linear case, so there is no mean variance term).

In e1757e7 I changed the code to compute it this way so it reads like the paper. The test still checks it against the independent iteration of the expected components, and the two agree to machine precision.

@bashtage
bashtage force-pushed the rm2006-analytic-forecast branch from f7c3d42 to 96c23f7 Compare October 9, 2026 14:20
The analytic forecast repeated the one-step forecast for every horizon,
which is only exact for a single EWMA. Iterate the expected values of
the EWMA components instead, so multi-step analytic forecasts agree
with the simulation forecasts.
The expected components follow a linear recursion, so iterating them is the same as applying horizon-dependent weights to the final component values, which is the form used in the RiskMetrics 2006 papers.
@bashtage

Copy link
Copy Markdown
Owner

Thanks. These changes merged in #934

@bashtage bashtage closed this Oct 16, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants