29. Policy Counterfactuals from Impulse Responses#
29.1. Overview#
How would an economy respond to its shocks if policy followed a different rule from the one that generated the historical data used to estimate a model?
Lucas [1976] taught us that a reliable answer requires a model whose private-sector decision rules change appropriately when the policy rule changes.
McKay and Wolf [2023] claim that, in a particular class of linear models, an econometrician can construct such Lucas-critique-robust counterfactuals without writing down the model.
The econometrician needs instead what they call “dynamic causal effects” of policy shocks — surprise deviations from a prevailing policy rule, both contemporaneous and anticipated (“news”) — together with the effects of the non-policy shock of interest under the prevailing policy rule.
This lecture puts their result to work in the context of a generalized version of the market for a single good studied in Dynamic Supply and Demand Curves and Induced Priors for Impulse Responses.
The market in those lectures has no policy of the type that concerns McKay and Wolf; we add one here: a per-unit tax on the good that the government adjusts according to a rule that leans against movements in the price.
The lecture
shows that the market with a tax fits McKay and Wolf’s framework, and that their key assumption is precisely the invariance of the open-loop dynamic supply and demand curves of Dynamic Supply and Demand Curves,
verifies their identification result: impulse responses to tax shocks under one rule deliver the economy’s exact behavior under another rule,
compares it with the older approach of Sims and Zha [2006], which imposes the new rule with a sequence of policy surprises and so falls foul of the Lucas critique, and examines a version of the defense of that approach by Leeper and Zha [2003] for “modest” policy interventions,
studies how well the counterfactual can be approximated with evidence on only a few policy shocks,
contrasts the uncertainty about a counterfactual that a structural modeler inherits from a prior over parameters, as in Induced Priors for Impulse Responses, with the precision that McKay and Wolf’s sufficient statistics deliver,
uses the market to sort procedures into those that require invertibility and those that do not, and
relates McKay and Wolf’s approach to the “dynamic programming squared” response to the Lucas critique, and states what their counterfactuals take for granted.
The invertibility item connects this lecture to the theme of Dynamic Supply and Demand Curves and Induced Priors for Impulse Responses, in which the supply shock is not fundamental for prices and quantities.
In this market,
external instruments recover the supply-shock impulse response that no rotation of VAR innovations can,
whether the supply shock is fundamental depends on the policy rule — a version of the Lucas critique that applies to VAR identification itself — because a price-stabilizing tax rule is observationally equivalent to making suppliers more flexible, and
McKay and Wolf’s route to counterfactual second moments through VAR innovations is exact only under invertibility, although in this market the error it makes otherwise turns out to be tiny.
29.1.1. Two responses to the Lucas critique#
The “dynamic programming squared” lectures of this series, such as Time Inconsistency of Ramsey Plans, Sustainable Plans for a Calvo Model, Stackelberg Plans, Optimal Taxation with State-Contingent Debt, and Accounting for Monetary and Fiscal Policy, pursue another modern response to the Lucas critique.
McKay and Wolf and dynamic programming squared compares the two responses and lists what McKay and Wolf’s counterfactuals take for granted.
Policy Regimes and the Lucas Critique pursues a third response: it treats rule changes as realizations of a Markov process that private agents know (When policy regimes switch).
In addition to what’s in Anaconda, this lecture uses the quantecon library.
!pip install --upgrade quantecon
We’ll make these imports:
import numpy as np
import quantecon as qe
import matplotlib.pyplot as plt
from quantecon import LQ
29.2. A market with a tax#
We use the market of Dynamic Supply and Demand Curves with the same parameter values.
Its supply side — competitive firms facing quadratic costs of adjusting output, with each firm taking the market price as given — is a close cousin of the model in Rational Expectations Equilibrium, where the “big \(Y\), little \(y\)” device corresponds to the “big \(X\), little \(x\)” device of Dynamic Supply and Demand Curves.
h_s = h_d = 1.0
g_s, g_d = 10.0, 0.1
β = 1 / 1.05
a = np.array([1, .8, .6, .4, .2]) # a(L): coefficients a0..a4
# MA polynomials for the shocks (coefficients on L^0..L^3)
B_d = np.convolve(np.convolve([1, .6], [1, .4]), [1, .2])
B_s = np.convolve(np.convolve([1, -.8], [1, .4]), [1, .2])
σ_s, σ_d = np.sqrt(0.5), 2.0 # shock standard deviations
As in Induced Priors for Impulse Responses, we work with a favorable supply shock \(\tilde w_{st} = -w_{st}\) and a positive demand shock \(\tilde w_{dt} = -w_{dt}\), each one standard deviation in size, so that both raise quantity.
The government levies a tax \(\tau_t\) per unit of the good, paid by suppliers, so that suppliers receive \(p_t - \tau_t\) when demanders pay \(p_t\); a negative \(\tau_t\) is a subsidy.
The supplier now maximizes
while the demander’s problem is unchanged.
The two Euler equations become
and eliminating \(p_t\) gives the market Euler equation \(\mathbb{E}_t\, \psi(L) q_t = -(s_t + d_t + \tau_t)\), with \(\psi(L) = h_s + h_d + g_s(1-\beta L^{-1})(1-L) + g_d\, a(\beta L^{-1})a(L)\), in which the tax enters exactly like a shock.
The government sets the tax by the rule
With \(\phi > 0\) the government subsidizes the good when its price is high and taxes it when its price is low, which stabilizes the price.
The policy shock \(\nu_{\ell, t-\ell}\) is a deviation from the rule at date \(t\) that was announced at date \(t - \ell\): \(\nu_{0,t}\) is a contemporaneous surprise, while \(\nu_{\ell, t-\ell}\) with \(\ell > 0\) is news about future policy.
29.2.1. The McKay–Wolf framework#
McKay and Wolf write a linear economy as two blocks,
where a variable written without a time subscript denotes its entire time path, as in \(x = (x_0, x_1, \ldots)\).
Here \(x\) collects observable outcomes, \(z\) the policy instrument, \(\varepsilon\) the non-policy shocks, and \(\nu\) the full menu of contemporaneous and news policy shocks.
Their key assumption is that the private-sector block — the matrices \(H\) — does not depend on the coefficients \((A_x, A_z)\) of the policy rule: policy affects private decisions only through the expected path of the instrument.
Our market satisfies this assumption, with observables \((q, p)\), instrument \(z = \tau\), and no unobserved endogenous variables \(w\).
To keep notation light, from now on we stack the observables and the instrument into one vector of paths, \(x = (q, p, \tau)\), and write the policy rule as \(R\, x = \nu\).
The private-sector block consists of the Euler equations (29.1)–(29.2), which do not involve \(\phi\).
These are the open-loop dynamic supply and demand curves of Dynamic Supply and Demand Curves, valid for an arbitrary path of prices — and now taxes — that the agents take as given.
The closed-loop decision rules that obtain inside a rational expectations equilibrium do depend on \(\phi\), because the equilibrium price process does: that dependence is the Lucas critique.
McKay and Wolf’s assumption would fail if, for example, agents had less information than the government and had to infer something about the economy from the tax rate.
Then the coefficients of the policy rule would affect what agents learn from observing the tax, as in the environments of Knowing the Forecasts of Others, where decision makers extract signals about hidden states from equilibrium prices and quantities.
In our market, agents see the shocks \(w_{st}, w_{dt}\) and know the policy rule.
29.2.2. Solving in sequence space#
Because the economy is linear, the impulse response to a shock that occurs at date 0 is the perfect-foresight path that follows it, with the shock’s entire future consequences — including any announced future deviations from the rule — known at date 0.
Following McKay and Wolf, we compute such paths by stacking the equations for \(t = 0, 1, \ldots, T-1\) into one linear system.
We set \(q_t = 0\) for \(t < 0\) (the economy starts at its steady state) and for \(t \ge T\) (impulse responses have died out by date \(T\)).
This is the finite-horizon approach of Classical Control with Linear Algebra: the stacked first-order conditions form one banded linear system, and as \(T\) grows its solution converges to the infinite-horizon solution that Dynamic Supply and Demand Curves obtains by factoring the characteristic polynomial into stable and unstable parts.
The terminal condition \(q_t = 0\) for \(t \ge T\) selects the bounded solution.
McKay and Wolf assume that both the baseline and the counterfactual rules deliver a unique bounded equilibrium, and we check below that our answers do not depend on \(T\).
The term \(g_d a(\beta L^{-1}) a(L) q_t\) in (29.2) equals \(g_d \sum_{i=0}^4 \sum_{k=0}^4 a_i a_k \beta^i q_{t+i-k}\).
T = 200 # length of the stacked paths
def private_block(g_s, g_d, T=T, h_s=h_s):
"""
Stack the Euler equations for t = 0, ..., T-1.
Unknowns are ordered x = (q, p, τ), each a path of length T, and the
equations read H @ x = (s, -d).
"""
Φ_s, Φ_d = np.zeros((T, T)), np.zeros((T, T))
for t in range(T):
Φ_s[t, t] += h_s + g_s * (1 + β)
if t >= 1:
Φ_s[t, t - 1] -= g_s
if t + 1 < T:
Φ_s[t, t + 1] -= g_s * β
Φ_d[t, t] += h_d
for i in range(5):
for k in range(5):
if 0 <= t + i - k < T:
Φ_d[t, t + i - k] += g_d * a[i] * a[k] * β**i
I, O = np.eye(T), np.zeros((T, T))
return np.block([[-Φ_s, I, -I], # supply: p - τ - Φ_s q = s
[Φ_d, I, O]]) # demand: p + Φ_d q = -d
def rule_block(φ, T=T):
"The rule τ = -φ p + ν written as R @ x = ν."
return np.hstack([np.zeros((T, T)), φ * np.eye(T), np.eye(T)])
def system_inverse(g_s, g_d, φ, h_s=h_s):
"Inverse of the stacked system: x = inverse @ (s, -d, ν)."
return np.linalg.inv(np.vstack([private_block(g_s, g_d, h_s=h_s), rule_block(φ)]))
def forcing(shock, T=T, σ_s=σ_s):
"Right-hand side for a favorable supply shock (0) or a positive demand shock (1)."
s, d = np.zeros(T), np.zeros(T)
if shock == 0:
s[:4] = -σ_s * B_s
else:
d[:4] = -σ_d * B_d
return np.concatenate([s, -d, np.zeros(T)])
def split(x):
"Split a stacked path into (q, p, τ)."
return x[:T], x[T:2 * T], x[2 * T:]
To check that the truncation does not matter, we solve for the response to a demand shock with \(T = 150\) and \(T = 300\) under three very different rules and compare the first 40 periods.
for φ in [-0.5, 0.5, 10.0]:
paths = []
for T_check in [150, 300]:
M_check = np.vstack([private_block(g_s, g_d, T_check), rule_block(φ, T_check)])
paths.append(np.linalg.solve(M_check, forcing(1, T_check))[:40])
print(f"φ = {φ:5}: max |T=150 - T=300| = {np.abs(paths[0] - paths[1]).max():.1e}")
φ = -0.5: max |T=150 - T=300| = 5.6e-17
φ = 0.5: max |T=150 - T=300| = 5.6e-17
φ = 10.0: max |T=150 - T=300| = 1.7e-16
Before going on, we check that with no tax (\(\phi = 0\), \(\nu = 0\)) the sequence-space solution reproduces the optimal-linear-regulator solution of Dynamic Supply and Demand Curves, whose code we repeat in the hidden cell below.
shock 0: max |sequence space - regulator| over 30 periods = 2.2e-06
shock 1: max |sequence space - regulator| over 30 periods = 1.7e-05
The two methods agree up to the tiny ridge that regularizes the regulator’s Riccati equation.
29.3. How the policy rule shapes the response to shocks#
We take \(\phi = 0.5\) as the baseline rule that has prevailed historically, and compare it with no policy (\(\phi = 0\)) and with aggressive price stabilization (\(\phi = 2\)).
φ_A = 0.5 # baseline rule
H_plot = 20 # horizons to plot
names = ["quantity $q$", "price $p$", "tax $\\tau$"]
shock_names = ["supply shock $\\tilde w_s$", "demand shock $\\tilde w_d$"]
fig, axes = plt.subplots(3, 2, figsize=(12, 9))
for φ, ls in [(0.0, ':'), (φ_A, '-'), (2.0, '--')]:
M_inv = system_inverse(g_s, g_d, φ)
for k in range(2):
paths = split(M_inv @ forcing(k))
for i in range(3):
axes[i, k].plot(paths[i][:H_plot], ls, lw=2, label=f"$\\phi = {φ}$")
for i in range(3):
for k in range(2):
axes[i, k].axhline(0, color='k', lw=0.5)
axes[i, k].set_title(f"response of {names[i]} to {shock_names[k]}")
axes[0, 0].legend()
for ax in axes[-1]:
ax.set_xlabel("periods")
plt.tight_layout()
plt.show()
Fig. 29.1 Responses under three policy rules#
A more aggressive rule damps the price response to a demand shock by subsidizing the good when its price rises, and quantity rises by more.
Every one of these responses reflects how forward-looking suppliers and demanders adjust their plans to the rule: the closed-loop decision rules change with \(\phi\).
29.3.1. Price stabilization as more flexible supply#
When \(\tau_t = -\phi p_t\), suppliers receive \(p_t - \tau_t = (1 + \phi) p_t\), and the supply Euler equation (29.1) becomes
Dividing by \(1 + \phi\) shows that, as far as \((q_t, p_t)\) are concerned, the market with the tax rule behaves exactly like a market without a tax whose supplier has
To someone who observes only prices and quantities, a price-stabilizing rule is indistinguishable from suppliers who are more flexible — with lower costs of producing and of adjusting output — and who face smaller cost shocks.
(Someone who also observes the tax can tell the two markets apart.)
We confirm the equivalence numerically.
for φ in [0.5, 2.0]:
gaps = []
for shock in range(2):
q1, p1, _ = split(system_inverse(g_s, g_d, φ) @ forcing(shock))
q2, p2, _ = split(system_inverse(g_s / (1 + φ), g_d, 0.0, h_s=h_s / (1 + φ))
@ forcing(shock, σ_s=σ_s / (1 + φ)))
gaps.append(max(np.abs(q1 - q2).max(), np.abs(p1 - p2).max()))
print(f"φ = {φ}: max |rule economy - flexible-supply economy| = {max(gaps):.1e}")
φ = 0.5: max |rule economy - flexible-supply economy| = 2.0e-15
φ = 2.0: max |rule economy - flexible-supply economy| = 1.8e-15
The equivalence explains a feature of the previous figure: under a more aggressive rule, quantity responds more strongly to a demand shock, just as it would if suppliers could adjust output more easily.
The equivalence also states the Lucas critique compactly in our market.
An econometrician who fit the dynamic supply curve to data generated under the rule, ignoring the tax, would estimate the cost parameters \(h_s/(1+\phi)\) and \(g_s/(1+\phi)\), not \(h_s\) and \(g_s\), and would mispredict what happens if the rule changes.
Later on, the same equivalence will explain why the policy rule affects whether a VAR can recover the supply shock.
29.4. McKay and Wolf’s identification result#
Their result combines two ingredients measured under the baseline rule: the responses to policy shocks, which we compute first, and the response to the non-policy shock of interest.
29.4.1. The causal effects of policy shocks#
A policy shock \(\nu_{\ell,0}\) announces at date 0 that the tax will deviate from the rule at date \(\ell\).
Column \(\ell\) of the matrix \(\Theta_{\nu}\) below collects the responses of \((q, p, \tau)\) to such a shock of size one under the baseline rule.
M_inv_A = system_inverse(g_s, g_d, φ_A)
Θ_ν = M_inv_A[:, 2 * T:] # 3T x T: responses to news at every horizon
fig, axes = plt.subplots(1, 3, figsize=(14, 4))
for ℓ, color in zip([0, 4, 8], ['C0', 'C1', 'C2']):
paths = split(Θ_ν[:, ℓ])
label = "surprise ($\\ell = 0$)" if ℓ == 0 else f"news about date {ℓ}"
for i in range(3):
axes[i].plot(paths[i][:H_plot], color=color, lw=2, label=label)
for i in range(3):
axes[i].axhline(0, color='k', lw=0.5)
axes[i].set_title(f"response of {names[i]}")
axes[i].set_xlabel("periods")
axes[0].legend()
plt.tight_layout()
plt.show()
Fig. 29.2 Responses to tax surprises and news#
News of a future tax increase lowers quantity well before the tax rises: suppliers, who find it costly to adjust output, begin cutting it as soon as they learn of the tax.
These anticipation effects make the whole policy path matter for private decisions.
29.4.2. Proposition 1#
Let \(x_A(\varepsilon)\) be the response of \(x = (q, p, \tau)\) to a non-policy shock \(\varepsilon\) under the baseline rule, and consider a counterfactual rule \(\tau_t = -\tilde\phi\, p_t\), written as \(\tilde R\, x = 0\).
McKay and Wolf’s Proposition 1 says that the counterfactual response is given by the formula below, where, following their notation, the subscript \(A\) marks the baseline rule and \(\tilde A\) the counterfactual rule \(\tilde R\):
The logic is that suppliers and demanders care only about the expected path of the tax.
A date-0 combination of contemporaneous and news shocks \(\tilde\nu\) can produce any expected tax path, in particular the one that the counterfactual rule would produce.
Because the shocks are all announced at date 0, the new rule holds both ex post along the path and ex ante, in agents’ expectations from date 0 on.
The econometrician needs \(x_A(\varepsilon)\) and \(\Theta_\nu\) — objects that can be estimated from data generated under the baseline rule — but not the model’s equations, nor even the baseline rule.
def mw_counterfactual(M_inv_base, x_A, φ_new):
"McKay–Wolf Proposition 1: enforce τ = -φ_new p with date-0 policy shocks."
Θ = M_inv_base[:, 2 * T:]
R_new = rule_block(φ_new)
ν = np.linalg.solve(R_new @ Θ, -R_new @ x_A)
return x_A + Θ @ ν, ν
29.4.3. The Sims–Zha alternative#
Sims and Zha [2006] proposed to enforce a counterfactual rule with a single kind of policy shock, the contemporaneous surprise \(\nu_{0,t}\), choosing a new value at each date so that the rule holds period by period.
This uses less information — only the first column of \(\Theta_\nu\) — but the rule then holds only ex post: at each date, agents expect the baseline rule to apply in the future and are surprised again and again.
A surprise at date \(t\) has the same effects as one at date 0, shifted \(t\) periods, and no effect before date \(t\).
def sims_zha(M_inv_A, x_A, φ_new):
"Enforce τ = -φ_new p ex post with a sequence of contemporaneous surprises."
θ0 = M_inv_A[:, 2 * T] # response to a date-0 surprise
Θ_sz = np.zeros((3 * T, T))
for t in range(T):
for b in range(3): # shift each block (q, p, τ) by t periods
Θ_sz[b * T + t:(b + 1) * T, t] = θ0[b * T:(b + 1) * T - t]
R_new = rule_block(φ_new)
ν = np.linalg.solve(R_new @ Θ_sz, -R_new @ x_A)
return x_A + Θ_sz @ ν, ν
We compute the response to a demand shock under aggressive price stabilization, \(\tilde\phi = 2\), in three ways: by solving the model under the new rule (the truth), by McKay and Wolf’s method, and by Sims and Zha’s.
φ_new = 2.0
x_A = M_inv_A @ forcing(1)
x_true = system_inverse(g_s, g_d, φ_new) @ forcing(1)
x_mw, ν_mw = mw_counterfactual(M_inv_A, x_A, φ_new)
x_sz, ν_sz = sims_zha(M_inv_A, x_A, φ_new)
print(f"max |McKay–Wolf - truth| = {np.abs(x_mw - x_true).max():.1e}")
print(f"max |Sims–Zha - truth| = {np.abs(x_sz - x_true).max():.1e}")
fig, axes = plt.subplots(1, 4, figsize=(16, 4))
for i in range(3):
ax = axes[i]
ax.plot(split(x_A)[i][:H_plot], 'k:', lw=2, label=f"baseline rule $\\phi = {φ_A}$")
ax.plot(split(x_true)[i][:H_plot], 'C0', lw=2, label=f"true, $\\tilde\\phi = {φ_new}$")
ax.plot(split(x_mw)[i][:H_plot], 'C0o', ms=4, label="McKay–Wolf")
ax.plot(split(x_sz)[i][:H_plot], 'C3--', lw=2, label="Sims–Zha")
ax.axhline(0, color='k', lw=0.5)
ax.set_title(f"{names[i]}, demand shock")
ax.set_xlabel("periods")
axes[0].legend(fontsize=8)
axes[3].bar(np.arange(H_plot) - 0.2, ν_mw[:H_plot], width=0.4, color='C0',
label="McKay–Wolf: news at date 0 about date $t$")
axes[3].bar(np.arange(H_plot) + 0.2, ν_sz[:H_plot], width=0.4, color='C3',
label="Sims–Zha: surprise at date $t$")
axes[3].axhline(0, color='k', lw=0.5)
axes[3].set_title("policy shocks used")
axes[3].set_xlabel("$t$")
axes[3].legend(fontsize=8)
plt.tight_layout()
plt.show()
max |McKay–Wolf - truth| = 6.7e-16
max |Sims–Zha - truth| = 2.0e-01
Fig. 29.3 Counterfactual responses to a demand shock#
McKay and Wolf’s construction reproduces the true counterfactual to machine precision.
The Sims–Zha construction does not: because suppliers and demanders keep expecting the old rule, they plan for less price stabilization than they actually get.
Because the error comes from expectations, we expect it to grow with the weight that agents place on the future, and the supplier’s adjustment cost \(g_s\) governs how forward-looking supply is.
We compute, for a range of values of \(g_s\), the error in the counterfactual response to a demand shock when the rule moves from \(\phi_A = 0.5\) to \(\tilde\phi = 2\).
g_grid = np.geomspace(0.1, 30, 12)
sz_errors = []
for g in g_grid:
M_inv = system_inverse(g, g_d, φ_A)
x_A_g = M_inv @ forcing(1)
x_true_g = system_inverse(g, g_d, φ_new) @ forcing(1)
x_sz_g, _ = sims_zha(M_inv, x_A_g, φ_new)
sz_errors.append([np.abs(split(x_sz_g)[i] - split(x_true_g)[i]).max()
/ np.abs(split(x_true_g)[i]).max() for i in range(2)])
sz_errors = np.array(sz_errors)
fig, ax = plt.subplots()
ax.semilogx(g_grid, sz_errors[:, 0], 'o-', lw=2, label="quantity")
ax.semilogx(g_grid, sz_errors[:, 1], 's-', lw=2, label="price")
ax.set_xlabel("supplier's adjustment cost $g_s$ (log scale)")
ax.set_ylabel("max relative error")
ax.legend()
plt.tight_layout()
plt.show()
Fig. 29.4 Sims–Zha error and adjustment costs#
Beyond very small values of \(g_s\), the error in quantity rises with \(g_s\): the more slowly suppliers can adjust, the more their current decisions depend on expected future policy, and the more it matters that the Sims–Zha counterfactual gets those expectations wrong.
The error in the price is largest at intermediate values of \(g_s\).
29.4.4. Modest policy interventions#
Leeper and Zha [2003] offered a defense of the Sims–Zha approach: if a policy intervention is modest, then ignoring its effect on expectations should do little harm.
Their notion of modesty is statistical: an intervention is modest if the policy shocks that implement it are small relative to the historical variability of policy shocks, so that agents would not detect a change in the policy regime.
Our market lets us examine a closely related question: how does the Sims–Zha error behave as the counterfactual rule \(\tilde\phi\) approaches the baseline \(\phi_A = 0.5\), so that the shocks needed to implement it shrink toward zero?
We compare the error with the size of the effect being estimated, the difference between the counterfactual and the baseline responses.
φ_tildes = np.array([0.4, 0.45, 0.48, 0.52, 0.55, 0.6, 0.75, 1.0, 2.0, 4.0])
ratios, sizes = [], []
for φ_t in φ_tildes:
x_true_t = system_inverse(g_s, g_d, φ_t) @ forcing(1)
x_sz_t, _ = sims_zha(M_inv_A, x_A, φ_t)
effect = np.abs(split(x_true_t)[0] - split(x_A)[0]).max()
error = np.abs(split(x_sz_t)[0] - split(x_true_t)[0]).max()
sizes.append(effect)
ratios.append(error / effect)
fig, axes = plt.subplots(1, 2, figsize=(12, 4))
axes[0].plot(φ_tildes, sizes, 'o-', lw=2, label="policy effect on $q$")
axes[0].plot(φ_tildes, np.array(ratios) * np.array(sizes), 's-', lw=2, label="Sims–Zha error in $q$")
axes[0].set_xlabel("counterfactual rule $\\tilde\\phi$")
axes[0].set_title("absolute size")
axes[0].legend()
axes[1].plot(φ_tildes, ratios, 'o-', lw=2, color='C3')
axes[1].set_ylim(0, 0.4)
axes[1].set_xlabel("counterfactual rule $\\tilde\\phi$")
axes[1].set_title("Sims–Zha error / policy effect")
for ax in axes:
ax.axvline(φ_A, color='k', ls=':', lw=1)
plt.tight_layout()
plt.show()
Fig. 29.5 Sims–Zha error for modest interventions#
As the intervention becomes more modest, the Sims–Zha error shrinks in proportion to the effect of the intervention itself.
For interventions near the baseline rule, the Sims–Zha counterfactual misses roughly 30 percent of the effect on quantity that it is trying to measure.
A modest intervention makes the absolute error small and leaves the error relative to the policy effect near 30 percent.
The expectational channel that Sims–Zha omits is a first-order part of how the counterfactual rule works: it does not become negligible relative to the other channels as the rule change becomes small.
The Sims–Zha path does correctly answer a different question: what happens if the government repeatedly surprises agents who go on believing that the old rule is in force?
Leeper and Zha’s modesty condition is designed to make that the relevant question, by ensuring that agents would not detect the intervention; the answer differs from the effect of a known change in the rule, which is what McKay and Wolf’s construction delivers.
29.4.5. Limited evidence on policy shocks#
McKay and Wolf’s exact result requires the effects of news shocks at every horizon.
In practice an econometrician has evidence on only a few distinct policy shocks, and McKay and Wolf propose choosing the date-0 combination of the available shocks that enforces the counterfactual rule as well as possible in a least-squares sense.
Suppose the econometrician knows the effects of a contemporaneous shock and of news shocks about the next \(n - 1\) periods.
This clean menu is an idealization: identified policy shocks in practice, such as the monetary policy shocks of Romer and Romer [2004] or Gertler and Karadi [2015] that McKay and Wolf use, are combinations of surprises and news about several horizons, each tracing out its own path for the policy instrument.
def mw_limited(M_inv_A, x_A, φ_new, n):
"Least-squares McKay–Wolf counterfactual using news shocks for horizons 0..n-1."
Ω = M_inv_A[:, 2 * T:2 * T + n]
R_new = rule_block(φ_new)
weights, *_ = np.linalg.lstsq(R_new @ Ω, -R_new @ x_A, rcond=None)
return x_A + Ω @ weights
ns = np.arange(1, 16)
errors = np.array([[np.abs(split(mw_limited(M_inv_A, x_A, φ_new, n))[i]
- split(x_true)[i]).max()
/ np.abs(split(x_true)[i]).max() for i in range(2)]
for n in ns])
fig, ax = plt.subplots()
ax.semilogy(ns, errors[:, 0], 'o-', lw=2, label="quantity")
ax.semilogy(ns, errors[:, 1], 's-', lw=2, label="price")
ax.set_xlabel("number of distinct policy shocks $n$ (news horizons $0, \\ldots, n-1$)")
ax.set_ylabel("max relative error")
ax.legend()
plt.tight_layout()
plt.show()
Fig. 29.6 Counterfactuals from limited policy evidence#
Apart from a small step back at \(n = 3\), the error falls quickly as evidence on more distinct policy shocks becomes available: with \(n = 10\) distinct shocks it is below 0.01 percent.
29.5. Model uncertainty and sufficient statistics#
In Induced Priors for Impulse Responses, a structural modeler who believes the model but is unsure of the adjustment costs expresses that uncertainty with the prior \(g_s \sim U[8, 12]\), \(g_d \sim U[0.05, 0.15]\).
That prior induces a prior over the counterfactual response to a demand shock under the rule \(\tilde\phi = 2\).
McKay and Wolf’s econometrician needs no prior over \((g_s, g_d)\): given estimates of \(x_A(\varepsilon)\) and \(\Theta_\nu\) from data generated by the true economy, Proposition 1 delivers the true counterfactual exactly.
In the language of McKay and Wolf, impulse responses to policy shocks, together with \(x_A(\varepsilon)\), are sufficient statistics for policy counterfactuals: every model in their class that matches those impulse responses implies the same counterfactual.
def fan(ax, paths, color, label, alpha=0.25):
"Plot the 5-95 percentile band and the median of paths (draws x horizons)."
lo, med, hi = np.percentile(paths, [5, 50, 95], axis=0)
h = np.arange(paths.shape[1])
ax.fill_between(h, lo, hi, color=color, alpha=alpha, lw=0)
ax.plot(h, med, color=color, lw=2, label=label)
rng = np.random.default_rng(1234)
n_draws = 200
base_draws, cf_draws, sz_ratio_draws = [], [], []
for _ in range(n_draws):
g_s_i, g_d_i = rng.uniform(8, 12), rng.uniform(0.05, 0.15)
M_inv_i = system_inverse(g_s_i, g_d_i, φ_A)
x_A_i = M_inv_i @ forcing(1)
x_cf_i = np.linalg.solve(np.vstack([private_block(g_s_i, g_d_i), rule_block(φ_new)]),
forcing(1))
x_sz_i, _ = sims_zha(M_inv_i, x_A_i, φ_new)
base_draws.append(np.concatenate(split(x_A_i)[:2]))
cf_draws.append(np.concatenate(split(x_cf_i)[:2]))
sz_ratio_draws.append(np.abs(split(x_sz_i)[0] - split(x_cf_i)[0]).max()
/ np.abs(split(x_cf_i)[0] - split(x_A_i)[0]).max())
base_draws, cf_draws = np.array(base_draws), np.array(cf_draws)
fig, axes = plt.subplots(1, 2, figsize=(12, 4))
for i, ax in enumerate(axes):
fan(ax, base_draws[:, i * T:i * T + H_plot], 'C7',
f"induced prior, baseline rule $\\phi = {φ_A}$")
fan(ax, cf_draws[:, i * T:i * T + H_plot], 'C1',
f"induced prior, counterfactual rule $\\tilde\\phi = {φ_new}$")
ax.plot(split(x_mw)[i][:H_plot], 'C0o', ms=4,
label="McKay–Wolf, from the true economy's impulse responses")
ax.axhline(0, color='k', lw=0.5)
ax.set_title(f"{names[i]}, demand shock")
ax.set_xlabel("periods")
axes[0].legend(fontsize=8)
plt.tight_layout()
plt.show()
print(f"Sims–Zha error / policy effect on q across the prior: "
f"[{min(sz_ratio_draws):.2f}, {max(sz_ratio_draws):.2f}]")
Fig. 29.7 Induced prior versus sufficient statistics#
Sims–Zha error / policy effect on q across the prior: [0.24, 0.28]
The structural modeler’s counterfactual band reflects uncertainty about \((g_s, g_d)\) that, in this exercise, never gets resolved.
The gray band shows that much of this uncertainty is already present in the modeler’s beliefs about how the economy responds under the baseline rule.
The McKay–Wolf econometrician replaces that uncertainty with measurement: the impulse responses under the baseline rule pin down the counterfactual without pinning down \((g_s, g_d)\).
The Sims–Zha error, meanwhile, is a robust feature of the model: across the whole prior, it misses about a quarter of the policy effect on quantity.
A structural modeler who saw the same impulse responses could learn \((g_s, g_d)\) from them, as in Exercise 28.3 of Induced Priors for Impulse Responses.
McKay and Wolf’s counterfactual does not require that step and does not depend on the details of a particular parametric model of the private sector.
29.6. Invertibility#
So far we have assumed that the econometrician knows the impulse responses to the non-policy shock and to the policy shocks.
Estimating them requires identifying assumptions.
In Dynamic Supply and Demand Curves and Induced Priors for Impulse Responses we saw that, because quantity adjusts sluggishly, the supply shock is not fundamental for \((q_t, p_t)\): it is not a linear combination of current and past prices and quantities.
No rotation of the innovations of a vector autoregression for \((q_t, p_t)\) recovers the supply shock — not a Cholesky factorization, and not one selected by sign restrictions.
Some procedures need invertibility and others do not.
In McKay and Wolf’s applications, the responses to policy shocks come from instruments, such as narrative measures or high-frequency financial market surprises.
To focus on the non-policy shocks, we work in what follows with the economy under the baseline rule and no policy shocks, so that the econometrician observes \((q_t, p_t)\) and \(\tau_t = -\phi_A p_t\).
To compute the population VAR we write the moving-average representation, truncated after H_ma lags,
in state-space form and apply the Kalman filter, as in Dynamic Supply and Demand Curves.
A First Look at the Kalman Filter introduces the filter, and Classical Prediction and Filtering With Linear Algebra derives the same Wold representation by factoring the spectral density.
H_ma = 80 # truncation lag for the moving-average representation
def structural_ma(g_s, g_d, φ, h_s=h_s, σ_s=σ_s):
"Stacked responses of (q, p, τ) to the two structural shocks: a 3T x 2 array."
M_inv = system_inverse(g_s, g_d, φ, h_s=h_s)
return np.stack([M_inv @ forcing(k, σ_s=σ_s) for k in range(2)], axis=1)
def ma_qp(X):
"MA coefficients D_j (2 x 2) of (q, p) from stacked paths, j = 0..H_ma-1."
return np.stack([X[:H_ma], X[T:T + H_ma]], axis=1)
def wold(D):
"Innovation covariance V and Wold MA coefficients C_j of z_t = sum_j D_j ε_(t-j)."
k = D.shape[2]
n = H_ma * k
A = np.zeros((n, n)); A[k:, :-k] = np.eye(n - k)
C = np.zeros((n, k)); C[:k] = np.eye(k)
G = np.hstack(list(D))
kalman = qe.Kalman(qe.LinearStateSpace(A, C, G, 1e-8 * np.eye(D.shape[1])))
kalman.stationary_values()
V = kalman.stationary_innovation_covar()
C_wold = np.array(kalman.stationary_coefficients(H_ma - 1, 'ma'))
return V, C_wold
def impulse_norms(D, V):
"||L^{-1} d|| for each structural impact vector d (columns of D_0)."
return np.linalg.norm(np.linalg.solve(np.linalg.cholesky(V), D[0]), axis=0)
X_A = structural_ma(g_s, g_d, φ_A)
D_A = ma_qp(X_A)
V_A, C_wold_A = wold(D_A)
print("||L^-1 d|| for (supply, demand) under the baseline rule:",
np.round(impulse_norms(D_A, V_A), 4))
||L^-1 d|| for (supply, demand) under the baseline rule: [0.8662 1. ]
As in Induced Priors for Impulse Responses, a norm below one says that the supply impact vector is not a rotation of the VAR innovations.
29.6.1. External instruments do not need invertibility#
Suppose that the econometrician observes a noisy measure of the supply shock — for example, a series on input costs —
where \(v_t\) is measurement error, independent of everything else.
Write \(z_t = (q_t, p_t)^\top = \sum_{j \ge 0} D_j \epsilon_{t-j}\) for the structural moving average, where \(\epsilon_t = (\tilde w_{st}, \tilde w_{dt})\) has unit variances and \(D_h^{(s)}\) is the first column of \(D_h\).
Because \(\tilde w_{st}\) is serially uncorrelated and independent of the demand shock,
the true response of \(z\) at horizon \(h\) to the supply shock.
A local projection — a regression of \(z_{t+h}\) on \(m_t\) — recovers the supply-shock impulse response up to scale, whether or not the supply shock is fundamental.
Stock and Watson [2018] and Plagborg-Møller and Wolf [2021] develop this external-instrument logic; the latter show that ordering the instrument first in a VAR estimates the same impulse responses.
McKay and Wolf estimate the responses to their non-policy shock this way: they order the series of investment-specific technology news shocks of Ben Zeev and Khan [2015] first in a VAR.
We simulate a long sample under the baseline rule and compare three estimates of the response to a supply shock, each normalized to raise quantity by one unit on impact.
n_sim = 2_000_000
ε = rng.standard_normal((n_sim + H_ma, 2))
m = ε[H_ma:, 0] + rng.standard_normal(n_sim) # noisy proxy for the supply shock
# simulate (q, p) from the moving-average representation
x_sim = np.zeros((n_sim, 2))
for i in range(2):
for k in range(2):
x_sim[:, i] += np.convolve(ε[:, k], D_A[:, i, k])[H_ma:H_ma + n_sim]
# local projections on the proxy
lp = np.array([[np.dot(x_sim[h:, i], m[:n_sim - h]) / np.dot(m, m)
for i in range(2)] for h in range(H_plot)])
lp /= lp[0, 0]
truth = D_A[:H_plot, :, 0] / D_A[0, 0, 0]
# the population VAR's best Cholesky candidate: p first, second shock
P_swap = np.array([[0, 1], [1, 0]])
L_p = P_swap @ np.linalg.cholesky(P_swap @ V_A @ P_swap)
chol = np.array([C_wold_A[h] @ L_p[:, 1] for h in range(H_plot)])
chol /= chol[0, 0]
fig, axes = plt.subplots(1, 2, figsize=(12, 4))
for i, ax in enumerate(axes):
ax.plot(truth[:, i], 'k-', lw=2, label="true supply-shock response")
ax.plot(lp[:, i], 'C0o', ms=5, label="local projection on the proxy $m_t$")
ax.plot(chol[:, i], 'C3--', lw=2, label="VAR, p-first Cholesky")
ax.axhline(0, color='k', lw=0.5)
ax.set_title(f"response of {names[i]} (unit impact on $q$)")
ax.set_xlabel("periods")
ax.set_xlim(0, 12)
axes[0].legend(fontsize=8)
plt.tight_layout()
plt.show()
Fig. 29.8 Recovering the supply-shock response#
The local projection recovers the true response, including the sign reversal after two periods, while the Cholesky-identified VAR shock does not.
We used a very long sample because supply shocks account for only a small part of the variation in prices; the local-projection estimates are noisy in samples of realistic length, and in population they coincide exactly with the true response.
This is the constructive counterpart of the negative results in Induced Priors for Impulse Responses: information from outside the VAR — here, a measured cost shock — can recover a shock that prices and quantities alone reveal only with a delay.
McKay and Wolf’s conditional counterfactuals in Proposition 1 have the same property: they need impulse responses to the non-policy shock and to the policy shocks, and if those are estimated with instruments, invertibility plays no role.
Anticipated policy shocks are themselves a standard source of non-invertibility — see Leeper et al. [2013] on fiscal foresight, and Shock Non Invertibility for the same “news” problem in a permanent income model — which is another reason why McKay and Wolf identify policy shocks with instruments.
29.6.2. Invertibility depends on the policy rule#
Whether the supply shock is fundamental for \((q_t, p_t)\) is a property of the equilibrium moving-average representation, and that representation depends on the policy rule.
A change in the policy rule can change whether a VAR recovers the supply shock.
φ_grid = np.linspace(-0.5, 10, 22)
fig, ax = plt.subplots()
for g, color in [(3.0, 'C0'), (10.0, 'C1'), (30.0, 'C3')]:
norms = []
for φ in φ_grid:
D = ma_qp(structural_ma(g, g_d, φ))
V, _ = wold(D)
norms.append(impulse_norms(D, V)[0])
ax.plot(φ_grid, norms, 'o-', lw=2, color=color, ms=4, label=f"$g_s = {g:g}$")
ax.axhline(1, color='k', lw=0.5)
ax.axvline(φ_A, color='k', ls=':', lw=1)
ax.set_xlabel("policy rule coefficient $\\phi$")
ax.set_ylabel("$\\|L^{-1} d_s\\|$")
ax.legend()
plt.tight_layout()
plt.show()
Fig. 29.9 Fundamentalness and the policy rule#
The more aggressively the government stabilizes the price, the closer the supply shock comes to being fundamental.
For \(g_s = 3\) the baseline rule already makes it fundamental, and for \(g_s = 10\) a rule with \(\phi \gtrsim 5\) does; for \(g_s = 30\), a still more aggressive rule would be needed.
A rule that destabilizes the price (\(\phi < 0\)) pushes the economy further from invertibility.
The equivalence that we derived earlier explains the pattern.
Under the rule \(\phi\), prices and quantities behave as in a market without a tax whose supplier has cost parameters \(h_s / (1 + \phi)\) and \(g_s / (1 + \phi)\), and whose cost shocks are scaled down by the same factor.
Scaling a shock does not change whether it is fundamental.
The rule matters only through the effective cost parameters.
In Exercise 27.2 of Dynamic Supply and Demand Curves we saw that the supply shock becomes fundamental when suppliers can adjust quickly: price stabilization makes them behave as if they could.
The analogy is approximate, because that exercise lowered \(g_s\) with \(h_s\) held fixed, while the rule lowers both.
for φ in [0.5, 2.0]:
D_rule = ma_qp(structural_ma(g_s, g_d, φ))
nrm_rule = impulse_norms(D_rule, wold(D_rule)[0])[0]
D_flex = ma_qp(structural_ma(g_s / (1 + φ), g_d, 0.0, h_s=h_s / (1 + φ)))
nrm_flex = impulse_norms(D_flex, wold(D_flex)[0])[0]
print(f"φ = {φ}: ||L^-1 d_s|| under the rule {nrm_rule:.4f}, "
f"in the flexible-supply market {nrm_flex:.4f}")
φ = 0.5: ||L^-1 d_s|| under the rule 0.8662, in the flexible-supply market 0.8662
φ = 2.0: ||L^-1 d_s|| under the rule 0.9308, in the flexible-supply market 0.9308
This is a version of the Lucas critique that applies to identification: a VAR identification scheme that is valid under one policy regime can fail under another, because the zeros of the equilibrium moving-average representation move with the policy rule.
29.6.3. Counterfactual second moments through VAR innovations#
McKay and Wolf also predict the unconditional second moments of the data under a new rule.
If the econometrician could identify every structural shock, she could apply Proposition 1 to each one and add up the resulting variances and autocovariances.
When the structural shocks cannot all be identified, McKay and Wolf propose applying Proposition 1 to the impulse responses of the Wold innovations instead, and they note that this is valid only if the structural moving-average representation is invertible.
We compare the Wold-innovation route with the true counterfactual autocovariances.
def autocov(X, n_lags=12):
"Autocovariances Γ_j (3 x 3), j = 0..n_lags, from stacked impulse responses X (3T x k)."
P = np.stack([X[b * T:(b + 1) * T] for b in range(3)]) # 3 x T x k
return np.array([np.einsum('itk,jtk->ij', P[:, j:], P[:, :T - j])
for j in range(n_lags + 1)])
def wold_route(g_s_, φ_base, φ_cf, σ_s_=σ_s):
"Counterfactual autocovariances: truth versus the Wold-innovation route."
M_inv_base = system_inverse(g_s_, g_d, φ_base)
X_base = np.stack([M_inv_base @ forcing(k, σ_s=σ_s_) for k in range(2)], axis=1)
X_true = structural_ma(g_s_, g_d, φ_cf, σ_s=σ_s_)
D = ma_qp(X_base)
V, C_wold = wold(D)
W = C_wold[:H_ma] @ np.linalg.cholesky(V) # responses to orthonormal Wold innovations
X_w = []
for k in range(2):
q, p = np.zeros(T), np.zeros(T)
q[:H_ma], p[:H_ma] = W[:, 0, k], W[:, 1, k]
x_k, _ = mw_counterfactual(M_inv_base, np.concatenate([q, p, -φ_base * p]), φ_cf)
X_w.append(x_k)
X_w = np.stack(X_w, axis=1)
Γ_true, Γ_w = autocov(X_true), autocov(X_w)
return Γ_true, Γ_w, impulse_norms(D, V)[0]
print(" g_s ||L^-1 d_s|| var(q): true Wold route max relative error in Γ_0..Γ_12")
for g in [0.5, 10.0]:
Γ_true, Γ_w, nrm = wold_route(g, φ_A, φ_new)
rel = np.abs(Γ_w - Γ_true).max() / np.abs(Γ_true).max()
print(f"{g:4.1f} {nrm:12.4f} {Γ_true[0, 0, 0]:13.5f} {Γ_w[0, 0, 0]:11.5f} {rel:22.1e}")
g_s ||L^-1 d_s|| var(q): true Wold route max relative error in Γ_0..Γ_12
0.5 1.0000 3.01567 3.01567 1.2e-15
10.0 0.8662 0.89802 0.89804 2.5e-05
When the supply shock is fundamental (\(g_s = 0.5\)), the Wold-innovation route reproduces the true counterfactual autocovariances to machine precision.
When it is not (\(g_s = 10\)), the route makes an error, but the error is tiny.
Invertibility makes the route exact for the following reason.
Under invertibility, each Wold innovation is a linear combination of the current structural shocks, and its impulse response is a linear combination of date-0 structural impulse responses, to which (29.4) applies.
Under non-invertibility, a Wold innovation also contains past structural shocks, and its impulse response includes the continuations of paths that started before date 0.
Proposition 1 treats such a continuation as a fresh date-0 path from the steady state, ignoring that agents had already been responding to it, and maps the continuation incorrectly.
X_A_struct = np.stack([M_inv_A @ forcing(k) for k in range(2)], axis=1)
X_new_struct = structural_ma(g_s, g_d, φ_new)
def continuation(x, k):
"The part of each path (q, p, τ) from date k on, restarted at date 0."
out = np.zeros_like(x)
for b in range(3):
out[b * T:(b + 1) * T - k] = x[b * T + k:(b + 1) * T]
return out
for k in [1, 2]:
mapped, _ = mw_counterfactual(M_inv_A, continuation(X_A_struct[:, 1], k), φ_new)
target = continuation(X_new_struct[:, 1], k)
rel = [np.abs(split(mapped)[i] - split(target)[i])[:T - 10].max() / np.abs(split(target)[i]).max()
for i in range(3)]
print(f"demand shock, continuation from date {k}: max |mapped - true| relative to the peak "
f"of the true path: q {rel[0]:.2f}, p {rel[1]:.2f}, τ {rel[2]:.2f}")
demand shock, continuation from date 1: max |mapped - true| relative to the peak of the true path: q 0.13, p 0.09, τ 0.09
demand shock, continuation from date 2: max |mapped - true| relative to the peak of the true path: q 0.23, p 0.57, τ 0.57
These errors are large: Proposition 1 misses each continuation by between 9 and 57 percent of its peak.
The overall error in counterfactual second moments is nevertheless tiny because, in our market, the weight that the Wold innovations put on past structural shocks is small — the non-fundamental part of the supply shock contributes little to the variance of prices and quantities.
29.7. McKay and Wolf and dynamic programming squared#
Several lectures in this series — the “dynamic programming squared” lectures — pursue a different response to the Lucas critique: they model the government as an optimizing agent and ask which policies it would choose and which it could credibly promise.
29.7.1. Rule-based scenario analysis#
McKay and Wolf describe their thought experiment, in which a monetary policy rule links the interest rate \(i\) to inflation \(\pi\):
Note that this thought experiment supposes that the private sector perfectly understands the change in rule: the new relationship between \(i\) and \(\pi\) holds for all \(t \geq 0\).
Their counterfactual is a scenario.
A government announces a rule at date 0.
Private agents believe it and expect it to hold forever.
The analyst computes outcomes under rational expectations.
Because the economy is linear, a rule is equivalent, shock by shock, to one path of the policy instrument.
A rule counterfactual is a family of path scenarios, one for each history of shocks; a peg or a zeroing-out counterfactual is a single path.
Lucas [1976] proposed this kind of experiment: compare outcomes under alternative rules, each a permanent change that private agents understand.
McKay and Wolf carry out Lucas’s experiment with measured impulse responses in place of an estimated model of the private sector.
Sargent and Wallace [1976] and Sims [1982] questioned the experiment.
If rules can change, rational agents assign probabilities to changes before they occur, and the historical data already reflect those probabilities.
A rule change that agents did not anticipate before date 0 and believe completely after date 0 lies outside the stochastic process that generated the data.
The construction requires that the instrument path implementing the new rule lie in the span of paths traced out by historical policy shocks.
A rule never followed in the sample can satisfy that requirement, but the date-0 news shocks that implement it may be large by historical standards.
If they are, Leeper and Zha’s modesty condition fails.
The scenario does not explain why a government would adopt the rule, keep it, or be believed.
The dynamic programming squared lectures address those questions; Policy Regimes and the Lucas Critique replaces the scenario with a known stochastic process for rule changes.
29.7.2. The same constraint, measured or modeled#
In Time Inconsistency of Ramsey Plans, a perfect-foresight Cagan demand for money implies that the rate of inflation,
depends on the entire future path of money growth \(\{\mu_{t+j}\}\).
Inflation summarizes how future policy affects today’s real balances.
In Stackelberg Plans, a Stackelberg follower’s Euler equations are part of the law of motion that the Stackelberg leader faces when it chooses its plan.
In Optimal Taxation with State-Contingent Debt, the household’s first-order conditions collapse into an implementability constraint that restricts the allocations a Ramsey planner can implement with distorting taxes.
In each case, private decisions depend on the whole expected path of a policy instrument, and a policymaker who chooses that path takes the dependence into account.
Our Euler equations (29.1)–(29.2) are the corresponding constraint, and McKay and Wolf’s key assumption is that they are the only channel through which policy matters.
The matrix \(\Theta_\nu\) of responses to contemporaneous and news shocks is then an empirical counterpart of the constraint that the planners of those lectures take from a model: it tells us which paths of \((q_t, p_t)\) a policymaker can implement by choosing a path for the tax.
29.7.3. Optimal policy and time inconsistency#
McKay and Wolf’s Proposition 2, which Exercise 29.1 puts to work, is a Ramsey or Stackelberg problem written in sequence space: choose a date-0 path for the instrument to minimize a loss, subject to the private sector’s Euler equations.
Like the Ramsey and Stackelberg plans of Time Inconsistency of Ramsey Plans, Stackelberg Plans, and Optimal Taxation with State-Contingent Debt, the resulting plan is time inconsistent, and Exercise 29.2 reproduces, with McKay–Wolf objects, the “reborn Stackelberg leader” calculation of Stackelberg Plans.
Stackelberg Plans and Optimal Taxation with State-Contingent Debt implement such a plan recursively by handing each continuation planner, as a state variable, the forward-looking variable that its predecessors promised — the follower’s decision in Stackelberg Plans, marginal-utility-weighted government debt in Optimal Taxation with State-Contingent Debt.
McKay and Wolf’s suggestion that a policymaker apply its targeting rule to revisions of its forecasts at each date — a “timeless perspective” — is a device of the same kind.
29.7.4. Credible rules and sustainable plans#
McKay and Wolf’s counterfactual uses a commitment timing protocol: the new rule is chosen once and for all at date 0, believed by everyone, and followed forever.
Sustainable Plans for a Calvo Model studies a different protocol, in which a sequence of policymakers each choose policy for one period, and asks which plans are sustainable — which plans each policymaker would want to confirm.
In the theory of sustainable plans developed by Abreu [1988], Chari and Kehoe [1990], and Stokey [1989], a plan is sustained by the private sector’s expectation that a deviation would trigger a switch to a worse continuation plan.
The theory supplies a mechanism for credibility that McKay and Wolf’s scenario lacks: credibility is an equilibrium outcome, and a plan that no continuation threat can support is not credible.
The theory also interprets a deviation from expected policy as an event that changes private beliefs about future policy.
If measured policy shocks are deviations of that kind, then their estimated effects combine the effect of the instrument path with a revision of beliefs about the rule, and the premise of Proposition 1 — that a deviation leaves agents’ view of the rule unchanged except through the announced path — fails.
Leeper and Zha’s modesty condition requires that deviations not trigger such revisions.
29.7.5. Endogenous policy regimes and institutional counterfactuals#
Accounting for Monetary and Fiscal Policy studies a model of Bocola et al. [2026] in which a government that cannot fully commit delegates monetary policy to a central bank with an inflation mandate, but may override the mandate when it needs revenue.
Whether it does depends on the fiscal state and on a random cost of overriding the mandate, and the economy switches endogenously between monetary-dominant and fiscal-dominant regimes.
The model departs from McKay and Wolf’s environment in three respects.
Policy is chosen, not given by a rule: the “rule” visible in the data is an equilibrium outcome that depends on the state of the economy.
Because the regime switches with the state, private demand for money depends on the probability that the mandate will be honored, not only on the expected path of the policy instrument; the economy is nonlinear, and the certainty equivalence that lets McKay and Wolf work with expected paths does not apply.
Its central counterfactual — setting the cost of overriding the mandate to zero throughout — changes the institutions that determine how policy is chosen, not the rule that policy follows; questions of this kind are beyond the reach of McKay and Wolf’s method and call for a structural model of the policymaker.
29.7.6. What the method takes for granted#
McKay and Wolf’s counterfactuals are exact under six assumptions, several of which the dynamic programming squared lectures relax.
The private sector’s behavior is linear and depends on policy only through the expected path of the instrument.
The counterfactual rule is credible: announced at date 0, understood, believed, and kept.
Measured policy shocks change the instrument path without changing beliefs about the rule.
The policy rule is a primitive chosen by the analyst, not the outcome of choices by a government that may be unable to commit.
The question concerns rules, not the institutions that shape how rules are chosen.
The baseline and counterfactual rules each deliver a unique equilibrium, as McKay and Wolf assume; the theory of sustainable plans, with its many credible plans, warns that a policy environment need not pin down one outcome.
Our market satisfies the first assumption, and our computations impose the next three by construction.
For every rule with \(\phi > -1\), the equivalence with a market whose suppliers are more flexible implies a unique equilibrium, because the equilibrium of that market solves a planning problem with a strictly convex objective.
Within these assumptions, McKay and Wolf replace a model of the private sector with measurements; the dynamic programming squared lectures, in turn, replace an assumed policy rule with a model of the policymaker.
29.8. When policy regimes switch#
Policy Regimes and the Lucas Critique lets the tax rule switch between laissez-faire and price stabilization according to a Markov chain that private agents know.
That lecture sets the demander’s adjustment cost \(g_d\) to zero.
With \(g_d = 0\), the market equilibrium solves a Markov jump linear quadratic planning problem of the kind studied in Markov Jump Linear Quadratic Dynamic Programming.
McKay and Wolf’s construction recovers outcomes under a permanent rule exactly from data generated by switching regimes, whether the expected responses condition on the regime at the date of the shock or average over it.
The construction does not deliver outcomes under a reform that agents expect may be reversed, because such a rule is not a linear restriction on expected paths.
In the first periods after a shock, a reversible reform has smaller effects than a permanent one: with a probability 0.2 of reversal each period, the response of quantity one period after a demand shock is about 7 percent smaller; with probability 0.5, about 18 percent smaller.
These numbers measure, in one model, the gap between McKay and Wolf’s scenario and a regime change whose credibility is summarized by a transition probability.
29.9. Summary#
The market with a tax fits McKay and Wolf’s framework, and their key assumption is the invariance of the open-loop dynamic supply and demand curves of Dynamic Supply and Demand Curves.
In this market:
Impulse responses to contemporaneous and news tax shocks under one rule deliver the exact response to supply and demand shocks under another rule.
The Sims–Zha method, which uses only contemporaneous surprises, makes errors in quantity that, beyond very small adjustment costs, grow with how forward-looking suppliers are.
Making the intervention more modest shrinks the Sims–Zha error only in proportion to the policy effect itself: near the baseline rule it still misses about 30 percent of the effect on quantity.
With evidence on only a few distinct policy shocks, a least-squares approximation is accurate once about ten distinct shocks are available.
Impulse responses to policy shocks are sufficient statistics for the counterfactual: they deliver it exactly, while a structural modeler with a prior over parameters inherits a band of uncertainty.
A price-stabilizing tax rule is observationally equivalent to more flexible suppliers, a compact form of the Lucas critique in this market.
The market also lets us sort procedures by whether they need invertibility.
procedure |
needs invertibility? |
in this market, \(g_s = 10\) |
|---|---|---|
Cholesky or sign-restricted rotations of VAR innovations |
yes |
miss the supply-shock response |
local projection on an external instrument |
no |
recovers the supply-shock response |
McKay–Wolf counterfactual for a given shock (Proposition 1) |
no, given the impulse responses |
exact |
Sims–Zha counterfactual for a given shock |
no, given the impulse responses |
not robust to the Lucas critique: for \(\tilde\phi = 2\), errors of about 9 percent (\(q\)) and 7 percent (\(p\)) of the peak response; 26 percent of the policy effect on \(q\) |
McKay–Wolf counterfactual second moments via Wold innovations |
yes |
small error |
Every entry in the table presumes McKay and Wolf’s environment: a linear private sector, a credible counterfactual rule, and policy shocks that do not change beliefs about the rule (McKay and Wolf and dynamic programming squared).
First, invertibility is not a fixed property of an economy: it depends on the policy rule, and a VAR identification scheme that works under one regime can fail under another — in our market because price stabilization acts like a proportional reduction in suppliers’ production and adjustment costs.
Second, in this market, non-invertibility matters a great deal for some objects — the impulse response to the supply shock — and very little for others — counterfactual second moments.
Finally, McKay and Wolf’s counterfactual is rule-based scenario analysis: a rule announced at date 0 and believed forever.
Policy Regimes and the Lucas Critique measures, in one model, how far that scenario lies from a rule change that agents expect may be reversed.
29.10. Exercises#
Exercise 29.1
McKay and Wolf’s Proposition 2 shows that impulse responses to policy shocks also suffice to compute optimal policy for a given loss function.
Consider a policymaker who, after a demand shock, wants to minimize
by choosing the date-0 policy shocks \(\nu\).
Using only \(x_A(\varepsilon)\) and \(\Theta_\nu\) under the baseline rule, compute the optimal responses of \((q_t, p_t, \tau_t)\) for \(\lambda = 0.1\) and \(\lambda = 1\).
Verify that the optimal allocation satisfies McKay and Wolf’s targeting rule \(\Theta_{p,\nu}^\top W p + \lambda\, \Theta_{q,\nu}^\top W q = 0\), where \(W = \mathrm{diag}(1, \beta, \beta^2, \ldots)\) and \(\Theta_{p,\nu}, \Theta_{q,\nu}\) are the price and quantity rows of \(\Theta_\nu\).
Solution
The loss is a weighted sum of squares of \(x_A + \Theta_\nu \nu\), and the optimal \(\nu\) solves a least-squares problem.
disc = β ** np.arange(T) # discount weights, the diagonal of W
Θ_q, Θ_p = Θ_ν[:T], Θ_ν[T:2 * T]
def optimal_plan(x_base, Θ, λ, start=0):
"""
Choose the policy shocks in the columns of Θ to minimize
sum_{t >= start} β^(t-start) (p_t^2 + λ q_t^2), starting from the path x_base.
"""
w = np.sqrt(disc[:T - start])
q_b, p_b, _ = split(x_base)
A_ls = np.vstack([w[:, None] * Θ[T + start:2 * T],
np.sqrt(λ) * w[:, None] * Θ[start:T]])
b_ls = -np.concatenate([w * p_b[start:], np.sqrt(λ) * w * q_b[start:]])
ν_opt, *_ = np.linalg.lstsq(A_ls, b_ls, rcond=None)
return x_base + Θ @ ν_opt
fig, axes = plt.subplots(1, 3, figsize=(14, 4))
for i in range(3):
axes[i].plot(split(x_A)[i][:H_plot], 'k:', lw=2, label="baseline rule")
for λ, color in [(0.1, 'C0'), (1.0, 'C1')]:
x_opt = optimal_plan(x_A, Θ_ν, λ)
q_o, p_o, τ_o = split(x_opt)
foc = Θ_p.T @ (disc * p_o) + λ * Θ_q.T @ (disc * q_o)
print(f"λ = {λ}: max |targeting rule| = {np.abs(foc).max():.1e}")
for i in range(3):
axes[i].plot(split(x_opt)[i][:H_plot], color=color, lw=2,
label=f"optimal, $\\lambda = {λ}$")
for i in range(3):
axes[i].axhline(0, color='k', lw=0.5)
axes[i].set_title(f"{names[i]}, demand shock")
axes[i].set_xlabel("periods")
axes[0].legend()
plt.tight_layout()
plt.show()
λ = 0.1: max |targeting rule| = 7.9e-16
λ = 1.0: max |targeting rule| = 3.6e-16
The targeting rule holds to machine precision: it is the first-order condition of the least-squares problem.
A policymaker who cares mostly about the price (\(\lambda = 0.1\)) subsidizes the good heavily when the demand shock raises its price; a larger weight on quantity tempers the subsidy, because the subsidy raises quantity.
The targeting rule says that the policymaker’s forecasts of its targets must be optimal within the set of forecasts that policy can implement, which is the idea of forecast targeting proposed by Svensson [1997].
The policymaker chooses the optimal plan once and for all at date 0.
The plan works partly through promises about future taxes that shape suppliers’ and demanders’ expectations today.
The plan is a Ramsey plan with commitment — a Stackelberg leader’s plan in the language of Stackelberg Plans — and like the plans in Time Inconsistency of Ramsey Plans, Stackelberg Plans, and Optimal Taxation with State-Contingent Debt it is time inconsistent, as the next exercise shows.
Exercise 29.2
Continue the previous exercise with \(\lambda = 0.1\).
Suppose that the economy follows the date-0 optimal plan through date \(k - 1\), and that at date \(k\) a new policymaker with the same loss function re-optimizes, choosing the policy shocks for dates \(t \ge k\) to minimize \(\frac12 \sum_{t \ge k} \beta^{t-k} (p_t^2 + \lambda q_t^2)\).
The re-optimizing policymaker takes as given everything that happened before date \(k\), including the quantities \(q_{k-1}, \ldots, q_{k-4}\) that enter the Euler equations from date \(k\) on.
Show that for \(k = 1, 2, 4\) the re-optimized path differs from the continuation of the date-0 plan.
Hint: to impose the history, replace the equations for dates \(t < k\) in the stacked system by equations that set \((q_t, p_t, \tau_t)\) equal to their values under the date-0 plan.
Solution
This is the “reborn Stackelberg leader” calculation of Stackelberg Plans, carried out with McKay–Wolf objects.
The re-optimizing policymaker could choose to continue the date-0 plan, which satisfies all the equations from date \(k\) on; if it chooses something else, the date-0 plan is time inconsistent.
λ = 0.1
x_plan = optimal_plan(x_A, Θ_ν, λ)
M_A = np.vstack([private_block(g_s, g_d), rule_block(φ_A)])
def continuation_loss(x, k):
q, p, _ = split(x)
return 0.5 * np.sum(disc[:T - k] * (p[k:]**2 + λ * q[k:]**2))
fig, axes = plt.subplots(1, 3, figsize=(14, 4))
for i in range(3):
axes[i].plot(split(x_plan)[i][:H_plot], 'k', lw=2, label="date-0 plan")
for k, color in [(1, 'C0'), (2, 'C1'), (4, 'C2')]:
M_k, rhs_k = M_A.copy(), forcing(1)
for b in range(3): # impose the history before date k
for t in range(k):
M_k[b * T + t] = 0.0
M_k[b * T + t, b * T + t] = 1.0
rhs_k[b * T + t] = x_plan[b * T + t]
M_k_inv = np.linalg.inv(M_k)
x_k_base = M_k_inv @ rhs_k # history, then the baseline rule
Θ_k = M_k_inv[:, 2 * T:][:, k:] # policy shocks for dates t >= k
x_reopt = optimal_plan(x_k_base, Θ_k, λ, start=k)
gap = [np.abs(split(x_reopt)[i][k:] - split(x_plan)[i][k:]).max() / np.abs(split(x_plan)[i]).max()
for i in range(3)]
print(f"k = {k}: max |re-optimized - plan| relative to the plan's peak: q {gap[0]:.3f}, "
f"p {gap[1]:.3f}, τ {gap[2]:.3f}; continuation loss "
f"{continuation_loss(x_reopt, k):.4f} vs {continuation_loss(x_plan, k):.4f}")
for i in range(3):
axes[i].plot(np.arange(k, H_plot), split(x_reopt)[i][k:H_plot], '--',
color=color, lw=2, label=f"re-optimized at date {k}")
for i in range(3):
axes[i].axhline(0, color='k', lw=0.5)
axes[i].set_title(f"{names[i]}, demand shock, $\\lambda = {λ}$")
axes[i].set_xlabel("periods")
axes[0].legend(fontsize=8)
plt.tight_layout()
plt.show()
k = 1: max |re-optimized - plan| relative to the plan's peak: q 0.005, p 0.084, τ 0.009; continuation loss 0.1575 vs 0.1576
k = 2: max |re-optimized - plan| relative to the plan's peak: q 0.010, p 0.167, τ 0.017; continuation loss 0.0102 vs 0.0106
k = 4: max |re-optimized - plan| relative to the plan's peak: q 0.003, p 0.039, τ 0.006; continuation loss 0.0006 vs 0.0007
At each date \(k\) the re-optimizing policymaker achieves a lower continuation loss by abandoning the date-0 plan.
The gains are small: at \(k = 1\) the continuation loss falls from 0.1576 to 0.1575.
The departures from the plan are at most 2 percent of the plan’s peak for quantity and the tax, and up to 17 percent for the price, at \(k = 2\).
The date-0 policymaker would not want those departures.
The date-0 plan promised taxes for dates \(t \ge k\) whose purpose was partly to shape expectations at dates before \(k\); once those dates have passed, the promises no longer serve that purpose, and a re-optimizing policymaker wants to break them.
This is the time inconsistency that Time Inconsistency of Ramsey Plans studies in a model of inflation.
A re-optimizing policymaker who honored those promises would continue the date-0 plan, as in the recursive implementations discussed in McKay and Wolf and dynamic programming squared.
Exercise 29.3
The error that the Wold-innovation route makes under non-invertibility is small in our market because the non-fundamental supply shock contributes little to the variance of prices and quantities.
Recompute the table of counterfactual second moments with a larger supply-shock standard deviation, \(\sigma_s \in \{2, 4\}\), and \(g_s = 10\).
How does the error change?
Solution
print(" σ_s ||L^-1 d_s|| max relative error in Γ_0..Γ_12")
for σ in [np.sqrt(0.5), 2.0, 4.0]:
Γ_true, Γ_w, nrm = wold_route(10.0, φ_A, φ_new, σ_s_=σ)
rel = np.abs(Γ_w - Γ_true).max() / np.abs(Γ_true).max()
print(f"{σ:4.2f} {nrm:12.4f} {rel:30.1e}")
σ_s ||L^-1 d_s|| max relative error in Γ_0..Γ_12
0.71 0.8662 2.5e-05
2.00 0.8662 2.0e-04
4.00 0.8662 7.9e-04
The error grows in proportion to the variance \(\sigma_s^2\) of the supply shock, but it stays small.
The reason is that a larger \(\sigma_s\) scales up the supply shock’s contribution to the data without making supply surprises any easier to detect: the degree of non-fundamentalness, measured by \(\|L^{-1} d_s\|\), is unchanged, but the part of the Wold innovations that reflects past supply shocks gets more weight.
Exercise 29.4
Use McKay and Wolf’s Proposition 1 to construct the “zeroing-out” counterfactual of Bernanke et al. [1997]: how would quantity and price respond to a supply shock if the government did not respond at all (\(\tilde\phi = 0\))?
Compare the McKay–Wolf answer with the truth and with the Sims–Zha answer.
Solution
x_A_s = M_inv_A @ forcing(0)
x_true_s = system_inverse(g_s, g_d, 0.0) @ forcing(0)
x_mw_s, _ = mw_counterfactual(M_inv_A, x_A_s, 0.0)
x_sz_s, _ = sims_zha(M_inv_A, x_A_s, 0.0)
fig, axes = plt.subplots(1, 3, figsize=(14, 4))
for i in range(3):
axes[i].plot(split(x_A_s)[i][:H_plot], 'k:', lw=2, label=f"baseline, $\\phi = {φ_A}$")
axes[i].plot(split(x_true_s)[i][:H_plot], 'C0', lw=2, label="true, no policy")
axes[i].plot(split(x_mw_s)[i][:H_plot], 'C0o', ms=4, label="McKay–Wolf")
axes[i].plot(split(x_sz_s)[i][:H_plot], 'C3--', lw=2, label="Sims–Zha")
axes[i].axhline(0, color='k', lw=0.5)
axes[i].set_title(f"{names[i]}, supply shock")
axes[i].set_xlabel("periods")
axes[0].legend(fontsize=8)
plt.tight_layout()
plt.show()
print(f"max |McKay–Wolf - truth| = {np.abs(x_mw_s - x_true_s).max():.1e}")
print(f"max |Sims–Zha - truth| = {np.abs(x_sz_s - x_true_s).max():.1e}")
max |McKay–Wolf - truth| = 5.6e-17
max |Sims–Zha - truth| = 1.9e-03
Again McKay and Wolf’s construction is exact, while the Sims–Zha construction, which keeps agents expecting the baseline rule, is not.
The policy effect is small here: removing the baseline rule changes the peak response of quantity by about 0.002, against a peak response of 0.033.
The Sims–Zha error is about two-thirds as large as the effect itself.
Exercise 29.5
In Induced Priors for Impulse Responses, sign restrictions that the theory implies with prior probability one identified a set of supply-shock responses that excluded the true response, because the supply shock is not fundamental.
We have seen that a sufficiently aggressive price-stabilizing rule makes the supply shock fundamental.
For the rules \(\phi = 0.5\) and \(\phi = 6\), compute the signs of the responses of \((q, p)\) to the two shocks at horizons \(0, \ldots, 5\) that hold for every draw from the prior \(g_s \sim U[8, 12]\), \(g_d \sim U[0.05, 0.15]\).
For each rule, give the econometrician the population VAR at the true parameters, impose those sign restrictions on rotations of the VAR innovations, and check whether the identified set of supply-shock responses contains the truth.
Solution
We compute the sign table from 150 prior draws and then sweep over rotations of the VAR innovations, normalizing each shock to raise quantity on impact, as in Induced Priors for Impulse Responses.
K_signs = 5
angles = np.linspace(0, 2 * np.pi, 20_000, endpoint=False)
def theory_sign_table(φ, n=150, seed=0):
"Signs of the responses of (q, p) that hold for every prior draw: +1, -1, or 0."
rng = np.random.default_rng(seed)
draws = []
for _ in range(n):
g_s_i, g_d_i = rng.uniform(8, 12), rng.uniform(0.05, 0.15)
M = np.vstack([private_block(g_s_i, g_d_i), rule_block(φ)])
X = np.linalg.solve(M, np.column_stack([forcing(0), forcing(1)]))
draws.append(ma_qp(X)[:K_signs + 1])
draws = np.array(draws)
return np.where((draws > 0).all(0), 1, np.where((draws < 0).all(0), -1, 0))
fig, axes = plt.subplots(2, 2, figsize=(12, 7))
for col, φ in enumerate([0.5, 6.0]):
table = theory_sign_table(φ)
D = ma_qp(structural_ma(g_s, g_d, φ))
V, C_w = wold(D)
L = np.linalg.cholesky(V)
r1 = L @ np.vstack([np.cos(angles), np.sin(angles)])
r2 = L @ np.vstack([-np.sin(angles), np.cos(angles)])
r1, r2 = r1 * np.sign(r1[0]), r2 * np.sign(r2[0]) # each shock raises q on impact
supply = np.einsum('tij,jm->tim', C_w[:H_plot], r1)
demand = np.einsum('tij,jm->tim', C_w[:H_plot], r2)
ok = (np.all(supply[:K_signs + 1] * table[:, :, [0]] >= 0, axis=(0, 1))
& np.all(demand[:K_signs + 1] * table[:, :, [1]] >= 0, axis=(0, 1)))
lo, hi = supply[:, :, ok].min(axis=2), supply[:, :, ok].max(axis=2)
inside = (lo - 1e-10 <= D[:H_plot, :, 0]) & (D[:H_plot, :, 0] <= hi + 1e-10)
print(f"φ = {φ}: ||L^-1 d_s|| = {impulse_norms(D, V)[0]:.3f}, "
f"share of rotations kept = {ok.mean():.4f}, "
f"truth inside the set: q {inside[:, 0].all()}, p {inside[:, 1].all()}")
for i in range(2):
ax = axes[i, col]
ax.fill_between(range(H_plot), lo[:, i], hi[:, i], color='C3', alpha=0.3,
label=f"sign-identified set, K = {K_signs}")
ax.plot(D[:H_plot, i, 0], 'k--', lw=2, label="true supply-shock response")
ax.axhline(0, color='k', lw=0.5)
ax.set_title(f"{names[i]}, supply shock, $\\phi = {φ}$")
ax.set_xlim(0, 12)
axes[0, 0].legend(fontsize=8)
plt.tight_layout()
plt.show()
φ = 0.5: ||L^-1 d_s|| = 0.866, share of rotations kept = 0.0160, truth inside the set: q False, p False
φ = 6.0: ||L^-1 d_s|| = 1.000, share of rotations kept = 0.0106, truth inside the set: q True, p True
Under the baseline rule the supply shock is not fundamental, and the theory-implied sign restrictions identify a set that misses the true response, just as in Induced Priors for Impulse Responses.
Under the aggressive rule the supply shock is fundamental, the true response is one of the admissible rotations, and the identified set contains it.
The sign table itself changes with the rule: the theory’s sign implications are properties of an equilibrium and are subject to the Lucas critique.