Repository navigation
fix(sv): re-apply the per-variable log-vol level in the SV forecast (#241) - #268
Merged
Merged
Conversation
…241) `build_pymc_latent` composes the in-sample log-vol as `v{i}_h + v{i}_mu` whenever the dynamics carries no intrinsic level (`has_explicit_level` False, i.e. random walk). `forecast_cholesky_path` extrapolated `v{i}_h` alone, so every multivariate SV forecast standard deviation came out at `exp(-mu_i / 2)` times the correct scale — silently, with the in-sample fit unaffected. Every SV density, conditional and scenario forecast inherited the mis-scaled bands. `forecast_cholesky_path` now branches on `has_explicit_level` exactly as the in-sample registration does, adding `v{i}_mu` back on for the random-walk path and leaving AR(1) (whose `alpha` carries the level) untouched. The `synthetic_sv_idata_2v` fixture encoded the inverse of the true invariant: it set `v{i}_h = h[..., i]` *and* a nonzero `v{i}_mu`, which asserts `mu_i == 0` — precisely the assumption the bug made. Every test built on it was structurally blind to this whole family of level bugs. The fixture now respects `h[..., i] == v{i}_h + v{i}_mu`. Three regression pins added; two of them fail on the unfixed code. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Egjd7ToFeb9TQqFnfRQZxV
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## main #268 +/- ##
=====================================
Coverage 95.4% 95.4%
=====================================
Files 46 46
Lines 3434 3438 +4
Branches 439 440 +1
=====================================
+ Hits 3278 3282 +4
Misses 111 111
Partials 45 45 ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
Merged
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Closes #241. Blocks the v0.0.9 release.
The bug
StochasticVolatility.build_pymc_latentcomposes the in-sample log-vol asso under random-walk dynamics
h[..., i] == v{i}_h + v{i}_mu.forecast_cholesky_pathcalleddynamics.forecast_log_vol(..., name_prefix=f"v{i}_"), andRandomWalk.forecast_log_volreadsposterior[f"{prefix}h"]— the level-free path — and extrapolates from its last value.v{i}_muwas never re-applied.Net effect: every multivariate SV forecast standard deviation came out at
exp(-mu_i / 2)times the correct scale. The in-sample fit was unaffected, so nothing looked wrong. Every SV density, conditional and scenario forecast inherited the mis-scaled bands.AR(1) was never affected:
has_explicit_levelisTrue, no outermu_iis registered, andAR1.forecast_log_volalready readsalpha.The fix
forecast_cholesky_pathnow branches onhas_explicit_levelexactly as the in-sample registration does — addingv{i}_muback for the random-walk path, leaving AR(1) alone.Why the tests missed it
synthetic_sv_idata_2vencoded the inverse of the true invariant:That asserts
mu_i == 0while supplying a nonzeromu_i— precisely the assumption the buggy forecast made. Every test built on this fixture was structurally blind to the whole family of level bugs, and the existingforecast_cholesky_pathtests only asserted shapes.The fixture now respects
h[..., i] == v{i}_h + v{i}_mu.Tests
TestForecastLevelComposition, three pins:test_zero_innovation_forecast_continues_the_in_sample_leveltest_level_is_not_double_countedtest_ar1_needs_no_outer_levelThe first sets
sigma_eta = 0, making the walk a flat continuation, sodiag(L_t)must equalexp(h[-1] / 2)exactly —R_cholhas a unit diagonal. Seed-independent and exact (rtol=1e-12), not a tolerance-tuned approximation.Verified locally: 1049 passed,
ruff check/ruff format/tyclean.Out of scope
Companion issue #244 —
v{i}_muand the random walk’s ownh0are additively redundant, so the level is not separately identified, andSVDefaultPriorreusesmu_mu/mu_sigma(calibrated from the series mean/SD) for what is a log-variance level. Both are prior-specification questions; this PR only makes the forecast agree with the model that was fitted.🤖 Generated with Claude Code
https://claude.ai/code/session_01Egjd7ToFeb9TQqFnfRQZxV