Skip to content

feat: data-informed SV priors + dynamics-aware log-vol level (closes #65 #66) - #70

Merged
thomaspinder merged 6 commits into
fix/constant-cache-cholesky-factorfrom
fix/sv-data-informed-priors-and-intercept
May 18, 2026
Merged

thomaspinder merged 6 commits into
fix/constant-cache-cholesky-factorfrom
fix/sv-data-informed-priors-and-intercept

Conversation

@thomaspinder

Copy link
Copy Markdown
Collaborator

Stacked on #69 — review/merge #69 first; this PR's base is fix/constant-cache-cholesky-factor.

Summary

Closes #65 and #66 in one bundled PR (they touch the same per-variable loop in src/impulso/sv/spec.py).

#65 — multivariate SV priors. The adapter was calling build_priors(np.zeros(T)), which produced a near-pinpoint prior on mu_i (sigma ≈ 1e-6) and a wildly off-target h0_mu ≈ -18.4. Fix:

  • VolatilityProcess.build_pymc_latent gains optional data: np.ndarray | None = None.
  • VAR._build_pymc_model pre-fits OLS once (cheap relative to MCMC) and passes the demeaned residuals (T_eff, n_vars) as data=resid.
  • Multivariate SV adapter loops per-variable, calling build_priors(data[:, i]) so each variable's priors come from its own innovation series.
  • Constant accepts and ignores data (Protocol parity).
  • Multivariate SV raises ValueError if data is None or wrong shape — the silent zeros-fallback that caused Multivariate SV prior defaults to np.zeros(T) — needs data-informed priors #65 is gone.

#66 — redundant log-vol intercept. The multivariate adapter added an outer mu_i shift on top of the dynamics' own intercept, double-counting the level for AR(1). Fix:

  • SVDynamics Protocol gains has_explicit_level: bool (ClassVar — true class-level constant).
  • AR1.has_explicit_level = True (its alpha carries the log-vol level); RandomWalk.has_explicit_level = False (no intrinsic level).
  • Multivariate SV adapter skips the outer mu_i when dynamics.has_explicit_level.

Standalone univariate SV is unaffected. It goes through _build_pymc_model, not build_pymc_latent. The byte-for-byte regression file (tests/test_sv_standalone_unchanged.py) was removed pre-existing in commit 034abe0, but the standalone smoke tests (test_sv_spec.py::test_sv_spec_fit_random_walk_smoke / _fit_ar1_smoke) pass unchanged.

Test plan

  • pytest -m "not slow" -q — 236 passed, 22 deselected
  • pytest -m slow tests/test_volatility.py::TestPosteriorEquivalence -v — 2 passed (Constant byte-for-byte unchanged)
  • pytest -m slow tests/test_sv_spec.py::test_sv_spec_fit_random_walk_smoke tests/test_sv_spec.py::test_sv_spec_fit_ar1_smoke -v — 2 passed (standalone SV unchanged)
  • pytest -m slow tests/test_fitted.py::TestFittedVarSigmaDispatch::test_sv_sigma_returns_per_t -v — 1 passed (SV-VAR end-to-end)
  • ruff check src/ tests/ — clean
  • ruff format --check src/ tests/ — clean
  • ty check — clean
  • New tests added:
    • TestHasExplicitLevel (2) — SVDynamics adapter trait
    • TestConstantBuildPymcLatent::test_accepts_and_ignores_data_kwarg — Constant Protocol parity
    • TestSVBuildPymcLatentDataValidation (2) — multivariate SV data=None and wrong-shape guards
    • TestSVMultivariatePerVariablePriors::test_calls_build_priors_with_per_variable_slice — spy on build_priors per variable
    • TestSVMultivariateDynamicsAwareLevel (2) — AR(1) skips v{i}_mu, RW keeps it
    • TestVarPassesResidualsToVolatility — CapturingVolatility integration test pinning the threading contract

Commits

  1. ffb13b7 — has_explicit_level on SVDynamics
  2. a0b8964 — data kwarg on Protocol + Constant
  3. 2873b9a — multivariate SV requires data; VAR threads OLS residuals
  4. 2359289 — per-variable priors + dynamics-aware level (core fix)
  5. 1593218 — CapturingVolatility integration test

Notes / follow-ups (out of scope)

  • P3's SV-VAR recovery test (TestSVVarRecovery) is currently softened to shape + finiteness only. With this fix, h-recovery should improve materially; a follow-up commit could re-strengthen the assertions. Deliberately deferred so this PR stays scoped.

🤖 Generated with Claude Code

thomaspinder and others added 5 commits May 18, 2026 20:18
RandomWalk.has_explicit_level = False, AR1.has_explicit_level = True
(both as ClassVar — true class-level constants, not user-configurable).
Multivariate SV adapter will use this in the next commit to skip the
redundant per-variable mu_i shift when the dynamics already carries the
log-vol level.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Protocol gains optional `data: np.ndarray | None = None` — the per-variable
(T, n_vars) series (typically OLS residuals from VAR) that stochastic
adapters use to seed per-variable priors. Constant accepts and ignores
for signature parity.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…#65)

Multivariate StochasticVolatility.build_pymc_latent now raises ValueError
if `data` is None or has the wrong shape — silent zeros-fallback was
the cause of #65. Per-variable prior threading lands in the next commit;
this commit only enforces the contract and threads OLS residuals from
VAR so the pipeline keeps building.

VAR._build_pymc_model computes one-shot OLS lstsq before model construction
and passes the (T_eff, n_vars) residual matrix as `data=resid`. Constant
adapters ignore it.

Pre-existing TestSVMultivariateBuild tests updated to pass a valid
data=np.zeros((T, n_vars)) argument; they care about model structure,
not prior quality.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…oses #65 #66)

For each variable i in the multivariate SV adapter:
- prior_params now come from build_priors(data[:, i]) — per-variable
  innovation series informs h0_mu, alpha_mu, mu_sigma, etc. instead of
  the np.zeros(T) placeholder that produced near-pinpoint mu_i ≈ 0 and
  h0_mu ≈ -18.4 (closes #65).
- mu_i is registered only when dynamics.has_explicit_level is False
  (random walk). For AR(1), the dynamics' alpha already carries the
  log-vol level — the outer shift was redundant (closes #66).

Standalone univariate StochasticVolatility.fit is unaffected: it goes
through _build_pymc_model, not build_pymc_latent. The byte-for-byte
regression test (tests/test_sv_standalone_unchanged.py) continues to
lock the standalone posterior.

Class docstring updated; the convention-divergence note about mu_i
folding into the log-vol level is replaced with the dynamics-aware
description.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…efs #65)

Integration test using a VolatilityProcess stand-in that records the
`data` it receives. Verifies VAR passes a (T_eff, n_vars) residual matrix
that is demeaned (intercept in X_full). Guards against silent regression
of the threading the previous commit set up.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@codecov-commenter

codecov-commenter commented May 18, 2026 •

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 92.4%. Comparing base (1d9fd87) to head (d91eeef).
⚠️ Report is 1 commits behind head on fix/constant-cache-cholesky-factor.

Additional details and impacted files
@@                         Coverage Diff                          @@
##           fix/constant-cache-cholesky-factor     #70     +/-   ##
====================================================================
+ Coverage                                92.3%   92.4%   +0.1%     
====================================================================
  Files                                      26      26             
  Lines                                    1126    1141     +15     
  Branches                                  107     111      +4     
====================================================================
+ Hits                                     1040    1055     +15     
  Misses                                     65      65             
  Partials                                   21      21             

☔ View full report in Codecov by Sentry.
📢 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.

@thomaspinder
thomaspinder merged commit 8ff239a into fix/constant-cache-cholesky-factor May 18, 2026
10 checks passed
@thomaspinder
thomaspinder deleted the fix/sv-data-informed-priors-and-intercept branch May 18, 2026 18:58

This branch was previously deployed

1 inactive deployment
docs-preview — d91eeef2 Deployed May 18, 2026 by thomaspinder via deploy-docs-preview #148
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