3. Structural VAR Modeling
An svar_model object represents a (possibly
Markov-switching) structural vector autoregression – the
structural form is parameterized and estimated directly, so
there is no separate “estimate a reduced form, then rotate it”
step. Almost everything else (data handling, estimation options,
forecasting, decompositions, fan charts, bootstrap, Bayesian
estimation, adding regime switching) works exactly as for
reduced-form VARs; see Reduced-form VAR Modeling for the full
worked example. This page covers what is specific to the
structural case.
3.1. The model
with \(r_{t} = 1, 2, \dots, h\) and transition probabilities \(p_{r_{t}, r_{t+1}}(I_{t})\), and structural shocks \(\varepsilon_{t} \sim N(0, I)\).
\(A_{0}(r_{t})\) is the contemporaneous-impact matrix. It is normalized to have a unit diagonal (
a0_i_i = 1), which fixes the scale of each structural shock.\(A_{1}, \dots, A_{p}\) are the lag-coefficient matrices, and \(C\) the coefficients on the deterministic / exogenous block \(x_{t}\) (constant, declared exogenous regressors, and lags of \(y\)).
The estimated parameters are named by analogy with the matrices:
a0(row, col)– contemporaneous coefficients (off-diagonal entries of \(A_{0}\); the diagonal is fixed at1).a1(row, col), …,ap(row, col)– lag coefficients (a(row, col), with the lag index omitted, refers to all lags).c(row, col)– coefficients on the exogenous / constant block.
row and col are integers or endogenous-variable names,
e.g. a0(R, PAI) or a1(2, 3).
3.2. Creating an SVAR
endog = {'PAIOIL','GROWTH','PAI','R','EXRATE'};
exog = {};
nlags = 4;
const = true;
mdl = svar_model(endog, ...
lag_length = nlags, ...
constant_term = const, ...
deterministic_vars = exog);
(The factory mirrors rfvar_model’s shape; the legacy
constructor signature was svar(varlist, exog, nlags, constant,
markov_chains) positionally.)
3.3. Identification
A structural VAR with \(n\) endogenous variables has
\(n(n - 1)/2\) more free parameters in \(A_{0}\) than
the data can pin down, so identifying restrictions must be
supplied. In RISE these are ordinary parameter restrictions on
a0 (and, if you wish, on the lag coefficients), passed to
estimate in the linear-restrictions cell array – the same
mechanism used for block-exogeneity restrictions in the
reduced-form VAR chapter.
A recursive (Cholesky-type) identification orders the variables and zeroes the entries of \(A_{0}\) above the diagonal, so that variable 1 is not affected contemporaneously by any other shock, variable 2 only by shock 1, and so on:
linres = {};
for ii = 1:numel(endog)
for jj = ii+1:numel(endog)
linres{end+1, 1} = sprintf('a0(%s,%s)=0', endog{ii}, endog{jj}); %#ok<SAGROW>
end
end
Here endog is the list given to svar_model, and the shock
epsilon<k> belongs to the k-th variable of that list. The
reduced-form counterpart, identify(m, 'choleski', shock_names) on
an rfvar_model declared with the same list, follows the same
declaration order and gives the same impulse responses, the k-th
shock name standing for epsilon<k> (see
Reduced-form VAR Modeling).
Exclusion (zero) restrictions can equally be imposed one at a
time, e.g. 'a0(PAIOIL,R)=0' (“the policy-rate shock has no
contemporaneous effect on oil-price inflation”), and you can mix
them with restrictions on the lag coefficients
('a1(PAIOIL,GROWTH)=0', …).
3.4. Estimation
Estimation is exactly as for a reduced-form VAR – pass the model, a database of time series (see Forecasting and simulation), the estimation sample, an (optional) prior, and the identifying restrictions:
mdlest = estimate(mdl, ...
data = db, ...
estim_start_date = date2serial(db.GROWTH.start), ...
estim_end_date = date2serial(db.GROWTH.finish), ...
estim_linear_restrictions = linres);
Bayesian estimation uses the same var_priors.minnesota
factory introduced in Reduced-form VAR Modeling:
var_prior = var_priors.minnesota(endog, const, exog, nlags, ...
tightness = 0.1, ...
lag_decay = 1.0, ...
ar_first_lag = 0.9);
mdlest_bayes = estimate(mdl, ...
data = db, ...
estim_start_date = date2serial(db.GROWTH.start), ...
estim_end_date = date2serial(db.GROWTH.finish), ...
estim_var_prior = var_prior, ...
estim_linear_restrictions = linres);
After estimation, inspect the structural form:
print_structural_form(mdlest)
3.5. Reduced-form and structural lag priors
An SVAR’s prior usually has two parts: a prior on \(A_{0}\) (the
identifying information, in estim_priors, restrictions and
endogenous priors) and a prior that shrinks the lags, typically toward
a random walk (in estim_var_prior). RISE offers two kinds of lag
prior, and they do not treat your prior on \(A_{0}\) the same way.
Reduced-form priors (minnesota, niw, inw,
generate_sims_zha_prior, …) are densities over the reduced form
\(B = A_{0}^{-1}[c\;A_{1} \cdots A_{L}]\) and
\(\Sigma_{u} = A_{0}^{-1}\mathrm{diag}(\sigma)^{2}A_{0}^{-\prime}\). For an
svar_model RISE evaluates them at the reduced form implied by the
structural parameters. The result is not a density over the structural
parameters \((A_{0}, A_{+}, \sigma)\), and no Jacobian can make it
one in general: the change of variables needs a one-to-one map, and
\((A_{0}, \sigma)\) has more free parameters than the
\(n(n+1)/2\) of \(\Sigma_{u}\) as soon as the SVAR is
over-parameterized relative to the reduced form (e.g. 24 against 21 in
Ahn and Rudd, 2025). The consequence is not cosmetic: integrating the
lags and variances out leaves a factor \(w(A_{0})\) that multiplies
the prior you put on \(A_{0}\). You write \(p(A_{0})\); the
model uses \(p(A_{0})\,w(A_{0})\). For the Baumeister-Hamilton
family the factor is known in closed form (Loria, 2026, Proposition 1);
in Ahn and Rudd it moves the posterior mode of \(A_{0}\) by up to
0.36.
Structural priors put the lag prior on the structural coefficients, conditional on \(A_{0}\). Writing equation \(i\) as \(a_{0,i}y_{t} = a_{+,i}x_{t} + \sigma_{i}e_{it}\),
(the second block is optional; the Gamma has shape \(\kappa_{i}\)
and rate \(\tau_{i} = \kappa_{i} a_{0,i} S a_{0,i}'\), and RISE
evaluates it as a density for the parameter \(\sigma_{i} > 0\)).
Given \(A_{0}\) this is a proper density over
\((A_{+}, \sigma)\): it integrates to one. The marginal prior of
\(A_{0}\) is therefore exactly the one you wrote, and there is
nothing to correct. They are built by
rise.engine.var.var_priors.generate_structural_prior and passed
through estim_var_prior like the reduced-form ones:
bh = rise.engine.var.var_priors.generate_structural_prior('bh', ...
endog, const, exog, nlags, db); % data: for S
mdlest = estimate(mdl, ...
data = db, ...
estim_var_prior = bh, ...
estim_priors = a0_priors, ...
estim_linear_restrictions = linres);
Two families:
'bh'– Baumeister and Hamilton (2015, 2018). Random-walk mean (\(E[A_{1} \mid A_{0}] = A_{0}\);ar_first_lagscales it,meanreplaces the whole map \(P\)), \(M_{i}\) diagonal with \(\lambda_{0}^{2}/(l^{2\lambda_{1}}S_{jj})\) for lag \(l\) of variable \(j\) and \(\lambda_{0}^{2}\lambda_{3}^{2}\) for the constant and each deterministic regressor. \(S\) is the covariance matrix of the residuals of univariate AR(nlags) regressions computed from the data you pass (or giveSdirectly). Optionslambda0(0.2),lambda1(1),lambda3(100),kappa(2),use_sigma_prior(true): the defaults are Ahn and Rudd’s values.'sims_zha'– Sims and Zha (1998) in its original structural form. The dummy observations \((Y_{d}, X_{d})\) of the reduced-form Sims-Zha prior (sameL1…L7and conventions) enter each structural equation as prior observations \(Y_{d}a_{0,i}' = X_{d}a_{+,i}' + \sigma_{i}e\), which stays conjugate: \(M_{i}^{-1} = X_{d}'X_{d}\) (the lag and constant dummies plus the sum-of-coefficients and co-persistence dummies) and \(P = (X_{d}'X_{d})^{-1}X_{d}'Y_{d}\). The Gamma block is the rest of the same dummy likelihood. (Sims and Zha normalize \(\sigma = 1\) with a free scale of \(A_{0}\); RISE’s parameterization with \(\sigma\) free is equivalent.)
The template also returns the pieces the conditional posterior needs
(conditional_prior, conditional_posterior: \(G_{i} = X'X +
M_{i}^{-1}\), the conditional posterior mean of \(a_{+,i}\) and the
residual sum of squares).
When to use which. Use a structural prior whenever the prior on \(A_{0}\) carries the identification – sign restrictions, informative priors on impact coefficients (Baumeister-Hamilton style), endogenous priors on impulse responses – and in any over-identified or set-identified SVAR: it is the only way to get the \(A_{0}\) prior you specify. It is also the prior to use to replicate papers built on the Baumeister-Hamilton or the structural Sims-Zha prior, with no correction term. Reduced-form priors remain the natural choice when the prior is genuinely about the reduced form (forecasting, a shared reduced-form prior across identification schemes) or to reproduce results obtained with them. Even with an exactly identified \(A_{0}\) (e.g. recursive), where a Jacobian would exist, RISE does not apply one, so the reweighting is there too.
What the structural prior covers. It is the prior of the lag,
constant and deterministic coefficients, and of each \(\sigma_{i}\)
when the Gamma block is on. A prior on any of them in estim_priors
would count twice and is an error
(setup_priors:StructuralPriorOverlap); with
use_sigma_prior = false the \(\sigma_{i}\) take the priors you
give them. Priors on \(A_{0}\) are always yours. The parameters it
covers still get the wide flat priors RISE creates for every VAR
coefficient, so that they are estimated; their contribution is
constant.
Defined parameters get no default prior. A parameter that
estim_nonlinear_restrictions defines as a function of others
('a0_y_p=-h12/(1-h12*h21)', as in Baumeister-Hamilton-type
identification through \(H = A_{0}^{-1}\)) is not free, so RISE
gives it none of these flat priors and does not estimate it; for a
switching parameter this holds state by state
('a0_y_p(reg,2)=...' leaves a0_y_p_reg_1 free). The order in
which the VAR prior and the restrictions are set does not matter. (A
prior on such a parameter would make it an estimated coordinate that
its definition overwrites: a dead direction of the search and a
singular Hessian.)
Regime switching. The prior is evaluated regime by regime, given \(a_{0,i}(r)\) and \(\sigma_{i}(r)\), with two rules that keep it a proper prior over the parameters that actually exist:
One evaluation per distinct parameter. A block (the lags of one equation, or its \(\sigma_{i}\)) that does not switch is evaluated once, not once per regime. (Reduced-form priors evaluate the whole prior once per regime, so a non-switching lag block is counted as many times as there are regimes.)
Mixed switching needs a reference regime. When a block does not switch but its conditioning set does – e.g. only \(\sigma\) switches, so the lag block is conditioned on a switching \(\sigma_{i}\) – the block is conditioned on the reference regime: option
reference_regimeof the factory, default 1 (the first state of every chain).estimateprints which blocks use it, and the full plan is inmdlest.model_data.estim_priors_data.var_prior_template.structural_plan.
Limits. svar_model only (reduced-form, panel and proxy VARs and
DSGE models raise svar_evaluator:StructuralPriorNotSvar); the
template’s variables, lags, constant and deterministic terms must match
the model’s (svar_evaluator:StructuralLayoutMismatch). With
exclusion restrictions on the lags, the Gaussian block is evaluated at
the restricted point over the full coefficient vector, as for the
reduced-form priors; it is then not the conditional density of the free
coefficients. See the tutorial Estimation/svar_structural_priors.
3.5.1. Integrating out the lags: estim_svar_integrate_lags
What it does. Given \(A_{0}\), a structural prior makes the posterior Gaussian in each equation’s lag, constant and deterministic coefficients \(a_{+,i}\) and, with the Gamma block, Gamma in \(\sigma_{i}^{-2}\). Both integrate out in closed form, equation by equation (Loria, 2026, Proposition 2):
with \(G_{i} = X'X + M_{i}^{-1}\) and \(\zeta_{i}\) the residual
sum of squares of the conditional regression of \(Y a_{0,i}'\) on
\(X\), prior term included. \(\log p(A_{0})\) is everything else
in RISE’s posterior: your priors on \(A_{0}\) (or on the parameters
\(A_{0}\) is derived from), restrictions, endogenous priors. With
estim_svar_integrate_lags = true the optimizer (estim_optimizer)
searches this marginal posterior, over those parameters only:
mdlest = estimate(mdl, ...
data = db, ...
estim_var_prior = bh, ... % 'bh' or 'sims_zha'
estim_priors = a0_priors, ...
solve_check_stability = false, ...
estim_svar_integrate_lags = true, ...
estim_svar_start = 'concentrated'); % optional, recommended
mdlest.model_data.estimation.posterior_maximization.svar_integrated
At the mode, the lags and \(\sigma\) are set to their conditional
posterior mode given \(A_{0}\) (the joint mode of
\((a_{+,i}, \sigma_{i})\), with \(\sigma_{i}^{2} = (2\tau_{i} +
\zeta_{i})/(T + k + 2\kappa_{i} + 1)\)), so the estimated model is
complete and irf, filter, forecast work as usual. The
summary svar_integrated holds the log marginal posterior at the mode
(log_marginal_posterior; the joint kernel there is the usual
log_post), the log marginal likelihood \(\log p(Y \mid A_{0})\),
the parameters searched and integrated, the Hessian of the marginal and
a Laplace marginal data density built on it (also reported as
log_marginal_data_density_laplace). The standard errors of the
integrated coefficients combine their conditional posterior covariance
given \(A_{0}\) with the uncertainty about \(A_{0}\) (law of
total variance, to first order). With estim_svar_start =
'concentrated' the multi-start search runs on the same marginal
posterior; use it, the marginal can have local modes. The marginal can
be evaluated at any point with
rise.engine.var.vartools.svar_integrated_posterior.
Why it helps. The search runs over \(A_{0}\) alone (18
parameters instead of 318 in Ahn and Rudd). And in Baumeister-Hamilton
type models, where the identifying information is on
\(H = A_{0}^{-1}\) and each column of \(H\) carries a unit
normalization, the joint posterior over all parameters has no mode:
when a column of \(H\) grows, the matching row of \(A_{0}\)
shrinks, \(\sigma_{i}\) and the lag block shrink with it, and the
joint density maximized over \((A_{+}, \sigma)\) grows like
\(\lvert h\rvert^{k+1}\), faster than a Student-t prior on
\(h\) pushes back. The joint optimizer runs off to infinity; the
marginal posterior of \(A_{0}\), which is what Baumeister and
Hamilton (and Ahn and Rudd) work with, has a mode. With the 'bh'
structural prior and this option, such models need neither a correction
term nor a custom mode search (the comparison with Ahn and Rudd’s own
numbers is still to be run). See the tutorial
Estimation/svar_integrated_lags.
When it qualifies. Otherwise estimate stops with an error; it
never falls back silently to the joint search.
svar_modelwith constant parameters. Under regime switching the likelihood mixes over regime paths and the integral is not closed-form (estimate:svarIntegrateSwitching); an EM profile search and a Gibbs sampler are planned.A structural prior in
estim_var_prior(estimate:svarIntegrateNoStructuralPrior), and no other prior on the lags or on a \(\sigma\) it covers (setup_priors:StructuralPriorOverlap,estimate:svarIntegrateOverlap).No endogenous prior involving the lags or \(\sigma\), including impulse responses and moments, and no endogenous prior given as a function handle, which cannot be checked (
estimate:svarIntegrateEndogenousPriors). Endogenous priors on \(A_{0}\) parameters are fine.Linear restrictions: exclusion restrictions on the lags (and lags tied to \(A_{0}\)) are integrated exactly over the free coefficients – the closed form above with \(G_{i} = D'(X'X + M_{i}^{-1})D\) and \(\kappa_{i} + (T + k - f_{i})/2\) in place of \(\kappa_{i} + T/2\), \(f_{i}\) the number of free coefficients. Restrictions tying lags of different equations, or a lag to \(\sigma\), a fixed or restricted \(\sigma\) under the Gamma block, lags in
estim_nonlinear_restrictions, derived lags andestim_general_restrictionsare refused (estimate:svarIntegrateRestrictions).solve_check_stability = false: the stability check truncates the region of the lags that the closed form integrates over (estimate:svarIntegrateOptions, as areestim_mle,estim_eval_lik = false,estim_blocksand missing data).
Without the Gamma block (use_sigma_prior = false) only the lags
are integrated out; each \(\sigma_{i}\) is searched with
\(A_{0}\) under its own prior, and the closed form is
\(-\tfrac{T}{2}\log 2\pi - \tfrac{1}{2}\log\lvert M_{i}\rvert -
\tfrac{1}{2}\log\lvert G_{i}\rvert - T\log\sigma_{i} -
\zeta_{i}/(2\sigma_{i}^{2})\) per equation.
Limits. Constant parameters only for now. The Laplace marginal data
density is an approximation over \(A_{0}\) only (the lags and
variances are integrated exactly). Parameters that
estim_nonlinear_restrictions define get no default prior and are not
searched. An estimated parameter that still moves no model parameter
– e.g. an \(A_{0}\) entry given a prior in estim_priors although
a definition overwrites it – is reported as inert and stays at its
start value (its posterior is its prior).
3.6. Starting values: estim_svar_start
The posterior-mode search starts, by default, from the prior start
values: \(A_{0} = I\), and whatever the priors (or the VAR prior’s
defaults) say for the lag coefficients, constants and shock standard
deviations; parameters with neither a prior nor a value get the
reduced-form OLS estimates under \(A_{0} = I\). This is
estim_svar_start = 'ols_identity', the default. That point can be
far from the mode, and it need not satisfy the identifying
restrictions on \(A_{0}\).
estim_svar_start = 'concentrated' starts somewhere better. Given
\(A_{0}\), the other blocks have closed-form conditional
maximum-likelihood values, from the OLS reduced form
\((\hat{B}, \hat{c}, \hat{\Omega})\) over the estimation sample:
Substituting them leaves a posterior in \(A_{0}\) alone – with
every prior, linear and nonlinear restriction, derived parameter and
endogenous prior, since RISE evaluates it with its own posterior
kernel. RISE maximizes it over the estimated \(A_{0}\) parameters
from estim_svar_start_trials starting points (default 10): the
prior start values, then draws from the \(A_{0}\) priors that
satisfy the restrictions. The best point goes to the optimizer:
mdlest = estimate(mdl, ...
data = db, ...
estim_var_prior = var_prior, ...
estim_linear_restrictions = linres, ...
estim_svar_start = 'concentrated', ...
estim_svar_start_trials = 10);
mdlest.model_data.estimation.posterior_maximization.svar_start
The summary svar_start records the log posterior at the chosen
start and at the 'ols_identity' one, the \(A_{0}\) parameters
searched over, and the number of posterior evaluations the search
cost (not included in funevals, which counts the optimizer’s).
What it guarantees, and what it does not:
Only estimated parameters move. Parameters without a prior keep their values (yours, or the
'ols_identity'seed). Linear restrictions are imposed after the lag, constant and sigma blocks are filled in, so a restricted lag coefficient keeps its restricted value.Never worse than the default. The
'ols_identity'start is evaluated too, and kept when its posterior is higher.It is a start, not an estimate. With priors or restrictions on the lags, the conditional ML blocks are not their conditional posterior modes; the optimizer takes it from there.
Cost. The search runs over the free \(A_{0}\) entries only (a Nelder-Mead search per starting point), whatever the number of lag coefficients. On a small model it can cost more evaluations than it saves the optimizer; it pays off as the number of lag parameters grows, and above all when the default start leads to a local mode.
svar_model only. On reduced-form, panel or proxy VARs (and DSGE models)
'concentrated'is an error.With
estim_svar_integrate_lagsthe same multi-start search runs on the integrated posterior of \(A_{0}\) instead (the lags and variances integrated out rather than set to their conditional ML values);svar_start.integratedis then true.
Under regime switching the start is a heuristic. The
constant-parameter fit above is computed with every regime tied to the
same parameters, then copied to every regime; the switching blocks are
perturbed (each shock standard deviation scaled down in the low states
and up in the high states of its chain, switching coefficients by a few
percent), the transition probabilities stay at their prior start
values, and the best of estim_svar_start_trials perturbations goes
to the optimizer. There is no EM step yet to pull the regimes apart
before the optimizer does. On a SVAR with volatility regimes, the
default start can end in a local mode far below the one this start
reaches (see the tutorial Estimation/svar_concentrated_start).
3.7. Impulse responses, decompositions, forecasting
Because the model is already structural, no identification
function is needed: the shock names are simply the structural
shocks (one per endogenous variable, by RISE’s naming convention),
and irf / variance_decomposition /
historical_decomposition / forecast are called directly:
myirfs = irf(mdlest); % all shocks, default horizon
myirfs = irf(mdlest, shock_names, 40);
vd = variance_decomposition(mdlest);
hd = historical_decomposition(mdlest);
fkst = forecast(mdlest, db, date2serial('2003Q1'));
For plotting (quick_irfs, plot_fanchart, plot_decomp,
…), parameter uncertainty via bootstrap, Bayesian posterior
sampling, fan charts of the decompositions and IRFs, and
conditional forecasting, see Reduced-form VAR Modeling – the calls
are identical, with a0 / a1 / … parameters in place of
the reduced-form b1 / b2 / … ones, and without the
extra Rfunc (identification) argument.
3.8. Adding regime switching
Pass a Markov-chain structure to the factory and list the
parameters it controls – for an SVAR these are typically a0
(switching contemporaneous transmission) and/or a1, …,
ap:
mc = struct();
mc.name = 'policy';
mc.number_of_states = 2;
mc.controlled_parameters = {'a0(R,:)'};
mc.endogenous_probabilities = [];
mc.probability_parameters = [];
mdl = svar_model(endog, ...
lag_length = nlags, ...
constant_term = const, ...
deterministic_vars = exog, ...
markov_chains = mc);
Time-varying transition probabilities are specified exactly as in
Reduced-form VAR Modeling (an endogenous_probabilities
definition plus the probability_parameters that enter it),
and the switching parameters are given priors through
estim_priors.
Proxy / instrumental SVARs are handled by the related Proxy (instrumental) SVAR Modeling object, and panels of (structural) VARs by the Panel VAR Modeling object.