SVAR identification II — external instruments and long-run restrictions¶
Vector autoregressions, Part 2 — Structural VAR: external instruments & long-run restrictions¶
This is the second half of **Part 2, *Structural VAR***. Its companion notebook (sign restrictions) replaced the recursive (Cholesky) ordering with sign restrictions; here we complete the modern identification toolkit with two further schemes, each pinning the structural impact matrix a different way:
- External-instrument / proxy SVAR (Stock-Watson 2012/18; Mertens-Ravn 2013) — bring in an outside variable correlated with the target shock but nothing else.
- Long-run restrictions (Blanchard-Quah 1989) — impose what a shock does in the limit (e.g. demand shocks are neutral for output in the long run).
Both are built in a new module svar_id.py that imports the bvar.py posterior and adds the identification (the original bvar.py is untouched; the svar_sign.py module from the sign-restriction notebook supplies the shared MA-IRF machinery).
Prior hyperparameters. Every macro reduced form below is fit with bvar.py's Minnesota prior at λ₁ = 0.2 (overall tightness), λ₂ = 0.5 (the traditional scalar cross-variable shrinkage of Litterman 1986 — not the data-driven default), and λ₃ = 1 (linear lag decay), with the funds-rate system given a near-unit-root own-persistence prior mean of 0.9.
Method 1 — the external-instrument (proxy) SVAR¶
The reduced-form residuals $\varepsilon_t = P u_t$ mix the structural shocks $u_t$; identification is choosing the column of $P$ for the shock of interest. Sign/Cholesky pick it from internal assumptions. A proxy picks it from an external instrument $z_t$ — an observed series that is relevant (correlated with the target shock $u_{1t}$) and exogenous (uncorrelated with the others). Then
$$\mathbb{E}[\varepsilon_t\,z_t] = P\,\mathbb{E}[u_t z_t] = P\begin{pmatrix}\phi\\0\\\vdots\\0\end{pmatrix} = \phi\,p_1,$$
so the covariance of the residuals with the instrument is the target impact vector $p_1$, up to scale — computed by simply regressing the residuals on $z_t$: $\hat b = (\varepsilon'z)/(z'z)$. We normalize $\hat b$ so the policy variable moves by 1 pp on impact, and trace $\Theta_h\hat b$ for the IRFs. Doing this for every bvar.py posterior draw gives the bands.
The instrument. For the US monetary shock we use the Romer & Romer (2004) narrative shock — the part of the funds-rate change that the FOMC's own Greenbook forecasts could not explain — from Ramey's (2016) replication data (ramey_monetary_data.csv, monthly 1969–2007). The VAR is the standard 5-variable system:
- log IP — the Industrial Production index (FRED series
INDPRO), the Federal Reserve's monthly index of real output in manufacturing, mining, and utilities. It is the workhorse real-activity variable in monthly monetary VARs, standing in for the business cycle because GDP is only quarterly. - unemployment rate,
- log CPI — the Consumer Price Index (the price level),
- log Pcom — an index of commodity prices (included to help tame the price puzzle), and
- FFR — the federal funds rate, the monetary-policy instrument.
The three index variables enter in logs ×100, so their impulse responses read directly as percent deviations from baseline (e.g. a −1.16% IP trough = industrial production about 1.2% below where it would otherwise be).
import numpy as np, pandas as pd, matplotlib.pyplot as plt
from statsmodels.tsa.api import VAR
import bvar, svar_id as si
d = pd.read_csv("ramey_monetary_data.csv")
Y = np.column_stack([d["LIP"]*100, d["UNEMP"], d["LCPI"]*100, d["LPCOM"]*100, d["FFR"]]) # logs x100 -> %
z = d["RRSHOCK"].values; lab = ["log IP", "Unemp", "log CPI", "log Pcom", "FFR"]; p = 12; H = 48
fit = bvar.filter_stationary(bvar.gibbs_minnesota(Y, p=p, own_mean=0.9, lam2=0.5, ndraw=3000, burn=1000, seed=1))
res = si.proxy_svar(fit, Y, z, policy_idx=4, H=H, norm=1.0) # normalize: shock raises FFR by 1pp
lo, med, hi = si.bands(res)
chol = VAR(pd.DataFrame(Y, columns=lab)).fit(p).irf(H).orth_irfs[:, :, 4] # recursive IRF, FFR ordered last
print("PROXY-SVAR (Romer-Romer instrument), monetary tightening normalized to +1pp FFR:")
print(" output (log IP) trough: %.2f%% at month %d (clear, LARGE contraction)" % (med[:, 0].min(), med[:, 0].argmin()))
print(" Cholesky output trough: %.2f%% (recursive VAR badly understates it)" % chol[:, 0].min())
print(" CPI response at 24m: %.2f%% (price puzzle: stubborn even with commodity prices)" % med[24, 2])
for k, i in enumerate([0, 2, 4]):
if k == 0: fig, ax = plt.subplots(1, 3, figsize=(14, 3.6))
ax[k].fill_between(range(H+1), lo[:, i], hi[:, i], color="darkgreen", alpha=.2, label="proxy 68% band")
ax[k].plot(med[:, i], color="darkgreen", lw=1.6, label="proxy-SVAR median")
ax[k].plot(chol[:, i], color="navy", lw=1.3, ls="--", label="Cholesky")
ax[k].axhline(0, color="0.6", lw=.6); ax[k].set_title("Response of %s" % lab[i]); ax[k].set_xlabel("months")
ax[0].legend(fontsize=7, loc="lower left")
fig.suptitle("External-instrument (proxy) SVAR: the Romer-Romer narrative shock identifies the monetary shock without an ordering.\n"
"Output contracts ~1% (far more than the recursive VAR); the price response stays puzzling (a well-known difficulty)", y=1.07)
plt.tight_layout(); plt.show()
PROXY-SVAR (Romer-Romer instrument), monetary tightening normalized to +1pp FFR: output (log IP) trough: -1.16% at month 33 (clear, LARGE contraction) Cholesky output trough: -0.35% (recursive VAR badly understates it) CPI response at 24m: 0.69% (price puzzle: stubborn even with commodity prices)
Reading the proxy-SVAR¶
- The external instrument reveals a big, credible output effect. The Romer-Romer-identified tightening drives output down ~1% — while the recursive Cholesky IRF (navy) has output barely moving (trough ~−0.35%). This is Coibion's (2012) reconciliation: narrative/external identification implies substantially larger real effects of monetary policy than a recursive VAR, which contaminates the shock with systematic policy reactions. No ordering was assumed — the instrument did the work.
- The price puzzle is stubborn. Even with a narrative instrument and commodity prices in the system, the CPI response stays positive for years. That is not a bug in the method; it is a genuine, much-debated empirical difficulty (Ramey 2016) — a reminder that credible identification is necessary but not sufficient to make every response behave.
- Relevance matters. A weak instrument (low correlation with the shock) gives an imprecise, unreliable impact vector — the proxy-SVAR's Achilles heel, the analogue of weak instruments in IV regression.
Method 2 — long-run restrictions (Blanchard-Quah)¶
Instead of restricting the impact, restrict the permanent effect. In a VAR whose first variable is a growth rate, the cumulative response is the level, and its long-run value is governed by the long-run multiplier $\Xi=(I-\sum_l\Pi_l)^{-1}$. Blanchard-Quah (1989) identify two shocks by declaring that only one has a permanent effect on output: the long-run structural matrix $\Xi P$ must be lower-triangular, so
$$\Xi P = \operatorname{chol}\!\big(\Xi\,\Sigma\,\Xi'\big),\qquad\Longrightarrow\qquad P=\Xi^{-1}\operatorname{chol}\!\big(\Xi\Sigma\Xi'\big).$$
The result is a supply shock (permanent effect on output — technology, the production frontier) and a demand shock (only transitory — it dies out as prices adjust). We fit it on $[\text{output growth},\ \text{unemployment}]$ (Blanchard-Quah's original pairing) from the quarterly macrodata, and plot the cumulative output response so the permanent-vs-transitory distinction is visible.
import statsmodels.api as sm
mdd = sm.datasets.macrodata.load_pandas().data
Yb = np.column_stack([400*np.diff(np.log(mdd["realgdp"].values)), mdd["unemp"].values[1:]]) # [output growth, unemployment]
fb = bvar.filter_stationary(bvar.gibbs_minnesota(Yb, p=2, lam2=0.5, ndraw=4000, burn=1000, seed=1))
rb = si.blanchard_quah(fb, H=40); irf = rb["irf"] # (ndraw, H+1, 2, 2): [.,h,var,shock]
sgn = np.sign(irf[:, 0, 0, :])[:, None, None, :]; irf = irf * sgn # normalize each shock to raise output on impact
cumout = np.cumsum(irf[:, :, 0, :], axis=1) # cumulative output level (level = cumsum of growth)
co_lo, co_md, co_hi = np.percentile(cumout, [16, 50, 84], axis=0); un_md = np.median(irf[:, :, 1, :], axis=0)
print("BLANCHARD-QUAH long-run restrictions [output growth, unemployment]:")
print(" cumulative OUTPUT to PERMANENT (supply) shock: h=0,20,40 = %s (settles: permanent)" % np.round(co_md[[0,20,40],0], 2))
print(" cumulative OUTPUT to TRANSITORY (demand) shock: h=0,20,40 = %s (returns to ~0)" % np.round(co_md[[0,20,40],1], 2))
fig, ax = plt.subplots(1, 2, figsize=(12, 3.6)); labs = ["permanent (supply) shock", "transitory (demand) shock"]; cs = ["firebrick", "steelblue"]
for k in range(2):
ax[0].fill_between(range(41), co_lo[:, k], co_hi[:, k], color=cs[k], alpha=.15); ax[0].plot(co_md[:, k], color=cs[k], lw=1.6, label=labs[k])
ax[1].plot(un_md[:, k], color=cs[k], lw=1.6, label=labs[k])
for a, t in zip(ax, ["Cumulative OUTPUT level response", "Unemployment response"]):
a.axhline(0, color="0.6", lw=.6); a.set_title(t); a.set_xlabel("quarters"); a.legend(fontsize=8)
fig.suptitle("Long-run (Blanchard-Quah) identification: the SUPPLY shock moves output permanently; the DEMAND shock only transitorily", y=1.03)
plt.tight_layout(); plt.show()
BLANCHARD-QUAH long-run restrictions [output growth, unemployment]: cumulative OUTPUT to PERMANENT (supply) shock: h=0,20,40 = [2.39 2.6 2.51] (settles: permanent) cumulative OUTPUT to TRANSITORY (demand) shock: h=0,20,40 = [2.2 0.6 0.11] (returns to ~0)
Results — three identifications, three kinds of restriction¶
- Long-run restrictions work cleanly here. The supply shock lifts output to a permanent ~2.5% higher level; the demand shock's output effect dies out — exactly the Blanchard-Quah decomposition, with both shocks lowering unemployment on impact (Okun's law). Where the restriction is credible (output is driven by a permanent technology component) it is very informative.
- A menu, not a winner. Across this Part 2 the same reduced form has been identified four ways — recursive, sign, external-instrument, long-run — each imposing a different kind of belief (a timing zero, an IRF sign, an outside instrument, a limit). None is universally right; the craft is matching the restriction you can defend to the question you are asking. The external instrument is the current gold standard for a single well-measured shock; long-run and sign restrictions shine when a credible instrument is unavailable.
- All of it rides the same posterior. Every band here is a function of the
bvar.pyreduced-form draws — identification and Bayesian inference compose cleanly.
References¶
- Stock, J. H. & Watson, M. W. (2018). Identification and estimation of dynamic causal effects with an application to external instruments. Economic Journal 128, 917–948.
- Mertens, K. & Ravn, M. O. (2013). The dynamic effects of personal and corporate income tax changes in the US. American Economic Review 103, 1212–1247.
- Romer, C. D. & Romer, D. H. (2004). A new measure of monetary shocks: derivation and implications. AER 94, 1055–1084. • Coibion, O. (2012). Are the effects of monetary policy shocks big or small? AEJ: Macro 4, 1–32.
- Blanchard, O. J. & Quah, D. (1989). The dynamic effects of aggregate demand and supply disturbances. AER 79, 655–673.
Next: bvar_identification_R.ipynb — the R cross-check (vars::BQ + a proxy-SVAR).