A MATLAB library for Bayesian state space models: estimate latent trends, track time-varying relationships, model stochastic volatility, and handle missing or mixed-frequency data. The library builds on the precision sampler of Chan and Jeliazkov (2009), which draws the entire state path jointly, with reusable routines for integrated likelihood evaluation and stochastic volatility sampling. Worked examples cover unobserved components, time-varying parameter, stochastic volatility and dynamic factor models.
run setup.m % adds core/ to the path
cd examples
ex01_precision_sampler % the precision sampler on a local level modelThe code needs MATLAB with the Statistics and Machine Learning Toolbox. setup.m also checks for
the Optimization Toolbox, which four of the archived packages use. The library and the examples
are tested on MATLAB R2025b, on Windows and, in the repository's continuous integration, on Linux.
Most of the methods are developed in the book Bayesian Macroeconometrics: Methods and Applications (Chapman & Hall/CRC, forthcoming): see the sample chapters and its code repository, with MATLAB, R and Python for all fourteen chapters. The missing-data draw of ex05 comes from Chan, Poon and Zhu (2023), and the TVP-MIDAS model of ex10 from Chan, Poon and Zhu (2026).
The how-to guides show how to use the core functions, one step at a time, on small examples with generated data. The first series draws the states of the local level model with the precision sampler, estimates its parameters by Gibbs sampling, and integrates the states out of its likelihood:
- How to Draw the States of an Unobserved Components Model
- How to Estimate a State Space Model by Gibbs Sampling
- How to Compute the Integrated Likelihood of a State Space Model
The second series does the same for a regression whose coefficients follow random walks, and then asks whether a coefficient varies at all:
- How to Draw Time-Varying Regression Coefficients
- How to Estimate a TVP Regression by Gibbs Sampling
- How to Tell Whether a Coefficient Varies over Time
The ten scripts in examples/ each run in under a minute. Start with ex01.
ex01-ex06 are linear Gaussian models. ex07-ex09 add stochastic volatility: ex07 and ex08 draw the
log-volatility by the auxiliary mixture sampler, and ex09, in which the volatility also enters the
mean, by accept-reject Metropolis-Hastings. ex10 combines the two groups: it imposes the linear
restriction on its time-varying weights with ssm.restrict, which ex05 uses for the quarterly
aggregation, and draws its volatility as ex07 does.
| Script | What it shows | Data |
|---|---|---|
ex01_precision_sampler |
The precision sampler, ssm.simulate_states, on a local level model, checked against a Kalman smoother and the truth |
generated; US CPI inflation |
ex02_integrated_likelihood |
Two models compared by marginal likelihood, with the states integrated out by ssm.intlike, checked against a Kalman filter and quadrature |
US PCE inflation |
ex03_tvp_regression |
A time-varying parameter Phillips curve, and the precision sampler timed against the Kalman filter with backward sampling | US PCE inflation and output gap; generated |
ex04_output_gap |
The output gap from a local linear trend with an AR(2) cycle, built with ssm.lagpolymat |
US real GDP |
ex05_missing_data |
Missing data and mixed frequencies with ssm.select_obs: the selection matrices of a small pattern, then a late start, a hole and a ragged edge in one VAR, and a series observed only through quarterly aggregates, both checked against dense algebra and the values removed |
generated |
ex06_dynamic_factor |
A dynamic factor model with one factor, as a business-cycle indicator | FRED-MD |
ex07_aux_mixture |
The auxiliary mixture sampler for stochastic volatility, ssm.ksc_rw_h0: the seven-component mixture against the exact density it approximates, the collapsed Gibbs sampler checked against the truth, and the draws reweighted to the exact posterior |
generated |
ex08_sv_ma_t |
Stochastic volatility with MA(1) and Student-t errors, the models of Chan and Hsiao (2014) with mean zero: the log-volatility drawn by ssm.ksc_ar1_mean, and the MA coefficient and the degrees of freedom by Metropolis-Hastings with normal proposals at modes found by ssm.mode_newton |
silver returns |
ex09_svm_armh |
Stochastic volatility in mean, with the log-volatility drawn by accept-reject Metropolis-Hastings, ssm.armh |
US CPI inflation |
ex10_tvp_midas |
A MIDAS regression with time-varying weights under a linear restriction, checked against dense algebra and the truth | generated |
examples/data/README.md gives the source of every data file and the
rows each example reads.
The tutorials answer one empirical question each with published models, on the
data in examples/data/, and can be read without MATLAB. Each comes with
a script that runs the same analysis on your own series.
- Which Unobserved Components Model Should I Use for Inflation? One with stochastic volatility in the transitory component: such models beat an AR(4) in forecasting average US CPI inflation over the next year, and stochastic volatility in the trend as well does not help.
- Which Parameters of a GDP Nowcasting Model Should Vary over Time? The coefficients of a mixed-frequency (MIDAS) regression: letting them vary improves the nowcasts of US real GDP growth, and letting the weights vary adds little.
Each package is kept byte for byte as published under replications/<paper>/legacy/, with a
permanent as-published/<paper> git tag and the source zip's md5 recorded in
provenance.md. Run the entry scripts from their own folders.
| If you want | Paper | Folder | Entry scripts |
|---|---|---|---|
| A trend inflation model whose trend stays within bounds | Chan, Koop & Potter (2013, JBES) | chan_koop_potter2013_jbes_trendbound |
ARtrend_bound.m |
| Stochastic volatility with moving average errors, for inflation | Chan (2013, JoE) | chan2013_joe_masv |
main_UCMA.m |
| Trend inflation, the NAIRU and the Phillips curve, with bounded time variation | Chan, Koop & Potter (2016, JAE) | chan_koop_potter2016_jae_boundedpc |
biUC.m, main_forecasting.m |
| Stochastic volatility in mean with time-varying parameters | Chan (2017, JBES) | chan2017_jbes_svm |
UC_SVM.m |
| Trend inflation tied to long-run inflation expectations | Chan, Clark & Koop (2018, JMCB) | chan_clark_koop2018_jmcb_trendie |
main_estimation.m, main_forecasting.m |
| The uncertainty of inflation expectations, from high-frequency data | Chan & Song (2018, JMCB) | chan_song2018_jmcb_inflrv |
main_inflation_RV.m |
| Trend-cycle decompositions of output compared by marginal likelihood | Grant & Chan (2017, JMCB) | grant_chan2017_jmcb_trendcycle |
main_UC.m |
| The Hodrick-Prescott filter as an unobserved components model | Grant & Chan (2017, JEDC) | grant_chan2017_jedc_hpfilter |
main_script.m |
| Stochastic volatility with heavy tails and serial dependence | Chan & Hsiao (2014, Wiley) | chan_hsiao2014_wiley_sv |
plainSV.m, MASV.m, MASVt.m |
| GARCH against stochastic volatility, for energy prices | Chan & Grant (2016, Energy Economics) | chan_grant2016_eneco_garchsv |
main_SV.m, main_GARCH.m, main_forecasting.m |
| Marginal likelihoods by the cross-entropy method | Chan & Eisenstat (2015, ER) | chan_eisenstat2015_er_mlce |
probit_mc.m, logit_mc.m, bin_t_mc.m, VAR_mc.m, TVPVAR_mc.m, DFVAR_mc.m |
| The deviance information criterion for latent variable models | Chan & Grant (2016, CSDA) | chan_grant2016_csda_dic |
SF.m, VAR1.m, TVPVAR.m, CTVPVAR.m, semireg.m |
| The observed-data deviance information criterion for volatility models | Chan & Grant (2016, JFEC) | chan_grant2016_jfec_dicsv |
main_SV.m |
| Tests of whether a model needs its time variation or its stochastic volatility | Chan (2018, ER) | chan2018_er_spectest |
main_NAIRU.m, main_UCSV.m |
tests/golden_runs/manifest.md names the few legacy scripts that
do not run as shipped.
Across the fourteen packages the same code recurs: the truncated normal sampler is copied into six
of them, and the function that builds the design matrix of a time-varying parameter regression
into four. The ssm package under core/ holds one copy of each step. Call the functions
directly, or copy the example closest to your model; every example but ex05 builds a complete
sampler from them.
Eleven of these functions, ssm.diffmat, ssm.geweke, ssm.inefficiency_factor,
ssm.ksc_ar1_mean, ssm.ksc_rw_diffuse, ssm.ksc_rw_h0, ssm.mcse, ssm.shaded_band,
ssm.specvar0, ssm.surform and ssm.tnormrnd, have the same code as their counterparts in
bvar-toolkit, apart from error identifiers and the
diagnostics' calls to ssm.specvar0. A unit test checks each against a pinned copy of the
bvar-toolkit original.
bvar.util.select_obs in bvar-toolkit copies ssm.select_obs, apart from its error identifier. The
Econometrics Toolbox has a class named ssm: calls such as ssm.simulate_states resolve to this
package, and ssm(...) still constructs the toolbox's state space object.
The subpackage ssm.models holds complete samplers built from these steps, each estimated in one
call. ssm.models.tvp_midas estimates the MIDAS regressions of
Chan, Poon and Zhu (2026), whose coefficients, weights and volatility can each vary over time, and
nowcasts the next period. The MIDAS tutorial calls it. ssm.models.uc
estimates unobserved components models, among them UCSV. Each has a random-walk trend and a gap.
The variances of the trend innovations and of the gap can each be constant or time-varying, and
the gap can have MA(1) errors. ssm.models.uc_forecast
forecasts with these models. The UC tutorial calls both.
The unit tests check each function against the code it comes from or replaces. ssm.surform and
ssm.tnormrnd are run beside every copy in the archived packages, four of SURform.m and six of
tnormrnd.m, and must give identical output,
ssm.tnormrnd draw for draw under a fixed seed. For ssm.simulate_states, the tests run the
published scripts UC.m, linreg_tvp.m and DFM.m on a short chain twice: once as published,
and once with the lines the function replaces swapped for a call to it. UC_SVM.m is run the same
way for ssm.armh, and the published functions sample_CSV.m and the book's
sample_SVM_h_ARMH.m are run on generated data beside ssm.armh, with the same target. Each of
these tests checks that the random number stream ends in the same state and that the draws agree
to rounding error.
ssm.ksc_ar1_mean is checked the same way against SV.m of Chan (2013), and
ssm.ksc_rw_diffuse against SVRW.m of Chan, Clark and Koop (2018), which draws the same path
when its initial value h0 is zero. ssm.ksc_rw_noncentered is checked draw for draw against a
copy of SVRW_gam.m of Chan (2018) that solves with the same Cholesky factor, with UCSV_gam.m
run whole.
ssm.intlike is checked against a Kalman filter and against two archived functions that compute
it, intlike_UC0.m and intlike_tvpvar.m. ssm.diffmat and ssm.lagpolymat are checked against the matrices the published
scripts write out, and ssm.select_obs against the worked illustrations of Chan, Poon and Zhu
(2023). ssm.restrict must equal, bit for bit, the update that ex05 and ex10 wrote out before
it existed, and its conditional mean and covariance must equal those from dense algebra.
ssm.models.tvp_midas runs on generated data whose restriction rows change from period to period,
and the restrictions must hold in every draw, those of the period to nowcast included. With
high-frequency regressors of zeros, the variances of the weights must keep their prior, and the
weights of the period to nowcast must have the moments of the random walk conditioned on every
restriction. ssm.midas_basis with averaged rows must equal, bit for bit, the loop it replaced in
the MIDAS tutorial. ssm.models.uc and ssm.models.uc_forecast must reproduce, draw for draw,
the code with which the UC tutorial's results were computed, in each of its four models. That code
is kept in tests/unit/private/. Their forecasts at every horizon must match paths simulated
forward from the same draws.
Files from other repositories that the tests run are held verbatim in
tests/fixtures/, with their md5s. On every push to main and every pull
request, CI checks the archived packages against their as-published tags and the fixtures
against their md5s, and runs the unit suite, setup.m, the examples, each tutorial's
your_data.m and each guide's guide.m. To run the tests locally:
run tests/unit/run_unit_tests.m
run tests/run_examples.mtests/variant_map.md records where each function's code comes from,
which archived copies it matches and which pairs must never be merged.
bvar-toolkit is the companion library for
Bayesian VARs. It also archives, as chan_jeliazkov2009_statespace, the replication package of
Chan and Jeliazkov (2009), which implements the precision sampler. The tests here run copies of
its UC.m and SVRW.m.
Every quarter, trend-cycle-toolkit re-estimates published models of US trend inflation, the output gap and trend output growth. Five of those models come from packages archived here: Chan (2013); Chan, Koop and Potter (2013); Grant and Chan (2017, JMCB); Grant and Chan (2017, JEDC); and Chan, Clark and Koop (2018).
Cite the paper behind each method you use: CITING.md maps every replication
package, library function and example to its paper, with BibTeX. To cite the toolkit itself:
Chan, J. C. C. (2026). statespace-toolkit: MATLAB code for Bayesian state space models. Zenodo. https://doi.org/10.5281/zenodo.22884655
The DOI resolves to the latest release. CITATION.cff holds the machine-readable record that
GitHub's "Cite this repository" button reads.
MIT; see LICENSE. The license covers the archived packages too. The one third-party
file, kde2d.m in grant_chan2017_jedc_hpfilter, the kernel density estimator of Botev,
Grotowski and Kroese (2010), keeps its own terms. NOTICE.md states what the license
covers.

