From 360e30f4965aa3868ce8f37b6a23ef62c86a913b Mon Sep 17 00:00:00 2001 From: thomassargent30 Date: Sun, 3 May 2026 13:03:43 -0400 Subject: [PATCH 01/25] Tom's April 3 edits of Phelan-Townsend DP^2 lecture --- lectures/PT_new.md | 1904 +++++++++++++++++++++++++++++++ lectures/_static/quant-econ.bib | 31 + lectures/_toc.yml | 1 + 3 files changed, 1936 insertions(+) create mode 100644 lectures/PT_new.md diff --git a/lectures/PT_new.md b/lectures/PT_new.md new file mode 100644 index 00000000..24470b7a --- /dev/null +++ b/lectures/PT_new.md @@ -0,0 +1,1904 @@ +--- +jupytext: + text_representation: + extension: .md + format_name: myst + format_version: 0.13 + jupytext_version: 1.14.4 +kernelspec: + display_name: Python (quantecon) + language: python + name: quantecon +--- +# Repeated Moral Hazard + +## Overview + +This lecture implements the model of repeated moral hazard studied by +{cite}`Phelan_Townsend_91`. + +Phelan and Townsend built on the recursive formulation of +{cite}`Spear_Srivastava_87`. + +Phelan and Townsend then used **lotteries** and **linear +programming** to compute optimal long-term incentive contracts. + +The lecture proceeds as follows. + +* We describe the Spear-Srivastava (1987) recursive formulation of + the principal-agent problem. +* We describe how Phelan and Townsend (1991) used lotteries to turn + the Bellman equation into a **linear program** (LP). +* We solve the **static** (one-period) version of the model and + replicate Figures 1--4 of {cite}`Phelan_Townsend_91`. +* We solve the **repeated** (multi-period) version and replicate + Figures 5--12 of {cite}`Phelan_Townsend_91`. + + +## Spear and Srivastava (1987) + +{cite}`Spear_Srivastava_87` presented a recursive formulation of an +infinitely repeated, discounted principal-agent problem. + +* A **principal** owns a technology that produces output $q_t$ at + time $t$ according to a conditional distribution $F(q_t | a_t)$ + that depends on the **effort** $a_t$ chosen by an **agent**. +* The principal does **not** observe $a_t$. +* The principal **does** observe $q_t$ at the end of period $t$ and + remembers the full history $\{q_s\}_{s=0}^t$. +* The principal is risk-neutral and has access to a loan market with + gross risk-free interest rate $\beta^{-1}$. +* The agent has preferences over random consumption streams ordered + by $E_0 \sum_{t=0}^\infty \beta^t u(c_t, a_t)$, where $u$ is + increasing in $c$ and decreasing in $a$. + +A **contract** $\sigma$ is a sequence of functions whose $t$-th +component $\sigma_t$ maps the history $q^{t-1} = (q_{t-1}, \ldots, +q_0)$ into a recommended effort $a_t$ and a promised consumption +$c_t$. + +The principal designs the contract to maximize expected discounted +surplus $E_0 \sum_{t=0}^\infty \beta^t \{q_t - c_t\}$. + +**Recursive representation.** +Let $w$ denote the discounted expected continuation utility that the +principal has promised to the agent at the start of a period. + +Spear and Srivastava showed that the state $w$ encodes all +payoff-relevant history and that the principal's problem reduces to +choosing functions $a(w)$, $c(w,q)$, and $\tilde{w}(w,q)$ subject to + +$$ +w = \int \bigl\{ u[c(w,q),\, a(w)] + \beta\,\tilde{w}(w,q) + \bigr\}\, dF[q \mid a(w)] +$$ (eq:eq1) + +and, for all alternative actions $\hat a \in A$, + +$$ +\int \bigl\{ u[c(w,q),\, a(w)] + \beta\,\tilde{w}(w,q) \bigr\}\, +dF[q\mid a(w)] +\;\geq\; +\int \bigl\{ u[c(w,q),\, \hat a] + \beta\,\tilde{w}(w,q) \bigr\}\, +dF[q\mid\hat a]. +$$ (eq:eq2) + +Equation {eq}`eq:eq1` is the **promise-keeping** constraint: the +contract must deliver the promised continuation utility $w$. + +Equation {eq}`eq:eq2` is the **incentive-compatibility** constraint: +the agent must prefer the recommended action $a(w)$ over any +deviation $\hat a$. + +The principal's value function $v(w)$ -- the maximum expected +discounted surplus attainable when the agent has been promised $w$ -- +satisfies the Bellman equation + +$$ +v(w) = \max_{a,\,c,\,\tilde{w}}\ +\int \bigl\{q - c(w,q) + \beta\, v[\tilde{w}(w,q)]\bigr\}\, +dF[q\mid a(w)] +$$ (eq:eq3) + +subject to the promise-keeping constraint {eq}`eq:eq1` and the +incentive-compatibility constraint {eq}`eq:eq2`. + + +## Phelan and Townsend (1991): Lotteries and Linear Programming + +A technical difficulty in problems like {eq}`eq:eq3` is that +incentive constraints can make the constraint set non-convex. + +{cite}`Phelan_Townsend_91` circumvented this problem by allowing the +principal to use **lotteries** -- randomizations over actions, +outputs, consumptions, and continuation values -- and restricting all +four variables to finite discrete grids. + +This convexification turns the Bellman equation {eq}`eq:eq3` into a +**linear program**. + +**Setup.** Let $P(q | a)$ be a family of discrete conditional +probability distributions over finite sets $Q$ (outputs) and $A$ +(actions). + +Let $C$ and $W$ be finite grids for consumption and +continuation values. + +The principal chooses a joint probability $\Pi(a, q, c, w')$ subject +to: + +$$ +\sum_{C \times W'} \Pi(\bar a, \bar q, c, w') = +P(\bar q \mid \bar a)\, +\sum_{Q \times C \times W'} \Pi(\bar a, q, c, w'), +\quad \forall\, \bar a,\, \bar q +$$ (eq:town1a) + +$$ +\Pi(a, q, c, w') \geq 0, +\qquad +\sum_{A \times Q \times C \times W'} \Pi(a, q, c, w') = 1. +$$ (eq:town1b) + +Equation {eq}`eq:town1a` says that conditional on action $\bar a$, +the marginal distribution of output follows $P(q \mid \bar a)$. + +Equations {eq}`eq:town1b` require the choice variables to be proper +probabilities. + +The **promise-keeping** constraint is + +$$ +w = \sum_{A \times Q \times C \times W'} +\bigl\{u(c, a) + \beta w'\bigr\}\,\Pi(a, q, c, w'). +$$ (eq:eq1prime) + +The **incentive-compatibility** constraint, for each pair +$(a, \hat a)$, is + +$$ +\sum_{Q \times C \times W'} \bigl\{u(c,a) + \beta w'\bigr\}\, +\Pi(a, q, c, w') +\;\geq\; +\sum_{Q \times C \times W'} \bigl\{u(c,\hat a) + \beta w'\bigr\}\, +\frac{P(q\mid\hat a)}{P(q\mid a)}\,\Pi(a, q, c, w'). +$$ (eq:eq2prime) + +The ratio $P(q\mid\hat a)/P(q\mid a)$ is the likelihood ratio that +updates the probability of outcome $q$ when the agent deviates from +the recommended action $a$ to $\hat a$. + +**Bellman operator as a linear program.** The principal's value +function satisfies + +$$ +v(w) = \max_{\Pi}\ +\sum_{A \times Q \times C \times W'} +\bigl\{(q - c) + \beta\, v(w')\bigr\}\,\Pi(a, q, c, w'), +$$ (eq:bell2) + +where the maximization is over probabilities $\Pi$ satisfying +{eq}`eq:town1a`, {eq}`eq:town1b`, {eq}`eq:eq1prime`, and +{eq}`eq:eq2prime`. + +This is a **linear program**: the objective and all constraints are +linear in the decision variables $\Pi$. + +Because $v(w')$ on the right side of {eq}`eq:bell2` is treated as a +**fixed** vector from the previous iteration, the Bellman operator +itself is a linear program. + +Phelan and Townsend iterated on this Bellman operator to convergence. + +At each iteration, an LP is solved for each grid point $w \in W$. + + +## Implementation + +We import some Python packages. + +```{code-cell} ipython3 +import numpy as np +import cvxpy as cp +from time import time +import gc +import matplotlib.pyplot as plt +from warnings import filterwarnings +``` + + +## The Static Economy + +This section replicates sections II and III of {cite}`Phelan_Townsend_91`, +which solve the **one-period** version of the model under full information +and private information (unobserved actions). + +### Setting + +The **full information problem** (FIP) maximises the principal's +expected surplus subject only to probability constraints and the +promise-keeping constraint: + +$$ +\max_{\Pi^w}\; \sum_{A \times Q \times C} (q - c)\,\Pi^w(a, q, c) +$$ + +subject to + +$$ +\begin{aligned} +\text{C1:}&\quad + w = \sum_{A \times Q \times C} U(a,c)\,\Pi^w(a,q,c) \\[4pt] +\text{C2:}&\quad + \sum_C \Pi^w(\bar a, \bar q, c) + = P(\bar q \mid \bar a) + \sum_{Q \times C} \Pi^w(\bar a, q, c), + \quad \forall\, \bar a,\, \bar q \\[4pt] +\text{C3:}&\quad + \sum_{A \times Q \times C} \Pi^w(a,q,c) = 1, + \quad \Pi^w(a,q,c) \geq 0 +\end{aligned} +$$ + +The **unobserved-action problem** adds an incentive-compatibility +constraint: + +$$ +\text{C4:}\quad +\sum_{Q \times C} U(a,c)\,\Pi^w(a,q,c) +\;\geq\; +\sum_{Q \times C} U(\hat a, c)\, +\frac{P(q\mid\hat a)}{P(q\mid a)}\,\Pi^w(a,q,c), +\quad \forall\, a,\, \hat a. +$$ + +### Parameterisation + +Following {cite}`Phelan_Townsend_91`, we use + +$$ +U(a, c) = 2\sqrt{c} + 2\sqrt{1-a} +$$ + +with discrete grids + +| Variable | Values | +| :------- | :----- | +| $a \in A$ | $\{0,\; 0.2,\; 0.4,\; 0.6\}$ | +| $q \in Q$ | $\{1,\; 2\}$ | +| $c \in C$ | $81$ equally spaced points on $[0,\, 2.25]$ | + +and conditional output probabilities + +| $a$ | $P(q=1)$ | $P(q=2)$ | +| :-----: | :------: | :------: | +| 0 | 0.9 | 0.1 | +| 0.2 | 0.6 | 0.4 | +| 0.4 | 0.4 | 0.6 | +| 0.6 | 0.25 | 0.75 | + +```{code-cell} ipython3 +def u(a, c): + return c**0.5 / 0.5 + (1 - a)**0.5 / 0.5 + +A = np.array([0, 0.2, 0.4, 0.6]) +Q = np.array([1, 2]) +C = np.linspace(0, 2.25, 81) +P = np.array([[0.9, 0.1], + [0.6, 0.4], + [0.4, 0.6], + [0.25, 0.75]]) +``` + +### Solving the Static Problem + +The function `solve_static_problem` solves both the FIP and the +unobserved-action problem for an array of promised utility values $w$. + +It implements constraints C1--C3 for the full information case and +adds C4 for the unobserved-action case. + +```{code-cell} ipython3 +# Define the function that solves the static problem +def solve_static_problem(W=None, + u=None, + A=None, + Q=None, + C=None, + P=None, + problem_type=None): + ''' + Function: Solve the static problem + + Parameters + ---------- + W: 1-D array + The expected utility. + u: function + The utility function in terms of actions and consumptions. + A: 1-D array + The finite set of possible actions. + Q: 1-D array + The finite set of possible outputs. + C: 1-D array + The finite set of possible consumptions. + P: 2-D array + The probability matrix of outputs given an action. + problem_type: str, "full information" or "unobserved-actions" + The problem type, i.e. the full information problem or the unobserved-action problem. + + Returns + ------- + s_W: 1-D array + The optimal values of surplus for each w in w_vec. + Pi: 4-D array + The probility of (a, q, c) given w. + ''' + + # Define parameter + n_A, n_Q, n_C = len(A), len(Q), len(C) + A_ind, Q_ind, C_ind = range(n_A), range(n_Q), range(n_C) + + Phi = np.array([[q-c for c in C] for q in Q]) + U = np.array([[u(a, c) for c in C] for a in A]) + + w = cp.Parameter() + + # Define variable Pi_x + Pi_list = list(np.zeros(n_A)) + + for a_ind in A_ind: + Pi_list[a_ind] = cp.Variable((n_Q, n_C)) + + # Define objective function + obj_expr = cp.sum([cp.sum(cp.multiply(Pi_list[a_ind], Phi)) + for a_ind in A_ind]) + obj = cp.Maximize(obj_expr) + + # Define constraints + C1 = [cp.sum([cp.sum([cp.sum(cp.multiply(Pi_list[a_ind][q_ind, :], + U[a_ind, :])) for a_ind in A_ind]) + for q_ind in Q_ind]) == w] + C2 = [(cp.sum(Pi_list[a_ind], axis=1)[q_ind] == P[a_ind, q_ind] * cp.sum(Pi_list[a_ind])) + for a_ind in A_ind for q_ind in Q_ind] + C3 = [cp.sum([cp.sum(Pi_list[a_ind]) for a_ind in A_ind]) == 1] + \ + [(Pi_list[a_ind] >= 0) for a_ind in A_ind] + + problem_type = problem_type.lower() + if problem_type == "full information": + constraints = C1 + C2 + C3 + else: + C4 = [(cp.sum([cp.sum(cp.multiply(Pi_list[a_ind][q_ind, :], U[a_ind, :])) + for q_ind in Q_ind]) >= + cp.sum([cp.sum(cp.multiply(Pi_list[a_ind][q_ind, :], + U[a_ind_hat, :])) * P[a_ind_hat, q_ind]/P[a_ind, q_ind] + for q_ind in Q_ind])) + for a_ind in A_ind for a_ind_hat in A_ind] + constraints = C1 + C2 + C3 + C4 + + # Create the problem + problem = cp.Problem(obj, constraints) + + # Initialize output variables + s_W = np.zeros(len(W)) + Pi = np.zeros((len(W), len(A), len(Q), len(C))) + + # Solve the problem + for i in range(len(W)): + w.value = W[i] + problem.solve(solver=cp.HIGHS) + s_W[i] = obj_expr.value + for a_ind in A_ind: + Pi[i, a_ind, :, :] = Pi_list[a_ind].value + + return s_W, Pi +``` + +### Figures 1-4 + +```{note} +Figures 3 and 4 require a simplex-based solver to reproduce +the vertices reported in {cite}`Phelan_Townsend_91`. We use +`cp.HIGHS`, which is available on Apple Silicon. +``` + +```{code-cell} ipython3 +W_static = np.linspace(1, 5, 100) +filterwarnings("ignore") + +s_W_full, Pi_full = solve_static_problem(W_static, u, A, Q, C, P, + "full information") +s_W_unobs, Pi_unobs = solve_static_problem(W_static, u, A, Q, C, P, + "unobserved-actions") +``` + +```{code-cell} ipython3 +# Figure 1 – Surplus functions +plt.figure(figsize=(6.5, 6.5)) +plt.plot(W_static, s_W_full) +plt.plot(W_static, s_W_unobs) +plt.hlines(0, 1.0, 5.0, linestyle="dashed") +plt.xlabel("w") +plt.ylabel("s(w)") +plt.xlim([1.0, 5.0]) +plt.ylim([-1.5, 2.0]) +plt.title("Figure 1\n Optimized surplus function", y=-0.2) +plt.text(2.5, 1.6, "Full Information", size=15) +plt.text(1.5, 0.8, "Unobserved Action", size=15) +plt.show() +``` + +```{code-cell} ipython3 +# Figure 2 – Expected effort +Ea_full = np.einsum('a,waqc->w', A, Pi_full) +Ea_unobs = np.einsum('a,waqc->w', A, Pi_unobs) + +plt.figure(figsize=(6.5, 6.5)) +plt.plot(W_static, Ea_full) +plt.plot(W_static, Ea_unobs) +plt.xlabel("w") +plt.ylabel("E{a(w)}") +plt.xlim([1.0, 5.0]) +plt.ylim([0.0, 0.8]) +plt.title("Figure 2\n Actions", y=-0.2) +plt.text(2.3, 0.65, "Full Information", size=15) +plt.text(2.6, 0.15, "Unobserved Action", size=15) +plt.show() +``` + +```{code-cell} ipython3 +# Figure 3 – Unobserved-action consumption schedule +Pi_sum_unobs = Pi_unobs.sum(axis=-1) # shape (W, A, Q) +Ec_unobs = (np.einsum('c,waqc->waq', C, Pi_unobs) + / np.where(Pi_sum_unobs > 1e-12, Pi_sum_unobs, 1.0)) + +l, m = len(A), len(Q) +X, Y = range(l), range(m) + +plt.figure(figsize=(6.5, 6.5)) +for x in X: + for y in Y: + plt.plot(W_static, Ec_unobs[:, x, y]) +plt.xlabel("w") +plt.ylabel("E(c) given a, q, w") +plt.xlim([1.0, 5.0]) +plt.ylim([0.0, 2.25]) +plt.title("Figure 3\n Unobserved Action Consumption", y=-0.3) +plt.annotate("a=.4, q=2", xy=(2.5, 0.5), xytext=(1.3, 0.7), + arrowprops={"arrowstyle":"-"}) +plt.annotate("a=.2, q=2", xy=(3.7, 1.5), xytext=(2.2, 1.65), + arrowprops={"arrowstyle":"-"}) +plt.annotate("a=0, q=(1,2)", xy=(4.8, 2.05), xytext=(3.0, 2.15), + arrowprops={"arrowstyle":"-"}) +plt.annotate("a=.2,\nq=2", xy=(2.0, 0.15), xytext=(1.3, 0.24), + arrowprops={"arrowstyle":"-"}) +plt.annotate("a=.4, q=1", xy=(3.0, 0.10), xytext=(3.6, 0.2), + arrowprops={"arrowstyle":"-"}) +plt.annotate("a=.2,\nq=1", xy=(4.0, 0.9), xytext=(4.3, 0.75), + arrowprops={"arrowstyle":"-"}) +plt.annotate("a=0, q=(1,2)\na=.2, q=1", xy=(2.1, 0), + xytext=(1.8, -0.3), arrowprops={"arrowstyle":"-"}) +plt.annotate(r"$\{$", fontsize=25, xy=(2.1, 0), xytext=(1.6, -0.3)) +plt.annotate(r"$\}$", fontsize=25, xy=(2.1, 0), xytext=(2.5, -0.3)) +plt.show() +``` + +```{code-cell} ipython3 +# Figure 4 – Full-information consumption schedule +Pi_sum_full = Pi_full.sum(axis=-1) # shape (W, A, Q) +Ec_full = (np.einsum('c,waqc->waq', C, Pi_full) + / np.where(Pi_sum_full > 1e-12, Pi_sum_full, 1.0)) + +plt.figure(figsize=(6.5, 6.5)) +for x in X: + for y in Y: + plt.plot(W_static, Ec_full[:, x, y]) +plt.xlabel("w") +plt.ylabel("E{c(w)}") +plt.xlim([1.0, 5.0]) +plt.ylim([0.0, 2.25]) +plt.title("Figure 4\n Full Information Consumption", y=-0.2) +plt.show() +``` + + +## The Repeated Economy + +We now extend the model to multiple periods. + +### Formulation + +The repeated problem replaces the static LP {eq}`eq:bell2` with a +Bellman equation. The principal's value function $s(w)$ satisfies + +$$ +s(w) = \max_{\Pi^w}\; +\sum_{A \times Q \times C \times W'} +\bigl\{(q - c) + \beta\, s(w')\bigr\}\,\Pi^w(a, q, c, w') +$$ + +subject to, for all $(\bar a, \bar q)$, + +$$ +\begin{aligned} +\text{C5:}&\quad + w = \sum_{A \times Q \times C \times W'} + \bigl\{U(a,c) + \beta w'\bigr\}\,\Pi^w(a,q,c,w') \\[4pt] +\text{C6:}&\quad + \sum_{C \times W'} \Pi^w(\bar a, \bar q, c, w') + = P(\bar q\mid\bar a) + \sum_{Q \times C \times W'} \Pi^w(\bar a, q, c, w') \\[4pt] +\text{C7:}&\quad + \sum_{A \times Q \times C \times W'} \Pi^w(a,q,c,w') = 1, + \quad \Pi^w(a,q,c,w') \geq 0 \\[4pt] +\text{C8:}&\quad + \sum_{Q \times C \times W'} + \bigl\{U(a,c)+\beta w'\bigr\}\Pi^w(a,q,c,w') + \;\geq\; + \sum_{Q \times C \times W'} + \bigl\{U(\hat a,c)+\beta w'\bigr\} + \frac{P(q\mid\hat a)}{P(q\mid a)}\Pi^w(a,q,c,w'), + \quad \forall\, a,\, \hat a. +\end{aligned} +$$ + +Constraints C5--C8 are the dynamic analogues of C1--C4. + +* Constraint C5 keeps the promise $w$. + +* Constraint C6 maintains the action-output law of motion. + +* Constraint C7 is a probability constraint. + +* Constraint C8 is incentive compatibility. + +We iterate the Bellman operator to convergence. + +At each iteration, a separate LP is solved for each grid point +$w \in W$. + +### The Two-Step Factored Algorithm + +Solving the four-dimensional LP at each step of value function +iteration is computationally demanding. + +Section VI of {cite}`Phelan_Townsend_91` proposes a factored +algorithm that splits each period into two sub-steps, exploiting the +additive separability of the utility function + +$$ +U(a, c) = 2\sqrt{1-a} + 2\sqrt{c}. +$$ + +**Step 1** (action and output, before consumption is decided). + +Let $w^m$ be the **intermediate** promised utility after the output +is observed but before consumption is allocated. + +Solve + +$$ +\begin{aligned} +\max_{\Pi^w}\; & +\sum_{A \times Q \times W^m} + \bigl\{q + s^m(w^m)\bigr\}\,\Pi^w(a, q, w^m) \\[4pt] +\text{C5:}&\quad + w = \sum_{A \times Q \times W^m} + \bigl\{2\sqrt{1-a} + w^m\bigr\}\,\Pi^w(a, q, w^m) \\[4pt] +\text{C6:}&\quad + \sum_{W^m} \Pi^w(\bar a, \bar q, w^m) + = P(\bar q\mid\bar a) + \sum_{Q \times W^m} \Pi^w(\bar a, q, w^m) \\[4pt] +\text{C7:}&\quad + \Pi^w(a,q,w^m) \geq 0,\quad + \sum_{A \times Q \times W^m} \Pi^w(a,q,w^m) = 1 \\[4pt] +\text{C8:}&\quad + \sum_{Q \times W^m} + \bigl\{2\sqrt{1-a} + w^m\bigr\}\Pi^w(a,q,w^m) + \;\geq\; + \sum_{Q \times W^m} + \bigl\{2\sqrt{1-\hat a} + w^m\bigr\} + \frac{P(q\mid\hat a)}{P(q\mid a)}\Pi^w(a,q,w^m), + \quad \forall\, a,\, \hat a. +\end{aligned} +$$ + +**Step 2** (consumption allocation). +Given $w^m$, solve + +$$ +\begin{aligned} +\max_{\Pi^{w^m}}\; & +\sum_{C \times W'} + \bigl\{\beta s(w') - c\bigr\}\,\Pi^{w^m}(c, w') \\[4pt] +\text{C5:}&\quad + w^m = \sum_{C \times W'} + \bigl\{2\sqrt{c} + \beta w'\bigr\}\,\Pi^{w^m}(c, w') \\[4pt] +\text{C7:}&\quad + \Pi^{w^m}(c,w') \geq 0,\quad + \sum_{C \times W'} \Pi^{w^m}(c,w') = 1. +\end{aligned} +$$ + +Step 2 is solved first (for all $w^m \in W^m$) to obtain $s^m(w^m)$. + +Then Step 1 is solved using $s^m$ as input. + +This factored algorithm significantly reduces computation time because +each sub-LP is smaller than the original joint LP. + +The two-step formulation is an approximation when $W^m$ is +discretised: as the number of grid points $N_m$ grows, it converges +to the exact solution. + +### Functions + +The function `solve_repeated_problem_2` implements one Bellman +iteration using the two-step algorithm. + +The function `solve_multi_period_economy_2` iterates to convergence +(or for a fixed number of periods $T$). + +```{code-cell} ipython3 +# Define the function that solves the dynamic problem at one iteration +def solve_repeated_problem_2(W=None, + W_m=None, + A=None, + Q=None, + C=None, + W_prime=None, + s_W_prime=None, + P=None, + problem_type=None, + β=0.8): + ''' + Function: Solve the dynamic problem at one iteration + + Parameters + ---------- + W: 1-D array + The expected utility. + A: 1-D array + The finite set of possible actions. + Q: 1-D array + The finite set of possible outputs. + C: 1-D array + The finite set of possible consumptions. + W_prime: 1-D array + The finite set of possible w_prime. + s_W_prime: 1-D array + The finite set of optimal values of surplus of w_prime. + P: 2-D array + The probability matrix of outputs given an action. + problem_type: str, "full information" or "unobserved-actions" + The problem type, i.e. the full information problem or the unobserved-action problem. + β: float, optional + The discouted factor. The value is 0.8 by default. + + Returns + ------- + s_W: 1-D array + The optimal values of surplus for each w in w_vec. + Pi_W_s1: 4-D array + The probility of (a, q, w_m) given w. + Pi_W_m_s2: 3-D array + The probility of (c, w_prime) given w_m. + ''' + + n_A, n_Q, n_C, n_W = len(A), len(Q), len(C), len(W) + n_W_m, n_W_prime = len(W_m), len(W_prime) + A_ind, Q_ind, C_ind = range(n_A), range(n_Q), range(n_C) + W_ind, W_m_ind, W_prime_ind = range(n_W), range(n_W_m), range(n_W_prime) + + # Problem of step 2 + + # Define parameters + Phi_s2 = np.array([[β * s_w_prime - c for s_w_prime in s_W_prime] for c in C]) + U_disc_s2 = np.array([[2 * c**0.5 + β * w_prime + for w_prime in W_prime] for c in C]) + + w_m_para = cp.Parameter() + + + # Define variables + Pi_w_m = cp.Variable((n_C, n_W_prime)) + + # Define the objective function + obj_expr_s2 = cp.sum(cp.multiply(Phi_s2, Pi_w_m)) + obj_s2 = cp.Maximize(obj_expr_s2) + + # Define constraints + C5_s2 = [cp.sum(cp.multiply(U_disc_s2, Pi_w_m)) == w_m_para] + C7_s2 = [cp.sum(Pi_w_m) == 1] + [Pi_w_m >= 0] + + # Create the problem of step 2 + problem_s2 = cp.Problem(obj_s2, C5_s2 + C7_s2) + + # Solve the probelm of step 2 + s_W_m = np.zeros(n_W_m) + Pi_W_m_s2 = np.zeros((n_W_m, n_C, n_W_prime)) + for w_m, w_m_ind in zip(W_m, W_m_ind): + w_m_para.value = w_m + problem_s2.solve(solver = cp.HIGHS) + s_W_m[w_m_ind] = obj_expr_s2.value + Pi_W_m_s2[w_m_ind, :, :] = Pi_w_m.value + + # Problem of step 1 + + # Define parameters + Phi_s1 = np.array([[(q+s_w_m) for s_w_m in s_W_m] for q in Q]) + U_disc_s1 = np.array([[[2 * (1 - a)**0.5 + w_m + for w_m in W_m] for q in Q] for a in A]) + U_disc_hat_s1 = np.array([[[[(2 * (1 - A[a_hat_ind])**0.5 + W_m[w_m_ind]) *\ + P[a_hat_ind, q_ind]/P[a_ind, q_ind] + for w_m_ind in W_m_ind] for q_ind in Q_ind] + for a_ind in A_ind] + for a_hat_ind in A_ind]) + + w_para = cp.Parameter() + + # Define variables + Pi_w_list = list(np.zeros(n_A)) + for a_ind in A_ind: + Pi_w_list[a_ind] = cp.Variable((n_Q, n_W_m)) + + # Define the objective function + obj_expr_s1 = cp.sum([cp.sum(cp.multiply(Phi_s1, Pi_w_list[a_ind])) + for a_ind in A_ind]) + obj_s1 = cp.Maximize(obj_expr_s1) + + # Define constraints + C5_s1 = [cp.sum([cp.sum(cp.multiply(U_disc_s1[a_ind, :, :], + Pi_w_list[a_ind])) + for a_ind in A_ind]) == w_para] + C6_s1 = [(cp.sum(Pi_w_list[a_ind][q_ind, :]) == P[a_ind, q_ind] *\ + cp.sum(Pi_w_list[a_ind])) + for q_ind in Q_ind for a_ind in A_ind] + C7_s1 = [cp.sum([cp.sum(Pi_w_list[a_ind]) for a_ind in A_ind]) == 1] + C7_s1 = C7_s1 + [(Pi_w_list[a_ind] >= 0) for a_ind in A_ind] + + problem_type = problem_type.lower() + if problem_type == "full information": + constraints_s1 = C5_s1 + C6_s1 + C7_s1 + else: + C8_s1 = [(cp.sum(cp.multiply(U_disc_s1[a_ind, :, :], + Pi_w_list[a_ind])) >= + cp.sum(cp.multiply(U_disc_hat_s1[a_hat_ind, a_ind, :, :], + Pi_w_list[a_ind]))) + for a_ind in A_ind for a_hat_ind in A_ind] + constraints_s1 = C5_s1 + C6_s1 + C7_s1 + C8_s1 + + # Create the problem of step 1 + problem_s1 = cp.Problem(obj_s1, constraints_s1) + + # Solve the problem of step 1 + s_W = np.zeros(n_W) + Pi_W_s1 = np.zeros((n_W, n_A, n_Q, n_W_m)) + for w, w_ind in zip(W, W_ind): + w_para.value = w + problem_s1.solve(solver = cp.HIGHS) + s_W[w_ind] = obj_expr_s1.value + for a_ind in A_ind: + Pi_W_s1[w_ind, a_ind, :, :] = Pi_w_list[a_ind].value + return s_W, Pi_W_s1, Pi_W_m_s2 +``` + +```{code-cell} ipython3 +# Define the function that solve the infinite-period or finite-period economy +def solve_multi_period_economy_2(A=None, + Q=None, + C=None, + P=None, + problem_type=None, + T=None, + β=0.8, + N=100, + N_m=100, + s_W_0=None, + tol=1e-8, + verbose=False): + ''' + Function: Solve the multi-period problem, either infinite-period or finite-period + + Parameters + ---------- + A: 1-D array + The finite set of possible actions. + Q: 1-D array + The finite set of possible outputs. + C: 1-D array + The finite set of possible consumptions. + P: 2-D array + The probability matrix of outputs given an action. + problem_type: str, "full information" or "unobserved-actions" + The problem type, i.e. the full information problem or the unobserved-action problem. + T: int, optional + The number of periods. If T is None, the algorithm solves the infinite-period economy. If T is some + integer, the algorithm solves the T-period economy. By default, T is None. + β: float, optional + The discouted factor in (0,1). The value is 0.8 by default. + N: int, optional + The length of discretized parameter space W. + N_m: int, optional + The length of discretized parameter space W_m. + s_W_0: 1-D array, optional + The initial guess for s_W with a length of N. + tol: float, optional + The precision of convergence. + verbose: bool, optional + If True, print progress at each iteration. The default is False. + + Returns + ------- + s_W: 1-D array + The optimal values of convergent surplus for each w in w_vec. + Pi_W_s1: 4-D array + The probility of (a, q, w_m) given w. + Pi_W_m_s2: 3-D array + The probility of (c, w_prime) given w_m. + ''' + + if β >= 1 or β <= 0: + raise ValueError('β should lie in [0,1]') + + # Define the function u[a,c] + def u(a, c): + return c**0.5/0.5 + (1-a)**0.5/0.5 + + if T is None: + # Discretize the parameter space W and W_m + problem_type = problem_type.lower() + if problem_type == "full information": + w_l = u(A.max(), C.min())/(1 - β) + w_u = u(A.min(), C.max())/(1 - β) + else: + w_l = u(A.min(), C.min())/(1 - β) + w_u = u(A.min(), C.max())/(1 - β) + W = np.linspace(w_l, w_u, N) + + W_m_l = β * w_l + 2 * C.min()**0.5 + W_m_u = β * w_u + 2 * C.max()**0.5 + W_m = np.linspace(W_m_l, W_m_u, N_m) + + # Assign initial value for s_W + if s_W_0 is not None: + s_W_prime = s_W_0 + else: + s_W_prime = np.zeros(N) + + # Iterate + optimal = False + iteration = 1 + while not optimal: + if verbose: + print('Iteration %i in process' % iteration) + start_time = time() + s_W, Pi_W_s1, Pi_W_m_s2 = solve_repeated_problem_2(W=W, W_m=W_m, + A=A, Q=Q, + C=C, W_prime=W, + s_W_prime=s_W_prime, + P=P, + problem_type=problem_type, + β=β) + end_time = time() + if verbose: + print('Iteration %i finished in:' % iteration, + round(end_time - start_time, 3), 's') + print('---------') + + if np.max(np.abs(s_W - s_W_prime)) <= tol: + optimal = True + else: + s_W_prime = s_W + + iteration += 1 + + if T is not None: + # Discretize the parameter space W + W_mat = np.zeros((T, N)) + + problem_type = problem_type.lower() + if problem_type == "full information": + w_l = u(A.max(), C.min()) + w_u = u(A.min(), C.max()) + else: + w_l = u(A.min(), C.min()) + w_u = u(A.min(), C.max()) + W_mat = np.cumsum(np.logspace(0, T-1, T, base=β).reshape(T, 1) *\ + np.linspace(w_l, w_u, N).reshape(1, N), + axis=0) + + # Solve the 1-period economy + if verbose: + print('Solving the 1-period economy') + print('-------') + s_W, Pi = solve_static_problem(W=W_mat[0, :], u=u, + A=A, Q=Q, C=C, P=P, + problem_type=problem_type) + + if T != 1: + for t in range(2, T+1): + if verbose: + print('Solving the %i-period economy' % t) + print('-------') + s_W_prime = np.copy(s_W) + W_m_l = β*W_mat[t-2,:].min() + 2*C.min()**0.5 + W_m_u = β*W_mat[t-2,:].max() + 2*C.max()**0.5 + W_m = np.linspace(W_m_l, W_m_u, N_m) + s_W, Pi_W_s1, Pi_W_m_s2 = solve_repeated_problem_2(W=W_mat[t-1,:], + W_m=W_m, A=A, + Q=Q, C=C, + W_prime=W_mat[t-2,:], + s_W_prime=s_W_prime, + P=P, + problem_type=problem_type, + β=β) + return s_W, Pi_W_s1, Pi_W_m_s2 +``` + +### Improved Solver: Pre-built Problems with Anderson Acceleration + +The original solver rebuilds all CVXPY problem objects on every +Bellman iteration, which causes memory to accumulate when many +iterations are needed -- a serious issue for $\beta$ close to 1. + +The function `solve_multi_period_economy_vfi` fixes this by building +the two sub-problems **once** with CVXPY `Parameter` objects for the +components that change between iterations ($\Phi^{s2}$ and $\Phi^{s1}$). +It also applies **Anderson acceleration** with a history of $m$ +recent iterates to speed up convergence, and accepts a `max_iter` +cap to prevent infinite loops. + +```{code-cell} ipython3 +def solve_multi_period_economy_vfi(A=None, + Q=None, + C=None, + P=None, + problem_type=None, + β=0.95, + N=50, + N_m=50, + s_W_0=None, + tol=1e-4, + max_iter=300, + m_anderson=5, + verbose=True): + """ + Infinite-horizon VFI using the two-step factored algorithm. + + Improvements over solve_multi_period_economy_2: + * CVXPY problems are built once with Parameter objects -- + no memory leak across iterations. + * Anderson acceleration (window m_anderson) reduces + the number of Bellman iterations needed. + * max_iter cap prevents unbounded runtime. + + Returns + ------- + s_W : 1-D array, converged surplus function on W + Pi_W_s1 : 4-D array, Pi(a, q, w_m | w) + Pi_W_m_s2 : 3-D array, Pi(c, w' | w_m) + W : 1-D array, the utility grid used + """ + if β >= 1 or β <= 0: + raise ValueError('β must lie in (0, 1)') + + def u_fn(a, c): + return c**0.5 / 0.5 + (1 - a)**0.5 / 0.5 + + problem_type = problem_type.lower() + if problem_type == "full information": + w_l = u_fn(A.max(), C.min()) / (1 - β) + w_u = u_fn(A.min(), C.max()) / (1 - β) + else: + w_l = u_fn(A.min(), C.min()) / (1 - β) + w_u = u_fn(A.min(), C.max()) / (1 - β) + + W = np.linspace(w_l, w_u, N) + W_m = np.linspace(β * w_l + 2 * C.min()**0.5, + β * w_u + 2 * C.max()**0.5, N_m) + + n_A, n_Q, n_C = len(A), len(Q), len(C) + A_ind, Q_ind = range(n_A), range(n_Q) + + # Fixed arrays (depend only on grids and P, not on s_W) + U_disc_s2 = np.array([[2 * c**0.5 + β * wp + for wp in W] for c in C]) # (n_C, N) + U_disc_s1 = np.array([[[2 * (1 - a)**0.5 + wm + for wm in W_m] for q in Q] + for a in A]) # (n_A, n_Q, N_m) + U_disc_hat_s1 = np.array([[[[ + (2 * (1 - A[ah])**0.5 + W_m[wmi]) * P[ah, qi] / P[ai, qi] + for wmi in range(N_m)] for qi in Q_ind] + for ai in A_ind] for ah in A_ind]) # (n_A, n_A, n_Q, N_m) + + # ---------------------------------------------------------- + # Step-2 CVXPY problem (built once) + # ---------------------------------------------------------- + Phi_s2_param = cp.Parameter((n_C, N)) # updated each outer iteration + w_m_para = cp.Parameter() + Pi_w_m = cp.Variable((n_C, N)) + + obj_expr_s2 = cp.sum(cp.multiply(Phi_s2_param, Pi_w_m)) + C5_s2 = [cp.sum(cp.multiply(U_disc_s2, Pi_w_m)) == w_m_para] + C7_s2 = [cp.sum(Pi_w_m) == 1, Pi_w_m >= 0] + problem_s2 = cp.Problem(cp.Maximize(obj_expr_s2), C5_s2 + C7_s2) + + # ---------------------------------------------------------- + # Step-1 CVXPY problem (built once) + # ---------------------------------------------------------- + Phi_s1_param = cp.Parameter((n_Q, N_m)) # updated after step 2 + w_para = cp.Parameter() + Pi_w_list = [cp.Variable((n_Q, N_m)) for _ in A_ind] + + obj_expr_s1 = cp.sum([cp.sum(cp.multiply(Phi_s1_param, Pi_w_list[ai])) + for ai in A_ind]) + C5_s1 = [cp.sum([cp.sum(cp.multiply(U_disc_s1[ai], Pi_w_list[ai])) + for ai in A_ind]) == w_para] + C6_s1 = [(cp.sum(Pi_w_list[ai][qi, :]) == + P[ai, qi] * cp.sum(Pi_w_list[ai])) + for qi in Q_ind for ai in A_ind] + C7_s1 = ([cp.sum([cp.sum(Pi_w_list[ai]) for ai in A_ind]) == 1] + + [Pi_w_list[ai] >= 0 for ai in A_ind]) + + if problem_type == "full information": + constraints_s1 = C5_s1 + C6_s1 + C7_s1 + else: + C8_s1 = [(cp.sum(cp.multiply(U_disc_s1[ai], Pi_w_list[ai])) >= + cp.sum(cp.multiply(U_disc_hat_s1[ah, ai], Pi_w_list[ai]))) + for ai in A_ind for ah in A_ind] + constraints_s1 = C5_s1 + C6_s1 + C7_s1 + C8_s1 + + problem_s1 = cp.Problem(cp.Maximize(obj_expr_s1), constraints_s1) + + # ---------------------------------------------------------- + # Initialise + # ---------------------------------------------------------- + s_W_prime = (np.array(s_W_0, dtype=float) + if s_W_0 is not None else np.zeros(N)) + + hist_x, hist_fx = [], [] + err = np.inf + + # ---------------------------------------------------------- + # Main iteration loop + # ---------------------------------------------------------- + for iteration in range(1, max_iter + 1): + t0 = time() + + # --- Step 2: solve for s_W_m --- + Phi_s2_param.value = np.array([[β * sv - c + for sv in s_W_prime] for c in C]) + s_W_m = np.zeros(N_m) + Pi_W_m_s2 = np.zeros((N_m, n_C, N)) + for i, wm in enumerate(W_m): + w_m_para.value = wm + problem_s2.solve(solver=cp.HIGHS, warm_start=True) + if obj_expr_s2.value is not None: + s_W_m[i] = obj_expr_s2.value + Pi_W_m_s2[i] = Pi_w_m.value + + # --- Step 1: solve for s_W --- + Phi_s1_param.value = np.array([[(q + swm) + for swm in s_W_m] for q in Q]) + s_W = np.zeros(N) + Pi_W_s1 = np.zeros((N, n_A, n_Q, N_m)) + for i, w in enumerate(W): + w_para.value = w + problem_s1.solve(solver=cp.HIGHS, warm_start=True) + if obj_expr_s1.value is not None: + s_W[i] = obj_expr_s1.value + for ai in A_ind: + if Pi_w_list[ai].value is not None: + Pi_W_s1[i, ai] = Pi_w_list[ai].value + + t1 = time() + err = np.max(np.abs(s_W - s_W_prime)) + if verbose: + print(f"Iter {iteration:3d}: max|ΔsW| = {err:.2e} ({t1-t0:.1f}s)") + + if err <= tol: + if verbose: + print(f"Converged in {iteration} iterations.") + break + + # --- Anderson acceleration --- + hist_x.append(s_W_prime.copy()) + hist_fx.append(s_W.copy()) + mk = min(len(hist_x), m_anderson) + if len(hist_x) > m_anderson: + hist_x.pop(0) + hist_fx.pop(0) + + if mk >= 2: + X = np.column_stack(hist_x[-mk:]) + FX = np.column_stack(hist_fx[-mk:]) + F = FX - X + FtF = F.T @ F + reg = max(1e-10 * np.trace(FtF) / mk, 1e-14) + ones = np.ones(mk) + try: + theta = np.linalg.solve(FtF + reg * np.eye(mk), ones) + theta /= ones @ theta + s_candidate = FX @ theta + s_next = (s_candidate + if np.all(np.isfinite(s_candidate)) + else s_W) + except np.linalg.LinAlgError: + s_next = s_W + else: + s_next = s_W + + s_W_prime = s_next + gc.collect() + + else: + print(f"Warning: did not converge after {max_iter} iterations. " + f"Final max|ΔsW| = {err:.2e}") + + return s_W, Pi_W_s1, Pi_W_m_s2, W +``` + +### Numerical Results + +We use the same parameters as for the static economy, plus a +discount factor $\beta = 0.8$ and grids of $N = N_m = 100$ points. + +**Initial values.** +We initialise the value function iteration with the one-period +(static) solution, scaled to discounted-sum units. + +```{code-cell} ipython3 +β = 0.8 +N = 100 +N_m = 100 + +W_l = u(A.min(), C.min()) / (1 - β) +W_u = u(A.min(), C.max()) / (1 - β) +W = np.linspace(W_l, W_u, N) + +W_m_l = β * W_l + 2 * C.min()**0.5 +W_m_u = β * W_u + 2 * C.max()**0.5 +W_m = np.linspace(W_m_l, W_m_u, N_m) + +in_time = time() +s_W_0, Pi_0 = solve_static_problem(W * (1 - β), u, + A, Q, C, P, + "unobserved-actions") +out_time = time() +print("Time(s):", round(out_time - in_time, 3)) +``` + +**Finite-period economy ($T = 3$).** + +```{code-cell} ipython3 +in_time = time() +s_W_T, Pi_W_s1_T, Pi_W_m_s2_T = solve_multi_period_economy_2( + A, Q, C, P, "unobserved-actions", + T=3, N=N, N_m=N_m) +out_time = time() +print("Time(s):", round(out_time - in_time, 3)) +``` + +```{code-cell} ipython3 +T = 3 +w_l_T = u(A.min(), C.min()) +w_u_T = u(A.min(), C.max()) +W_mat = np.cumsum( + np.logspace(0, T - 1, T, base=β).reshape(T, 1) * + np.linspace(w_l_T, w_u_T, N).reshape(1, N), + axis=0) +W_T = W_mat[2, :] + +plt.figure(figsize=(6.5, 6.5)) +plt.plot(W_T, s_W_T, "k-.") +plt.text(8, 3, "3-Period Unobserved Action", size=12) +plt.title("Figure\n Optimized surplus function", y=-0.2) +plt.show() +``` + +**Infinite-period economy.** + +```{code-cell} ipython3 +{ + "tags": [ + "hide-output" + ] +} +in_time = time() +s_W, Pi_W_s1, Pi_W_m_s2 = solve_multi_period_economy_2( + A, Q, C, P, "unobserved-actions", + N=N, N_m=N_m, + s_W_0=s_W_0 / (1 - β), + tol=1e-8) +out_time = time() +print("Time(s):", round(out_time - in_time, 3)) +``` + +```{code-cell} ipython3 +plt.figure(figsize=(6.5, 6.5)) +plt.plot(W, s_W, "k-.") +plt.xlim([5.0, 25.0]) +plt.ylim([-7.5, 10.0]) +plt.xlabel("w") +plt.ylabel("s(w)") +plt.title("Figure\n Optimized surplus function", y=-0.2) +plt.text(15, 6.5, "Infinity Unobserved Action", size=12) +plt.show() +``` + + +### Recovering $\Pi^w(a, q, c, w')$ + +The two-step algorithm returns +$\Pi^w(a, q, w^m)$ and $\Pi^{w^m}(c, w')$ separately. + +We recover the full joint distribution by using + +$$ +\Pi^w(a,q,c,w') += \sum_{w^m} + \Pi^w(a,q,w^m)\,\Pi^{w^m}(c,w'). +$$ + +```{code-cell} ipython3 +n_A, n_Q, n_C, n_W, n_W_prime = 4, 2, 81, N, N +A_ind, Q_ind, C_ind = range(n_A), range(n_Q), range(n_C) +W_ind, W_prime_ind = range(n_W), range(n_W_prime) + +Pi = np.array([[[[[ + Pi_W_s1[w_ind, a_ind, q_ind, :] @ + Pi_W_m_s2[:, c_ind, w_prime_ind] + for w_prime_ind in W_prime_ind] + for c_ind in C_ind] + for q_ind in Q_ind] + for a_ind in A_ind] + for w_ind in W_ind]) +``` + +#### Figure 5 + +```{code-cell} ipython3 +# Solve the static full information +W_full = np.linspace(5, 25, N) + +in_time = time() +s_W_1, Pi_1 = solve_static_problem(W_full*(1-β), u, A, + Q, C, P, "full information") +out_time = time() + +print("Time(s):", round(out_time - in_time, 3)) +``` + +```{code-cell} ipython3 +plt.figure(figsize=(6.5, 6.5)) +plt.plot(W, s_W, "k-.") +plt.plot(W, s_W_0/(1 - β), "yellow") +plt.plot(W_full, s_W_1/(1 - β), "red") +plt.xlim([5.0, 25.0]) +plt.ylim([-7.5, 10.0]) +plt.hlines(0, 5.0, 25.0, linestyle="dashed") +plt.xlabel("w") +plt.ylabel("s(w)") +plt.title("Figure 5\n Optimized surplus function", y=-0.2) +plt.text(5.4, -2.0, "Full Information (top)", size=12) +plt.text(5.4, -3.0, "T = infinity Unobserved Action (middle)", size=12) +plt.text(5.4, -4.0, "T = 1 Unobserved Action", size=12) +plt.show() +``` + +#### Figure 6 + +```{code-cell} ipython3 +# Calculate expected efforts +# T=1 Unobserved Action +X, Y = list(range(len(A))), list(range(len(Q))) +Z, N = list(range(len(C))), list(range(len(W))) +Ea_1 = np.array([np.sum([A[x]*Pi_0[i,x,:,:] for x in X]) for i in N]) + +# T=infinity unobserved Action +Ea_inf = np.array([np.sum([A[x]*Pi[i,x,:,:,:] for x in X]) for i in N]) + +# Plot expected efforts +plt.figure(figsize=(6.5, 6.5)) +plt.plot(W, Ea_1) +plt.plot(W, Ea_inf) +plt.xlabel("w") +plt.ylabel("E{a(w)}") +plt.xlim([5.0, 25.0]) +plt.ylim([0.0, 0.8]) +plt.title("Figure 6\n Actions", y=-0.2) +plt.text(14, 0.60, "T = infinity Unobserved Action (top)", size=10) +plt.text(14, 0.55, "T = 1 Unobserved Action (bottom)", size=10) +plt.show() +``` + +#### Figure 7 + +```{code-cell} ipython3 +def ex_con(Pi, A, Q, C, W, type="infinity"): + X, Y = list(range(len(A))), list(range(len(Q))) + Z, N = list(range(len(C))), list(range(len(W))) + Ec = np.zeros((len(N), len(X), len(Y))) + for i in N: + for x in X: + for y in Y: + if type == "infinity": + total_prob = np.sum(Pi[i,x,y,:,:]) + if total_prob <= 1e-9: + Ec[i,x,y] = float("-inf") + else: + Ec[i,x,y] = np.sum([np.sum(C[z] * Pi[i, x, y, z, :]) + for z in Z])/total_prob + elif type == "one": + total_prob = np.sum(Pi[i,x,y,:]) + if total_prob <= 1e-9: + Ec[i,x,y] = float("-inf") + else: + Ec[i,x,y] = np.sum([C[z] * Pi[i, x, y, :] + for z in Z])/total_prob + return Ec +``` + +```{code-cell} ipython3 +Ec_inf = ex_con(Pi, A, Q, C, W) + +# Plot expected consumption +plt.figure(figsize=(10.5, 10.5)) +for x in X: + for y in Y: + plt.plot(W, Ec_inf[:, x, y]) +plt.xlabel("w") +plt.ylabel("E(c) given a, q, w") +plt.xlim([5.0, 25.0]) +plt.ylim([0.0, 2.25]) +plt.title("Figure 7\n Unobserved Action Consumption", y=-0.3) +plt.annotate("a=.4, q=2", xy=(13.5, 0.5), xytext=(10.5, 0.7), + arrowprops={"arrowstyle":"-"}) +plt.annotate("a=.2, q=2", xy=(20.0, 1.3), xytext=(15.5, 1.65), + arrowprops={"arrowstyle":"-"}) +plt.annotate("a=0, q=(1,2)", xy=(24, 2.15), xytext=(15.0, 2.15), + arrowprops={"arrowstyle":"-"}) +plt.annotate("a=.2, q=2", xy=(10.1, 0.01), xytext=(7.5, 0.03), + arrowprops={"arrowstyle":"-"}) +plt.annotate("a=.4, q=2", xy=(10.5, 0.10), xytext=(7.5, 0.15), + arrowprops={"arrowstyle":"-"}) +plt.annotate("a=.6, q=2", xy=(11.5, 0.25), xytext=(8.5, 0.30), + arrowprops={"arrowstyle":"-"}) +plt.annotate("a=.6, q=1", xy=(12.5, 0.05), xytext=(14.5, 0.10), + arrowprops={"arrowstyle":"-"}) +plt.annotate("a=.4, q=1", xy=(15.0, 0.35), xytext=(18, 0.2), + arrowprops={"arrowstyle":"-"}) +plt.annotate("a=.2, q=1", xy=(20.0, 1.1), xytext=(21.5, 0.75), + arrowprops={"arrowstyle":"-"}) +plt.annotate("", xy=(10.0, 0), xytext=(11.5, -0.1), + arrowprops={"arrowstyle":"-"}) +plt.annotate("a=0, q=(1,2)\na={.2,.4}, q=1", fontsize=15, xy=(10.5, 0), + xytext=(5.5, -0.3)) +plt.annotate(r"$\{$",fontsize=35, xy=(10.5, 0), xytext=(4.5, -0.3)) +plt.annotate(r"$\}$",fontsize=35, xy=(10.5, 0), xytext=(9.5, -0.3)) +plt.show() +``` + +#### Figure 8 + +```{code-cell} ipython3 +def ex_ut(Pi, A, Q, C, W): + X, Y = list(range(len(A))), list(range(len(Q))) + Z, N = list(range(len(C))), list(range(len(W))) + Ew = np.zeros((len(N),len(X),len(Y))) + for i in N: + for x in X: + for y in Y: + total_prob = np.sum(Pi[i, x, y, :, :]) + if total_prob <= 1e-9: + Ew[i,x,y] = float("-inf") + else: + Ew[i,x,y] = np.sum([np.sum(W[w] * Pi[i, x, y, :, w]) + for w in N])/total_prob + return Ew +``` + +```{code-cell} ipython3 +Ew_inf = ex_ut(Pi, A, Q, C, W) + + +# Plot expected consumption +plt.figure(figsize=(7.5, 7.5)) +marker = [["o","v"],[">","<"],["x","1"],["2","3"]] +for x in X: + for y in Y: + plt.plot(W, Ew_inf[:,x,y],marker=marker[x][y]) +plt.plot(W,W,"k-.") +plt.xlabel("w") +plt.ylabel("E(w') given a, q, w") +plt.xlim([10.0, 25.0]) +plt.ylim([10.0, 25.0]) +plt.title("Figure 8\n Future Utility", y=-0.2) +plt.annotate("a=.4, q=2", xy=(14.0, 15.0), xytext=(10.5, 17.0), + arrowprops={"arrowstyle":"-"}) +plt.annotate("a=.2, q=2", xy=(19.5, 20.0), xytext=(15.0, 23.0), + arrowprops={"arrowstyle":"-"}) +plt.annotate("a=0, q=(1,2)", xy=(24.5, 24.5), xytext=(18.0, 24.5), + arrowprops={"arrowstyle":"-"}) +plt.annotate("a=.2, q=2", xy=(10.0, 10.7), xytext=(7.5, 10.7), + arrowprops={"arrowstyle":"-"}) +plt.annotate("a=.4, q=2", xy=(10.3, 11.2), xytext=(7.5, 11.2), + arrowprops={"arrowstyle":"-"}) +plt.annotate("a=.6, q=2", xy=(11.5, 12.2), xytext=(10.1, 14.0), + arrowprops={"arrowstyle":"-"}) +plt.annotate("a=.6, q=1", xy=(11.7, 10.7), xytext=(13.5, 10.7), + arrowprops={"arrowstyle":"-"}) +plt.annotate("a=.4, q=1", xy=(15.0, 14.0), xytext=(16.5, 12.5), + arrowprops={"arrowstyle":"-"}) +plt.annotate("a=.2, q=1", xy=(20.0, 19.5), xytext=(21.0, 18.0), + arrowprops={"arrowstyle":"-"}) +plt.annotate("", xy=(10.1, 10.1), xytext=(12.0, 9.2), + arrowprops={"arrowstyle":"-"}) +plt.annotate("a=0, q=(1,2)\na={.2,.4}, q=1", xy=(10.1, 10.1), xytext=(9.5, 8.5)) +plt.annotate(r"$\{$",fontsize=25, xy=(10.1, 10.1), xytext=(8.5, 8.5)) +plt.annotate(r"$\}$",fontsize=25, xy=(10.1, 10.1), xytext=(12.5, 8.5)) +plt.show() +``` + +For figures 9--12, {cite}`Phelan_Townsend_91` used $\beta = 0.95$. + +We now use `solve_multi_period_economy_vfi` -- which builds the CVXPY +problems once and applies Anderson acceleration -- to solve the +infinite-horizon economy at $\beta = 0.95$ with a grid of $N = N_m = 50$ +points. +Starting from the static solution rescaled to discounted-sum units, the +iteration converges to tolerance $10^{-4}$. + +```{code-cell} ipython3 +β_95 = 0.95 +N_95 = 50 +N_m95 = 50 + +# Initial value: static solution rescaled to infinite-horizon units +w_l_95 = u(A.min(), C.min()) / (1 - β_95) +w_u_95 = u(A.min(), C.max()) / (1 - β_95) +W_95 = np.linspace(w_l_95, w_u_95, N_95) + +s_W_0_95, _ = solve_static_problem(W_95 * (1 - β_95), u, A, Q, C, P, + "unobserved-actions") + +in_time = time() +s_W_new, Pi_W_s1_new, Pi_W_m_s2_new, W_new = solve_multi_period_economy_vfi( + A, Q, C, P, "unobserved-actions", + β=β_95, N=N_95, N_m=N_m95, + s_W_0=s_W_0_95 / (1 - β_95), + tol=1e-4, max_iter=300, m_anderson=5, + verbose=True) +out_time = time() +print("Time(s):", round(out_time - in_time, 3)) +``` + +```{code-cell} ipython3 +# Recover full joint distribution Pi(a, q, c, w' | w) +N_new = len(W_new) +A_ind_new = range(n_A) +Q_ind_new = range(n_Q) +C_ind_new = range(n_C) +W_ind_new = range(N_new) +Wp_ind_new = range(N_new) + +Pi_new = np.array([[[[[ + Pi_W_s1_new[wi, ai, qi, :] @ Pi_W_m_s2_new[:, ci, wpi] + for wpi in Wp_ind_new] + for ci in C_ind_new] + for qi in Q_ind_new] + for ai in A_ind_new] + for wi in W_ind_new]) +``` + +```{code-cell} ipython3 +Ew_beta = ex_ut(Pi_new, A, Q, C, W_new) +``` + +```{code-cell} ipython3 +def simulation(W, C, s_W, T, Pi, Ew, seed=12345): + # initial w such that s(w)=0 + w_index = np.argwhere(np.abs(s_W) == np.min(np.abs(s_W)))[0][0] + w0 = W[w_index] + date = np.arange(T) + + # set seed for random number + np.random.seed(seed) + randn = np.random.rand(T, 8) + + w_index1, w_index2 = w_index, w_index + w_series = w0*np.ones(T+1) + c_series = np.zeros(T) + Pi_c = list(np.zeros(T)) + Pi_w = list(np.zeros(T)) + + for i in range(T): + + w_index_temp1 = w_index1 + + Pi_temp_a = Pi[w_index_temp1, :, :, :, :].sum( + axis=1).sum( + axis=1).sum( + axis=1) + Pi_temp_a_cum = np.cumsum(Pi_temp_a / np.sum(Pi_temp_a)) + a_index = np.sum(randn[i, 0] >= Pi_temp_a_cum) + Pi_temp_q = Pi[w_index_temp1, a_index, :, :, :].sum( + axis=1).sum( + axis=1) + Pi_temp_q_cum = np.cumsum(Pi_temp_q / np.sum(Pi_temp_q)) + q_index = np.sum(randn[i, 1] >= Pi_temp_q_cum) + + Pi_temp_w = Pi[w_index_temp1, a_index, q_index, :, :].sum( + axis=0) + Pi_temp_w_cum = np.cumsum(Pi_temp_w/np.sum(Pi_temp_w)) + w_index1 = np.sum(randn[i, 2] >= Pi_temp_w_cum) + + # simulation for consumption as well as its distribution + Pi_c[i] = Pi[w_index_temp1, a_index, q_index, :, w_index1] + Pi_c[i] /= np.sum(Pi_c[i]) + Pi_temp_c_cum = np.cumsum(Pi_c[i]) + c_index = np.sum(randn[i, 3] >= Pi_temp_c_cum) + c_series[i] = C[c_index] + + # simulation for expected utility + w_series[i+1] = Ew[w_index_temp1, a_index, q_index] + + # simulation for distribution over future utility + Pi_temp_a = Pi[w_index2, :, :, :, :].sum(axis=1).sum( + axis=1).sum(axis=1) + Pi_temp_a_cum = np.cumsum(Pi_temp_a / np.sum(Pi_temp_a)) + a_index = np.sum(randn[i, 4] >= Pi_temp_a_cum) + Pi_temp_q = Pi[w_index2, a_index, :, :, :].sum( + axis=1).sum(axis=1) + Pi_temp_q_cum = np.cumsum(Pi_temp_q / np.sum(Pi_temp_q)) + q_index = np.sum(randn[i, 5] >= Pi_temp_q_cum) + Pi_temp_c = Pi[w_index2, a_index, q_index, :, :].sum(axis=1) + Pi_temp_c_cum = np.cumsum(Pi_temp_c/np.sum(Pi_temp_c)) + c_index = np.sum(randn[i, 6] >= Pi_temp_c_cum) + Pi_w[i] = Pi[w_index2, a_index, q_index, c_index, :] + Pi_w[i] /= np.sum(Pi_w[i]) + w_index2 = np.sum(randn[i,7] >= np.cumsum(Pi_w[i])) + + return c_series, w_series, Pi_w, Pi_c +``` + +```{code-cell} ipython3 +c_series = np.zeros((80, 4)) +w_series = np.zeros((81, 4)) +for i in range(4): + c_series[:, i], w_series[:, i], _, _ = simulation( + W_new, C, s_W_new, 80, Pi_new, Ew_beta, seed=(12345 + i)) +``` + +#### Figure 9 + +```{code-cell} ipython3 +# Plot consumption simulation +date_c = np.arange(80) + 1 +plt.figure(figsize=(6.5, 6.5)) +plt.plot(date_c, c_series[:, 0]) +plt.plot(date_c, c_series[:, 1]) +plt.plot(date_c, c_series[:, 2]) +plt.plot(date_c, c_series[:, 3]) +plt.xlabel("date") +plt.ylabel("consumption") +plt.xlim([0, 80]) +plt.ylim([0.00, 2.25]) +plt.title("Figure 9\n Individual Consumptions ($\\beta=0.95$)", y=-0.2) +plt.show() +``` + +#### Figure 10 + +```{code-cell} ipython3 +# Plot expected utility simulation +date_w = np.arange(81) +plt.figure(figsize=(6.5, 6.5)) +plt.plot(date_w, w_series[:, 0]) +plt.plot(date_w, w_series[:, 1]) +plt.plot(date_w, w_series[:, 2]) +plt.plot(date_w, w_series[:, 3]) +plt.xlabel("date") +plt.ylabel("expected utility") +plt.title("Figure 10\n Individual Utilities ($\\beta=0.95$)", y=-0.2) +plt.show() +``` + +```{code-cell} ipython3 +%time _, _, Pi_w, Pi_c = simulation(W_new, C, s_W_new, 80, Pi_new, Ew_beta) +``` + +#### Figure 11 + +```{code-cell} ipython3 +# Plotting distribution for consumption +%matplotlib inline + +date_mat_c = np.reshape(np.arange(80) + 1, (80, 1)) * \ + np.ones((1, len(C))) +c_mat = np.ones((80, 1)) @ np.reshape(C, (1, len(C))) + +fig = plt.figure(figsize=(8, 5)) +ax = fig.add_subplot(projection='3d') +plt.title("Figure 11 \n Consumptions over time ($\\beta=0.95$)", y=-0.3) +plt.xlabel('date') +plt.ylabel('consumption') +ax.set_zlabel('percentage') + +surf = ax.plot_surface(date_mat_c, c_mat, np.array(Pi_c), + cmap='viridis') +plt.show() +``` + +#### Figure 12 + +```{code-cell} ipython3 +# Plotting distribution for future utilities +%matplotlib inline +date_mat_w = np.reshape(np.arange(80) + 1, (80, 1)) * \ + np.ones((1, len(W_new))) +W_mat_12 = np.ones((80, 1)) @ np.reshape(W_new, (1, len(W_new))) + +fig = plt.figure(figsize=(8, 5)) +ax = fig.add_subplot(projection='3d') +plt.title("Figure 12 \n Utilities over time ($\\beta=0.95$)", y=-0.3) +plt.xlabel('date') +plt.ylabel('w') +ax.set_zlabel('percentage') + +surf = ax.plot_surface(date_mat_w, W_mat_12, np.array(Pi_w), + cmap='viridis') +plt.show() +``` + +## Concluding Remarks + +### Economics + +**Moral hazard and the cost of private information.** +When the principal cannot observe the agent's effort, the optimal contract +must balance two competing objectives: *insurance* (smoothing the agent's +consumption across output realizations) and *incentives* (rewarding high +output to make effort attractive). + +The unobserved-action surplus function in Figure 1 lies everywhere below +the full-information frontier, and the gap between them measures the +deadweight loss that private information imposes. + +**Dynamic contracts and promised utility.** +The recursive formulation of {cite}`Spear_Srivastava_87` compresses all +payoff-relevant history into a single scalar state: the discounted +expected continuation utility $w$ that the principal has promised the +agent. + +By tracking $w$ rather than the full history of outputs, the problem +becomes tractable. + +Figures 7--8 show that under the optimal infinite-horizon contract the +principal rewards high output by granting the agent a higher continuation +utility and punishes low output by lowering it -- a dynamic incentive +device that induces effort without relying on large contemporaneous +consumption differences alone. + +**Immiseration.** +Figures 9--12 illustrate a celebrated result of the repeated moral-hazard +literature: over time the distribution of the agent's continuation utility +drifts toward the lower boundary of the feasible set. + +Agents with persistently low output accumulate a history of downward +utility adjustments, eventually receiving near-zero consumption. + +This *immiseration* result emerges from a trade-off between incentive +provision and long-run insurance. + +### Technical Tricks + +**Lotteries and convexification.** +Incentive constraints can render the set of feasible contracts non-convex, +making standard optimization techniques unreliable. + +{cite}`Phelan_Townsend_91` circumvent this by allowing the principal to +choose a joint *lottery* $\Pi(a, q, c, w')$ over actions, outputs, +consumptions, and continuation values. + +Because any mixture of feasible lotteries is itself feasible, the +constraint set becomes convex, and global optima are well-defined. + +**Linear programming.** +With finite grids, the convexified Bellman equation is a linear program: +the objective $(q - c + \beta v(w'))$ and every constraint are linear in +$\Pi$. + +Treating $v(w')$ as a fixed vector from the previous iteration, value +function iteration reduces to solving one LP per grid point per +iteration -- a task handled efficiently by modern LP solvers such as +HiGHS. + +**Dynamic programming.** +The promised-utility state variable $w$ makes the problem recursive. +At each iteration the Bellman operator maps a surplus function $v$ to an +updated surplus function $Tv$; repeated application converges to the +infinite-horizon fixed point. + +Initializing from the scaled static solution substantially reduces the +number of iterations required. + +**Two-step factored algorithm.** +The additive separability $U(a,c) = 2\sqrt{1-a} + 2\sqrt{c}$ allows the +four-dimensional LP to be split into two smaller sub-problems. + +*Step 2* allocates consumption given an intermediate promised utility +$w^m$; *Step 1* assigns actions, outputs, and intermediate continuation +utilities given $w$. + +Because each sub-LP has far fewer decision variables than the full joint +LP, computation is substantially faster and the approach scales to finer +grids. + +**Dynamic programming squared.** +This lecture is an instance of what Lars Ljungqvist and Thomas Sargent call +**dynamic programming squared** in their textbook +{cite}`Ljungqvist2012`. + +The phrase refers to problems in which a value or value function from +one Bellman equation appears as a key argument inside a second Bellman +equation. + +Here the surplus function $s(w)$ -- the solution to the principal's +outer dynamic program -- has the agent's continuation utility $w$ as its +state variable, and $w$ itself is determined by an *inner* promise-keeping +constraint that enforces the agent's Bellman equation. + +Two Bellman equations are therefore nested: the principal's value +function $v(w)$ and the agent's promised-utility evolution +$w' = \tilde w(w, q)$ are solved simultaneously. + +The same architecture reappears throughout this lecture series. +In {doc}`Stackelberg plans ` the Stackelberg leader's +value function takes the followers' competitive-equilibrium value +function as an argument. + +In {doc}`Optimal Taxation with State-Contingent Debt `, +a Ramsey planner's outer Bellman equation uses the household's +marginal utility of wealth $x$ -- itself defined by an inner +implementability constraint -- as its state variable. + +In the {doc}`Calvo model ` and the two Chang lectures +({doc}`Ramsey plans ` and +{doc}`credible policies `), a government's value +function takes the private sector's continuation value $\theta$ +as an argument, with $\theta$ governed by its own Bellman equation. + +In {doc}`Unemployment Insurance ` the planner's +contract-design problem embeds the worker's continuation utility +as the state variable in an outer surplus-maximization program, +producing the same nested structure that drives immiseration in +the present lecture. + +In all of these settings, the inner dynamic program defines a +state variable -- a promised utility, a marginal value, or a +continuation value -- that restricts what the outer dynamic +program can promise or deliver. + + + +## Exercises + +````{admonition} Exercise 1 +:class: exercise + +Using the surplus arrays `s_W_full` and `s_W_unobs` computed in the +static section, define the **agency cost function** + +$$ +\delta(w) = s^{FI}(w) - s^{UA}(w), \quad w \in W_{static}. +$$ + +1. Plot $\delta(w)$ over $W_{static} = [1, 5]$. +2. Report the value $\hat{w}$ at which $\delta$ is largest. +3. Explain intuitively why agency costs are highest at that level of + promised utility. +```` + +````{dropdown} Solution to Exercise 1 +:class: dropdown + +```{code-cell} ipython3 +delta_W = s_W_full - s_W_unobs + +plt.figure(figsize=(6.5, 6.5)) +plt.plot(W_static, delta_W) +plt.xlabel("w") +plt.ylabel(r"$\delta(w) = s^{FI}(w) - s^{UA}(w)$") +plt.xlim([1.0, 5.0]) +plt.ylim(bottom=0.0) +plt.title("Agency Cost in the Static Model", y=-0.2) +plt.show() + +w_hat = W_static[np.argmax(delta_W)] +print(f"Largest agency cost at w = {w_hat:.3f}, δ = {delta_W.max():.4f}") +``` + +Agency costs are highest near intermediate levels of promised utility +because at those values the principal most values inducing high effort +(output is valuable) while the agent still requires meaningful +consumption-state variation to be incentivized. +At low $w$ the agent is near subsistence and effort is low anyway; +at high $w$ the agent is nearly fully insured and the marginal incentive +cost of each additional unit of effort is small. +```` + +````{admonition} Exercise 2 +:class: exercise + +The output probability matrix $P$ governs how informative output is about +effort. +Define a **flatter** probability matrix + +$$ +P_{flat} = \begin{pmatrix} +0.70 & 0.30 \\ +0.55 & 0.45 \\ +0.45 & 0.55 \\ +0.30 & 0.70 +\end{pmatrix} +$$ + +in which output is less informative about effort than in the baseline $P$. + +1. Re-solve the static unobserved-action problem with $P_{flat}$ and the + same grid $W_{static}$. +2. On a single figure with two panels, compare the surplus functions + $s^{UA}(w)$ and expected effort levels $E\{a(w)\}$ under $P$ and + $P_{flat}$. +3. Explain the economic intuition for any differences you find. +```` + +````{dropdown} Solution to Exercise 2 +:class: dropdown + +```{code-cell} ipython3 +P_flat = np.array([[0.70, 0.30], + [0.55, 0.45], + [0.45, 0.55], + [0.30, 0.70]]) + +s_W_flat, Pi_flat = solve_static_problem(W_static, u, A, Q, C, P_flat, + "unobserved-actions") +Ea_flat = np.einsum('a,waqc->w', A, Pi_flat) + +fig, axes = plt.subplots(1, 2, figsize=(13, 6)) + +axes[0].plot(W_static, s_W_unobs, label="Baseline $P$") +axes[0].plot(W_static, s_W_flat, label="Flat $P$") +axes[0].hlines(0, 1.0, 5.0, linestyle="dashed") +axes[0].set_xlabel("w") +axes[0].set_ylabel("s(w)") +axes[0].set_xlim([1.0, 5.0]) +axes[0].set_title("Surplus Function", y=-0.2) +axes[0].legend() + +axes[1].plot(W_static, Ea_unobs, label="Baseline $P$") +axes[1].plot(W_static, Ea_flat, label="Flat $P$") +axes[1].set_xlabel("w") +axes[1].set_ylabel(r"$E\{a(w)\}$") +axes[1].set_xlim([1.0, 5.0]) +axes[1].set_ylim([0.0, 0.8]) +axes[1].set_title("Expected Effort", y=-0.2) +axes[1].legend() + +plt.tight_layout() +plt.show() +``` + +With $P_{flat}$ output carries less statistical information about effort: +the likelihood ratio $P(q \mid \hat{a}) / P(q \mid a)$ is closer to 1 +for all deviations $\hat{a} \neq a$. +The incentive-compatibility constraint {eq}`eq:eq2prime` therefore becomes +harder to satisfy: large consumption rewards for high output must be +offered to deter deviations, crowding out insurance. +As a result the principal extracts less surplus and induces less effort +than under the baseline $P$ -- the surplus function shifts down and +expected effort falls. +```` diff --git a/lectures/_static/quant-econ.bib b/lectures/_static/quant-econ.bib index 133a5ef4..6707f3ec 100644 --- a/lectures/_static/quant-econ.bib +++ b/lectures/_static/quant-econ.bib @@ -1320,6 +1320,37 @@ @book{Roman2005 publisher={Springer} } + +@article{Spear_Srivastava_87, + author = {Stephen E. Spear and Sanjay Srivastava}, + title = {{On Repeated Moral Hazard with Discounting}}, + journal = {Review of Economic Studies}, + year = 1987, + volume = {54}, + number = {4}, + pages = {599-617}, + month = {}, + keywords = {}, + doi = {}, + abstract = {In this paper, we analyze optimal contracts in an infinitely repeated agency model in which both the principal and agent discount the future. We show that there is a stationary representation of the optimal contract when the agent's conditional discounted expected utility is used as a state variable. This representation reduces the multi-period problem to a static variational problem which can be analyzed using standard variational techniques. This reduction is used to obtain several properties of the contract.}, + url = {https://ideas.repec.org/a/oup/restud/v54y1987i4p599-617..html} +} + +@article{Phelan_Townsend_91, + author = {Christopher Phelan and Robert M. Townsend}, + title = {{Computing Multi-Period, Information-Constrained Optima}}, + journal = {Review of Economic Studies}, + year = 1991, + volume = {58}, + number = {5}, + pages = {853-881}, + month = {}, + keywords = {}, + doi = {}, + abstract = {This paper presents a detailed theoretical derivation and justification for methods used to compute solutions to a multi-period (including infinite-period), continuum-agent, unobservedeffort economy. Actual solutions are displayed illustrating cross-sectional variability in consumption and labour effort in the population at a point in time and variability for a typical individual over time. The optimal tradeoff between insurance and incentives is explored and the issue of excess variability is addressed by consideration of the analogue full-information economy and various restricted-contracting regimes.}, + url = {https://ideas.repec.org/a/oup/restud/v58y1991i5p853-881..html} +} + @article{PhelanStacchetti2001, author={Christopher Phelan and Ennio Stacchetti}, title={Sequential Equilibria in a Ramsey Tax Model}, diff --git a/lectures/_toc.yml b/lectures/_toc.yml index 072a56c3..1234e144 100644 --- a/lectures/_toc.yml +++ b/lectures/_toc.yml @@ -71,6 +71,7 @@ parts: - caption: Dynamic Programming Squared numbered: true chapters: + - file: PT_new - file: un_insure - file: dyn_stack - file: calvo_machine_learn From e0437e3af580a67330df0222d80223de842de41c Mon Sep 17 00:00:00 2001 From: HumphreyYang Date: Tue, 19 May 2026 21:22:51 +0800 Subject: [PATCH 02/25] updates --- lectures/PT_new.md | 372 +++++++++++++++++++++----------- lectures/_static/quant-econ.bib | 11 +- 2 files changed, 247 insertions(+), 136 deletions(-) diff --git a/lectures/PT_new.md b/lectures/PT_new.md index 24470b7a..f0793b49 100644 --- a/lectures/PT_new.md +++ b/lectures/PT_new.md @@ -14,37 +14,44 @@ kernelspec: ## Overview -This lecture implements the model of repeated moral hazard studied by +This lecture computes the information-constrained optima studied by {cite}`Phelan_Townsend_91`. -Phelan and Townsend built on the recursive formulation of -{cite}`Spear_Srivastava_87`. +Their paper studies a continuum-agent economy with unobserved effort. -Phelan and Townsend then used **lotteries** and **linear -programming** to compute optimal long-term incentive contracts. +The planner chooses lotteries over individual histories, subject to +promise-keeping and incentive-compatibility constraints, and maximizes +discounted social surplus. -The lecture proceeds as follows. +The key recursive idea comes from {cite}`Spear_Srivastava_87`: an +agent's promised continuation utility is a sufficient state variable. -* We describe the Spear-Srivastava (1987) recursive formulation of - the principal-agent problem. -* We describe how Phelan and Townsend (1991) used lotteries to turn - the Bellman equation into a **linear program** (LP). -* We solve the **static** (one-period) version of the model and +Phelan and Townsend combine that idea with lotteries, finite grids, and +linear programming to compute full-information, static +unobserved-action, and repeated unobserved-action allocations. + +We proceed as follows. + +* We review the promised-utility recursion of + {cite}`Spear_Srivastava_87`. +* We formulate the Phelan-Townsend lottery problem and its finite-grid + linear-programming approximation. +* We solve the *static* version of the economy and replicate Figures 1--4 of {cite}`Phelan_Townsend_91`. -* We solve the **repeated** (multi-period) version and replicate +* We solve the *repeated* economy and replicate Figures 5--12 of {cite}`Phelan_Townsend_91`. ## Spear and Srivastava (1987) -{cite}`Spear_Srivastava_87` presented a recursive formulation of an -infinitely repeated, discounted principal-agent problem. +{cite}`Spear_Srivastava_87` showed how to write an infinitely repeated, +discounted principal-agent problem recursively. * A **principal** owns a technology that produces output $q_t$ at time $t$ according to a conditional distribution $F(q_t | a_t)$ that depends on the **effort** $a_t$ chosen by an **agent**. -* The principal does **not** observe $a_t$. -* The principal **does** observe $q_t$ at the end of period $t$ and +* The principal does *not* observe $a_t$. +* The principal *does* observe $q_t$ at the end of period $t$ and remembers the full history $\{q_s\}_{s=0}^t$. * The principal is risk-neutral and has access to a loan market with gross risk-free interest rate $\beta^{-1}$. @@ -52,21 +59,20 @@ infinitely repeated, discounted principal-agent problem. by $E_0 \sum_{t=0}^\infty \beta^t u(c_t, a_t)$, where $u$ is increasing in $c$ and decreasing in $a$. -A **contract** $\sigma$ is a sequence of functions whose $t$-th -component $\sigma_t$ maps the history $q^{t-1} = (q_{t-1}, \ldots, -q_0)$ into a recommended effort $a_t$ and a promised consumption -$c_t$. +A **contract** recommends effort before output is realized and then +assigns consumption and continuation promises as functions of observed +output histories. The principal designs the contract to maximize expected discounted surplus $E_0 \sum_{t=0}^\infty \beta^t \{q_t - c_t\}$. -**Recursive representation.** Let $w$ denote the discounted expected continuation utility that the principal has promised to the agent at the start of a period. -Spear and Srivastava showed that the state $w$ encodes all -payoff-relevant history and that the principal's problem reduces to -choosing functions $a(w)$, $c(w,q)$, and $\tilde{w}(w,q)$ subject to +The promised utility $w$ summarizes payoff-relevant history. Given +$w$, the principal chooses a recommended action $a(w)$, an +output-contingent consumption rule $c(w,q)$, and an output-contingent +next-period promise $\tilde w(w,q)$ subject to $$ w = \int \bigl\{ u[c(w,q),\, a(w)] + \beta\,\tilde{w}(w,q) @@ -107,37 +113,45 @@ incentive-compatibility constraint {eq}`eq:eq2`. ## Phelan and Townsend (1991): Lotteries and Linear Programming A technical difficulty in problems like {eq}`eq:eq3` is that -incentive constraints can make the constraint set non-convex. +incentive constraints can make deterministic contract problems +non-convex. + +{cite}`Phelan_Townsend_91` instead formulate the planning problem in +terms of **lotteries** over actions, outputs, consumptions, and +continuation utilities. -{cite}`Phelan_Townsend_91` circumvented this problem by allowing the -principal to use **lotteries** -- randomizations over actions, -outputs, consumptions, and continuation values -- and restricting all -four variables to finite discrete grids. +At the aggregate level these probabilities +are also population fractions, so individual randomization creates no +aggregate uncertainty in their continuum-agent economy. -This convexification turns the Bellman equation {eq}`eq:eq3` into a -**linear program**. +For computation, all relevant sets are restricted to finite grids. -**Setup.** Let $P(q | a)$ be a family of discrete conditional +On +those grids the Bellman step is a **linear program**. + +*Setup.* Let $P(q | a)$ be a family of discrete conditional probability distributions over finite sets $Q$ (outputs) and $A$ -(actions). +(actions). -Let $C$ and $W$ be finite grids for consumption and -continuation values. +Let $C$ and $W'$ be finite grids for current consumption and next-period +promised utility. -The principal chooses a joint probability $\Pi(a, q, c, w')$ subject -to: +For each current promise $w$, the planner chooses a joint probability +$\Pi^w(a, q, c, w')$ subject to: $$ -\sum_{C \times W'} \Pi(\bar a, \bar q, c, w') = +\sum_{c \in C}\sum_{w' \in W'} \Pi^w(\bar a, \bar q, c, w') = P(\bar q \mid \bar a)\, -\sum_{Q \times C \times W'} \Pi(\bar a, q, c, w'), +\sum_{q \in Q}\sum_{c \in C}\sum_{w' \in W'} + \Pi^w(\bar a, q, c, w'), \quad \forall\, \bar a,\, \bar q $$ (eq:town1a) $$ -\Pi(a, q, c, w') \geq 0, +\Pi^w(a, q, c, w') \geq 0, \qquad -\sum_{A \times Q \times C \times W'} \Pi(a, q, c, w') = 1. +\sum_{a \in A}\sum_{q \in Q}\sum_{c \in C}\sum_{w' \in W'} + \Pi^w(a, q, c, w') = 1. $$ (eq:town1b) Equation {eq}`eq:town1a` says that conditional on action $\bar a$, @@ -150,7 +164,7 @@ The **promise-keeping** constraint is $$ w = \sum_{A \times Q \times C \times W'} -\bigl\{u(c, a) + \beta w'\bigr\}\,\Pi(a, q, c, w'). +\bigl\{u(c, a) + \beta w'\bigr\}\,\Pi^w(a, q, c, w'). $$ (eq:eq1prime) The **incentive-compatibility** constraint, for each pair @@ -158,23 +172,23 @@ $(a, \hat a)$, is $$ \sum_{Q \times C \times W'} \bigl\{u(c,a) + \beta w'\bigr\}\, -\Pi(a, q, c, w') +\Pi^w(a, q, c, w') \;\geq\; \sum_{Q \times C \times W'} \bigl\{u(c,\hat a) + \beta w'\bigr\}\, -\frac{P(q\mid\hat a)}{P(q\mid a)}\,\Pi(a, q, c, w'). +\frac{P(q\mid\hat a)}{P(q\mid a)}\,\Pi^w(a, q, c, w'). $$ (eq:eq2prime) The ratio $P(q\mid\hat a)/P(q\mid a)$ is the likelihood ratio that updates the probability of outcome $q$ when the agent deviates from the recommended action $a$ to $\hat a$. -**Bellman operator as a linear program.** The principal's value +*Bellman operator as a linear program.* The principal's value function satisfies $$ v(w) = \max_{\Pi}\ \sum_{A \times Q \times C \times W'} -\bigl\{(q - c) + \beta\, v(w')\bigr\}\,\Pi(a, q, c, w'), +\bigl\{(q - c) + \beta\, v(w')\bigr\}\,\Pi^w(a, q, c, w'), $$ (eq:bell2) where the maximization is over probabilities $\Pi$ satisfying @@ -182,15 +196,18 @@ where the maximization is over probabilities $\Pi$ satisfying {eq}`eq:eq2prime`. This is a **linear program**: the objective and all constraints are -linear in the decision variables $\Pi$. +linear in the decision variables $\Pi^w$. Because $v(w')$ on the right side of {eq}`eq:bell2` is treated as a -**fixed** vector from the previous iteration, the Bellman operator +*fixed* vector from the previous iteration, the Bellman operator itself is a linear program. -Phelan and Townsend iterated on this Bellman operator to convergence. +Phelan and Townsend solve one LP for each grid point $w \in W$ and +iterate on the surplus function. -At each iteration, an LP is solved for each grid point $w \in W$. +Their Theorem 4 gives the +contraction result that justifies this iteration for the +infinite-horizon problem. ## Implementation @@ -209,15 +226,18 @@ from warnings import filterwarnings ## The Static Economy -This section replicates sections II and III of {cite}`Phelan_Townsend_91`, -which solve the **one-period** version of the model under full information -and private information (unobserved actions). +This section replicates Sections II and III of +{cite}`Phelan_Townsend_91`. + +Section II studies the full-information benchmark. + +Section III adds +unobserved actions and the resulting incentive constraints. ### Setting -The **full information problem** (FIP) maximises the principal's -expected surplus subject only to probability constraints and the -promise-keeping constraint: +The **full-information problem** (FIP) maximizes expected surplus +subject only to feasibility and promise keeping: $$ \max_{\Pi^w}\; \sum_{A \times Q \times C} (q - c)\,\Pi^w(a, q, c) @@ -240,8 +260,10 @@ $$ \end{aligned} $$ -The **unobserved-action problem** adds an incentive-compatibility -constraint: +The **unobserved-action problem** adds incentive compatibility. For +each recommended action $a$ and each possible deviation $\hat a$, the +utility from obeying must be at least as large as the utility from +deviating while preserving the same output-contingent consumption rule: $$ \text{C4:}\quad @@ -249,12 +271,13 @@ $$ \;\geq\; \sum_{Q \times C} U(\hat a, c)\, \frac{P(q\mid\hat a)}{P(q\mid a)}\,\Pi^w(a,q,c), -\quad \forall\, a,\, \hat a. +\quad \forall\, a,\, \hat a \in A. $$ ### Parameterisation -Following {cite}`Phelan_Townsend_91`, we use +Following {cite}`Phelan_Townsend_91`, we use the period utility +function $$ U(a, c) = 2\sqrt{c} + 2\sqrt{1-a} @@ -277,6 +300,12 @@ and conditional output probabilities | 0.4 | 0.4 | 0.6 | | 0.6 | 0.25 | 0.75 | +These are the parameter values used to construct Figures 1--8 in the +paper. + +The static grid of promised utility values below spans the +interval $[1,5]$, matching the horizontal scale in Figures 1--4. + ```{code-cell} ipython3 def u(a, c): return c**0.5 / 0.5 + (1 - a)**0.5 / 0.5 @@ -332,7 +361,7 @@ def solve_static_problem(W=None, s_W: 1-D array The optimal values of surplus for each w in w_vec. Pi: 4-D array - The probility of (a, q, c) given w. + The probability of (a, q, c) given w. ''' # Define parameter @@ -397,9 +426,11 @@ def solve_static_problem(W=None, ### Figures 1-4 ```{note} -Figures 3 and 4 require a simplex-based solver to reproduce -the vertices reported in {cite}`Phelan_Townsend_91`. We use -`cp.HIGHS`, which is available on Apple Silicon. +Phelan and Townsend report solutions computed with standard revised +simplex methods. We use HiGHS through CVXPY. At degenerate utility +grid points, a different LP solver can select a different optimal +lottery, so some consumption schedules can differ slightly even when +the surplus function is unchanged. ``` ```{code-cell} ipython3 @@ -504,12 +535,24 @@ plt.show() ## The Repeated Economy -We now extend the model to multiple periods. +We now move from the one-period economy to the finite- and +infinite-horizon economies studied in Section IV of +{cite}`Phelan_Townsend_91`. + +The planner maximizes discounted social surplus. + +As in the paper, this +can be interpreted as allowing society to borrow and lend at the constant +gross interest rate $\beta^{-1}$, so that discounted surplus is the +right feasibility criterion. ### Formulation -The repeated problem replaces the static LP {eq}`eq:bell2` with a -Bellman equation. The principal's value function $s(w)$ satisfies +The recursive repeated problem chooses today's action, output, +consumption, and next-period promised utility. + +The surplus function +$s(w)$ satisfies $$ s(w) = \max_{\Pi^w}\; @@ -552,15 +595,20 @@ Constraints C5--C8 are the dynamic analogues of C1--C4. * Constraint C8 is incentive compatibility. -We iterate the Bellman operator to convergence. +For a finite horizon, the one-period surplus function is used to solve +the two-period problem, the two-period surplus function is used to solve +the three-period problem, and so on. + +For the infinite horizon, we +iterate on the Bellman operator until the surplus function is stable. At each iteration, a separate LP is solved for each grid point $w \in W$. ### The Two-Step Factored Algorithm -Solving the four-dimensional LP at each step of value function -iteration is computationally demanding. +Solving the full LP over $(a,q,c,w')$ at each grid point is +computationally demanding. Section VI of {cite}`Phelan_Townsend_91` proposes a factored algorithm that splits each period into two sub-steps, exploiting the @@ -570,11 +618,15 @@ $$ U(a, c) = 2\sqrt{1-a} + 2\sqrt{c}. $$ -**Step 1** (action and output, before consumption is decided). +*Step 1* (action and output, before consumption is assigned). Let $w^m$ be the **intermediate** promised utility after the output is observed but before consumption is allocated. +Thus $w^m$ includes +the utility from current consumption and the discounted next-period +promise, but not the current effort utility. + Solve $$ @@ -603,7 +655,7 @@ $$ \end{aligned} $$ -**Step 2** (consumption allocation). +*Step 2* (consumption allocation). Given $w^m$, solve $$ @@ -620,15 +672,17 @@ $$ \end{aligned} $$ -Step 2 is solved first (for all $w^m \in W^m$) to obtain $s^m(w^m)$. +Step 2 is solved first computationally, for all $w^m \in W^m$, to +obtain $s^m(w^m)$. -Then Step 1 is solved using $s^m$ as input. +Step 1 then uses this intermediate surplus function +as input. This factored algorithm significantly reduces computation time because each sub-LP is smaller than the original joint LP. The two-step formulation is an approximation when $W^m$ is -discretised: as the number of grid points $N_m$ grows, it converges +discretized: as the number of grid points $N_m$ grows, it converges to the exact solution. ### Functions @@ -673,16 +727,16 @@ def solve_repeated_problem_2(W=None, problem_type: str, "full information" or "unobserved-actions" The problem type, i.e. the full information problem or the unobserved-action problem. β: float, optional - The discouted factor. The value is 0.8 by default. + The discount factor. The value is 0.8 by default. Returns ------- s_W: 1-D array The optimal values of surplus for each w in w_vec. Pi_W_s1: 4-D array - The probility of (a, q, w_m) given w. + The probability of (a, q, w_m) given w. Pi_W_m_s2: 3-D array - The probility of (c, w_prime) given w_m. + The probability of (c, w_prime) given w_m. ''' n_A, n_Q, n_C, n_W = len(A), len(Q), len(C), len(W) @@ -714,7 +768,7 @@ def solve_repeated_problem_2(W=None, # Create the problem of step 2 problem_s2 = cp.Problem(obj_s2, C5_s2 + C7_s2) - # Solve the probelm of step 2 + # Solve the problem of step 2 s_W_m = np.zeros(n_W_m) Pi_W_m_s2 = np.zeros((n_W_m, n_C, n_W_prime)) for w_m, w_m_ind in zip(W_m, W_m_ind): @@ -784,7 +838,7 @@ def solve_repeated_problem_2(W=None, ``` ```{code-cell} ipython3 -# Define the function that solve the infinite-period or finite-period economy +# Define the function that solves the infinite-period or finite-period economy def solve_multi_period_economy_2(A=None, Q=None, C=None, @@ -816,7 +870,7 @@ def solve_multi_period_economy_2(A=None, The number of periods. If T is None, the algorithm solves the infinite-period economy. If T is some integer, the algorithm solves the T-period economy. By default, T is None. β: float, optional - The discouted factor in (0,1). The value is 0.8 by default. + The discount factor in (0,1). The value is 0.8 by default. N: int, optional The length of discretized parameter space W. N_m: int, optional @@ -833,13 +887,13 @@ def solve_multi_period_economy_2(A=None, s_W: 1-D array The optimal values of convergent surplus for each w in w_vec. Pi_W_s1: 4-D array - The probility of (a, q, w_m) given w. + The probability of (a, q, w_m) given w. Pi_W_m_s2: 3-D array - The probility of (c, w_prime) given w_m. + The probability of (c, w_prime) given w_m. ''' if β >= 1 or β <= 0: - raise ValueError('β should lie in [0,1]') + raise ValueError('β must lie in (0, 1)') # Define the function u[a,c] def u(a, c): @@ -943,9 +997,10 @@ Bellman iteration, which causes memory to accumulate when many iterations are needed -- a serious issue for $\beta$ close to 1. The function `solve_multi_period_economy_vfi` fixes this by building -the two sub-problems **once** with CVXPY `Parameter` objects for the +the two sub-problems *once* with CVXPY `Parameter` objects for the components that change between iterations ($\Phi^{s2}$ and $\Phi^{s1}$). -It also applies **Anderson acceleration** with a history of $m$ + +It also applies *Anderson acceleration* with a history of $m$ recent iterates to speed up convergence, and accepts a `max_iter` cap to prevent infinite loops. @@ -1144,7 +1199,7 @@ def solve_multi_period_economy_vfi(A=None, We use the same parameters as for the static economy, plus a discount factor $\beta = 0.8$ and grids of $N = N_m = 100$ points. -**Initial values.** +*Initial values.* We initialise the value function iteration with the one-period (static) solution, scaled to discounted-sum units. @@ -1169,7 +1224,7 @@ out_time = time() print("Time(s):", round(out_time - in_time, 3)) ``` -**Finite-period economy ($T = 3$).** +*Finite-period economy ($T = 3$).* ```{code-cell} ipython3 in_time = time() @@ -1197,14 +1252,11 @@ plt.title("Figure\n Optimized surplus function", y=-0.2) plt.show() ``` -**Infinite-period economy.** +*Infinite-period economy.* ```{code-cell} ipython3 -{ - "tags": [ - "hide-output" - ] -} +:tags: [hide-output] + in_time = time() s_W, Pi_W_s1, Pi_W_m_s2 = solve_multi_period_economy_2( A, Q, C, P, "unobserved-actions", @@ -1287,6 +1339,17 @@ plt.text(5.4, -4.0, "T = 1 Unobserved Action", size=12) plt.show() ``` +Figure 5 compares three surplus functions. + +The full-information frontier is highest. + +The infinite-horizon unobserved-action frontier is +below it because incentive constraints are added, but it lies above the +frontier obtained by repeating the one-period unobserved-action contract. + +The difference between the two unobserved-action curves is the gain from +history dependence. + #### Figure 6 ```{code-cell} ipython3 @@ -1313,6 +1376,13 @@ plt.text(14, 0.55, "T = 1 Unobserved Action (bottom)", size=10) plt.show() ``` +History dependence also raises effort relative to repeated one-period +contracts. + +Near the lower utility bound, incentive compatibility forces +low effort, but away from that bound continuation promises help provide +incentives without relying only on current consumption. + #### Figure 7 ```{code-cell} ipython3 @@ -1380,6 +1450,13 @@ plt.annotate(r"$\}$",fontsize=35, xy=(10.5, 0), xytext=(9.5, -0.3)) plt.show() ``` +Figure 7 shows how dynamic contracts smooth current consumption relative +to the static unobserved-action economy. + +Output still affects rewards, +but a large part of the reward and punishment is shifted into future +promised utility. + #### Figure 8 ```{code-cell} ipython3 @@ -1441,12 +1518,27 @@ plt.annotate(r"$\}$",fontsize=25, xy=(10.1, 10.1), xytext=(12.5, 8.5)) plt.show() ``` +Figure 8 displays the expected next-period promise conditional on +current $w$, recommended action $a$, and realized output $q$. + +High +output generally raises continuation utility and low output lowers it. + +At the endpoints of the feasible promise set, the transition stays on the +45-degree line because only the corresponding extreme plan can deliver +that endpoint. + For figures 9--12, {cite}`Phelan_Townsend_91` used $\beta = 0.95$. +They report that Figures 5--8 are easier to read at $\beta = 0.8$, while +the simulated individual paths and distributions in Figures 9--12 are +more informative at the higher discount factor. + We now use `solve_multi_period_economy_vfi` -- which builds the CVXPY problems once and applies Anderson acceleration -- to solve the infinite-horizon economy at $\beta = 0.95$ with a grid of $N = N_m = 50$ points. + Starting from the static solution rescaled to discounted-sum units, the iteration converges to tolerance $10^{-4}$. @@ -1571,6 +1663,13 @@ for i in range(4): W_new, C, s_W_new, 80, Pi_new, Ew_beta, seed=(12345 + i)) ``` +The simulations start from the grid point at which surplus is closest to +zero. + +This corresponds to the ex ante symmetric, or "fair", allocation +in the paper: it is the highest common promised utility that can be +assigned while keeping discounted social surplus nonnegative. + #### Figure 9 ```{code-cell} ipython3 @@ -1656,7 +1755,8 @@ plt.show() ### Economics -**Moral hazard and the cost of private information.** +*Moral hazard and the cost of private information.* + When the principal cannot observe the agent's effort, the optimal contract must balance two competing objectives: *insurance* (smoothing the agent's consumption across output realizations) and *incentives* (rewarding high @@ -1664,48 +1764,56 @@ output to make effort attractive). The unobserved-action surplus function in Figure 1 lies everywhere below the full-information frontier, and the gap between them measures the -deadweight loss that private information imposes. +surplus cost of unobserved effort. + +*Dynamic contracts and promised utility.* -**Dynamic contracts and promised utility.** The recursive formulation of {cite}`Spear_Srivastava_87` compresses all payoff-relevant history into a single scalar state: the discounted expected continuation utility $w$ that the principal has promised the agent. -By tracking $w$ rather than the full history of outputs, the problem -becomes tractable. +By tracking $w$ rather than the full history of outputs, the dynamic +contracting problem becomes tractable. Figures 7--8 show that under the optimal infinite-horizon contract the principal rewards high output by granting the agent a higher continuation -utility and punishes low output by lowering it -- a dynamic incentive -device that induces effort without relying on large contemporaneous -consumption differences alone. +utility and punishes low output by lowering it. + +Continuation promises +therefore substitute partly for large contemporaneous consumption +spreads. -**Immiseration.** -Figures 9--12 illustrate a celebrated result of the repeated moral-hazard -literature: over time the distribution of the agent's continuation utility -drifts toward the lower boundary of the feasible set. +*Diversity over time.* -Agents with persistently low output accumulate a history of downward -utility adjustments, eventually receiving near-zero consumption. +Figures 9--12 illustrate the paper's central computational message: +starting from a common initial promise, dynamic incentives generate +non-trivial individual histories and cross-sectional dispersion in +consumption and promised utility. -This *immiseration* result emerges from a trade-off between incentive -provision and long-run insurance. +With the finite grids used here, the endpoints of the promise set are +absorbing. + +The simulations should therefore be read as finite-grid +illustrations of how history dependence spreads the distribution over +time, not as a separate theorem about the limiting distribution. ### Technical Tricks -**Lotteries and convexification.** +*Lotteries and convexification.* + Incentive constraints can render the set of feasible contracts non-convex, making standard optimization techniques unreliable. -{cite}`Phelan_Townsend_91` circumvent this by allowing the principal to +{cite}`Phelan_Townsend_91` circumvented this by allowing the planner to choose a joint *lottery* $\Pi(a, q, c, w')$ over actions, outputs, consumptions, and continuation values. Because any mixture of feasible lotteries is itself feasible, the constraint set becomes convex, and global optima are well-defined. -**Linear programming.** +*Linear programming.* + With finite grids, the convexified Bellman equation is a linear program: the objective $(q - c + \beta v(w'))$ and every constraint are linear in $\Pi$. @@ -1715,16 +1823,19 @@ function iteration reduces to solving one LP per grid point per iteration -- a task handled efficiently by modern LP solvers such as HiGHS. -**Dynamic programming.** +*Dynamic programming.* + The promised-utility state variable $w$ makes the problem recursive. + At each iteration the Bellman operator maps a surplus function $v$ to an updated surplus function $Tv$; repeated application converges to the infinite-horizon fixed point. -Initializing from the scaled static solution substantially reduces the -number of iterations required. +The implementation initializes the iteration from the scaled static +solution, which is a useful numerical starting point. + +*Two-step factored algorithm.* -**Two-step factored algorithm.** The additive separability $U(a,c) = 2\sqrt{1-a} + 2\sqrt{c}$ allows the four-dimensional LP to be split into two smaller sub-problems. @@ -1736,25 +1847,22 @@ Because each sub-LP has far fewer decision variables than the full joint LP, computation is substantially faster and the approach scales to finer grids. -**Dynamic programming squared.** -This lecture is an instance of what Lars Ljungqvist and Thomas Sargent call -**dynamic programming squared** in their textbook +*Dynamic programming squared.* + +This lecture is closely related to what Lars Ljungqvist and Thomas +Sargent call *dynamic programming squared* in {cite}`Ljungqvist2012`. -The phrase refers to problems in which a value or value function from -one Bellman equation appears as a key argument inside a second Bellman -equation. +The phrase refers to recursive problems in which one continuation object +is carried as a state variable inside another recursive problem. Here the surplus function $s(w)$ -- the solution to the principal's outer dynamic program -- has the agent's continuation utility $w$ as its -state variable, and $w$ itself is determined by an *inner* promise-keeping -constraint that enforces the agent's Bellman equation. - -Two Bellman equations are therefore nested: the principal's value -function $v(w)$ and the agent's promised-utility evolution -$w' = \tilde w(w, q)$ are solved simultaneously. +state variable, while feasible movements in $w$ are governed by +promise-keeping and incentive constraints. The same architecture reappears throughout this lecture series. + In {doc}`Stackelberg plans ` the Stackelberg leader's value function takes the followers' competitive-equilibrium value function as an argument. @@ -1773,8 +1881,7 @@ as an argument, with $\theta$ governed by its own Bellman equation. In {doc}`Unemployment Insurance ` the planner's contract-design problem embeds the worker's continuation utility as the state variable in an outer surplus-maximization program, -producing the same nested structure that drives immiseration in -the present lecture. +producing a closely related nested recursive structure. In all of these settings, the inner dynamic program defines a state variable -- a promised utility, a marginal value, or a @@ -1824,6 +1931,7 @@ Agency costs are highest near intermediate levels of promised utility because at those values the principal most values inducing high effort (output is valuable) while the agent still requires meaningful consumption-state variation to be incentivized. + At low $w$ the agent is near subsistence and effort is low anyway; at high $w$ the agent is nearly fully insured and the marginal incentive cost of each additional unit of effort is small. @@ -1895,9 +2003,11 @@ plt.show() With $P_{flat}$ output carries less statistical information about effort: the likelihood ratio $P(q \mid \hat{a}) / P(q \mid a)$ is closer to 1 for all deviations $\hat{a} \neq a$. + The incentive-compatibility constraint {eq}`eq:eq2prime` therefore becomes harder to satisfy: large consumption rewards for high output must be offered to deter deviations, crowding out insurance. + As a result the principal extracts less surplus and induces less effort than under the baseline $P$ -- the surplus function shifts down and expected effort falls. diff --git a/lectures/_static/quant-econ.bib b/lectures/_static/quant-econ.bib index 6707f3ec..043a866b 100644 --- a/lectures/_static/quant-econ.bib +++ b/lectures/_static/quant-econ.bib @@ -1331,7 +1331,7 @@ @article{Spear_Srivastava_87 pages = {599-617}, month = {}, keywords = {}, - doi = {}, + doi = {10.2307/2297484}, abstract = {In this paper, we analyze optimal contracts in an infinitely repeated agency model in which both the principal and agent discount the future. We show that there is a stationary representation of the optimal contract when the agent's conditional discounted expected utility is used as a state variable. This representation reduces the multi-period problem to a static variational problem which can be analyzed using standard variational techniques. This reduction is used to obtain several properties of the contract.}, url = {https://ideas.repec.org/a/oup/restud/v54y1987i4p599-617..html} } @@ -1346,7 +1346,7 @@ @article{Phelan_Townsend_91 pages = {853-881}, month = {}, keywords = {}, - doi = {}, + doi = {10.2307/2297941}, abstract = {This paper presents a detailed theoretical derivation and justification for methods used to compute solutions to a multi-period (including infinite-period), continuum-agent, unobservedeffort economy. Actual solutions are displayed illustrating cross-sectional variability in consumption and labour effort in the population at a point in time and variability for a typical individual over time. The optimal tradeoff between insurance and incentives is explored and the issue of excess variability is addressed by consideration of the analogue full-information economy and various restricted-contracting regimes.}, url = {https://ideas.repec.org/a/oup/restud/v58y1991i5p853-881..html} } @@ -1922,11 +1922,12 @@ @book{LasotaMackey1994 } @book{Ljungqvist2012, - author = {Ljungqvist, L and Sargent, T J}, - publisher = {MIT Press}, + author = {Lars Ljungqvist and Thomas J. Sargent}, + publisher = {The MIT Press}, title = {Recursive Macroeconomic Theory}, edition = {4}, - year = {2018} + year = {2018}, + isbn = {9780262038669} } @article{Lucas1978, From 0aacdb8fb2f2407cc1edf3017a14d7e6b53d55c1 Mon Sep 17 00:00:00 2001 From: HumphreyYang Date: Wed, 20 May 2026 10:05:00 +0800 Subject: [PATCH 03/25] updates --- lectures/_toc.yml | 2 +- lectures/{PT_new.md => repeat_mh.md} | 13 ++++++------- 2 files changed, 7 insertions(+), 8 deletions(-) rename lectures/{PT_new.md => repeat_mh.md} (99%) diff --git a/lectures/_toc.yml b/lectures/_toc.yml index 1234e144..d0ede1f6 100644 --- a/lectures/_toc.yml +++ b/lectures/_toc.yml @@ -71,7 +71,7 @@ parts: - caption: Dynamic Programming Squared numbered: true chapters: - - file: PT_new + - file: repeat_mh - file: un_insure - file: dyn_stack - file: calvo_machine_learn diff --git a/lectures/PT_new.md b/lectures/repeat_mh.md similarity index 99% rename from lectures/PT_new.md rename to lectures/repeat_mh.md index f0793b49..820afdbf 100644 --- a/lectures/PT_new.md +++ b/lectures/repeat_mh.md @@ -3,19 +3,18 @@ jupytext: text_representation: extension: .md format_name: myst - format_version: 0.13 - jupytext_version: 1.14.4 kernelspec: - display_name: Python (quantecon) + display_name: Python 3 language: python - name: quantecon + name: python3 --- + # Repeated Moral Hazard ## Overview This lecture computes the information-constrained optima studied by -{cite}`Phelan_Townsend_91`. +{cite:t}`Phelan_Townsend_91`. Their paper studies a continuum-agent economy with unobserved effort. @@ -23,7 +22,7 @@ The planner chooses lotteries over individual histories, subject to promise-keeping and incentive-compatibility constraints, and maximizes discounted social surplus. -The key recursive idea comes from {cite}`Spear_Srivastava_87`: an +The key recursive idea comes from {cite:t}`Spear_Srivastava_87`: an agent's promised continuation utility is a sufficient state variable. Phelan and Townsend combine that idea with lotteries, finite grids, and @@ -42,7 +41,7 @@ We proceed as follows. Figures 5--12 of {cite}`Phelan_Townsend_91`. -## Spear and Srivastava (1987) +## Promised-utility Recursion {cite}`Spear_Srivastava_87` showed how to write an infinitely repeated, discounted principal-agent problem recursively. From 65c58e282c356d6f1d197be5ff205e9a423ed982 Mon Sep 17 00:00:00 2001 From: HumphreyYang Date: Thu, 21 May 2026 16:56:34 +1000 Subject: [PATCH 04/25] updates --- lectures/chang_ramsey.md | 41 +++++++++++++++++++++------------------- lectures/repeat_mh.md | 30 +++++++++++++++++------------ 2 files changed, 40 insertions(+), 31 deletions(-) diff --git a/lectures/chang_ramsey.md b/lectures/chang_ramsey.md index 97571036..2f0d62b0 100644 --- a/lectures/chang_ramsey.md +++ b/lectures/chang_ramsey.md @@ -3,10 +3,12 @@ jupytext: text_representation: extension: .md format_name: myst + format_version: 0.13 + jupytext_version: 1.19.1 kernelspec: - display_name: Python 3 - language: python name: python3 + display_name: Python 3 (ipykernel) + language: python --- (chang_ramsey)= @@ -22,10 +24,9 @@ kernelspec: In addition to what's in Anaconda, this lecture will need the following libraries: -```{code-cell} ipython ---- -tags: [hide-output] ---- +```{code-cell} ipython3 +:tags: [hide-output] + !pip install polytope cvxopt ``` @@ -68,7 +69,7 @@ and other lectures. We'll start with some standard imports: -```{code-cell} ipython +```{code-cell} ipython3 import numpy as np import polytope import matplotlib.pyplot as plt @@ -918,16 +919,18 @@ $\beta = 0.8$. (Here we have set the number of subgradients to 10 in order to speed up the code for now - we can increase accuracy by increasing the number of subgradients) -```{code-cell} python3 +```{code-cell} ipython3 :load: _static/lecture_specific/chang_credible/changecon.py + + ``` -```{code-cell} python3 +```{code-cell} ipython3 ch1 = ChangModel(β=0.3, mbar=30, h_min=0.9, h_max=2, n_h=8, n_m=35, N_g=10) ch1.solve_sustainable() ``` -```{code-cell} python3 +```{code-cell} ipython3 def plot_competitive(ChangModel): """ Method that only plots competitive equilibrium set @@ -960,13 +963,13 @@ def plot_competitive(ChangModel): plot_competitive(ch1) ``` -```{code-cell} python3 +```{code-cell} ipython3 ch2 = ChangModel(β=0.8, mbar=30, h_min=0.9, h_max=1/0.8, n_h=8, n_m=35, N_g=10) ch2.solve_sustainable() ``` -```{code-cell} python3 +```{code-cell} ipython3 plot_competitive(ch2) ``` @@ -1023,14 +1026,14 @@ From the figures earlier in this lecture, we know that when $\beta = 0.3$, $\Omega = [0.0088,0.0499]$, and when $\beta = 0.8$, $\Omega = [0.0395,0.2193]$ -```{code-cell} python3 +```{code-cell} ipython3 ch1 = ChangModel(β=0.3, mbar=30, h_min=0.99, h_max=1/0.3, n_h=8, n_m=35, N_g=50) ch2 = ChangModel(β=0.8, mbar=30, h_min=0.1, h_max=1/0.8, n_h=20, n_m=50, N_g=50) ``` -```{code-cell} python3 +```{code-cell} ipython3 ch1.solve_bellman(θ_min=0.01, θ_max=0.0499, order=30, tol=1e-6) ch2.solve_bellman(θ_min=0.045, θ_max=0.15, order=30, tol=1e-6) ``` @@ -1040,14 +1043,14 @@ good. We do this by calculating the residuals between iterates on the value function on a fine grid: -```{code-cell} python3 +```{code-cell} ipython3 max(abs(ch1.resid_grid)), max(abs(ch2.resid_grid)) ``` The value functions plotted below trace out the right edges of the sets of equilibrium values plotted above -```{code-cell} python3 +```{code-cell} ipython3 fig, axes = plt.subplots(1, 2, figsize=(12, 4)) for ax, model in zip(axes, (ch1, ch2)): @@ -1062,7 +1065,7 @@ plt.show() The next figure plots the optimal policy functions; values of $\theta',m,x,h$ for each value of the state $\theta$: -```{code-cell} python3 +```{code-cell} ipython3 for model in (ch1, ch2): fig, axes = plt.subplots(2, 2, figsize=(12, 6), sharex=True) @@ -1094,7 +1097,7 @@ With the first set of parameter values, this function does not intersect the 45-degree line until $\bar \theta$, whereas in the second set of parameter values, it intersects in the interior. -```{code-cell} python3 +```{code-cell} ipython3 fig, axes = plt.subplots(1, 2, figsize=(12, 4)) for ax, model in zip(axes, (ch1, ch2)): @@ -1115,7 +1118,7 @@ equilibrium. These are shown below for both sets of parameters -```{code-cell} python3 +```{code-cell} ipython3 for model in (ch1, ch2): fig, axes = plt.subplots(2, 2, figsize=(12, 6)) diff --git a/lectures/repeat_mh.md b/lectures/repeat_mh.md index 820afdbf..c096cb0a 100644 --- a/lectures/repeat_mh.md +++ b/lectures/repeat_mh.md @@ -3,10 +3,12 @@ jupytext: text_representation: extension: .md format_name: myst + format_version: 0.13 + jupytext_version: 1.19.1 kernelspec: - display_name: Python 3 - language: python name: python3 + display_name: Python 3 (ipykernel) + language: python --- # Repeated Moral Hazard @@ -32,18 +34,18 @@ unobserved-action, and repeated unobserved-action allocations. We proceed as follows. * We review the promised-utility recursion of - {cite}`Spear_Srivastava_87`. + {cite:t}`Spear_Srivastava_87`. * We formulate the Phelan-Townsend lottery problem and its finite-grid linear-programming approximation. * We solve the *static* version of the economy and - replicate Figures 1--4 of {cite}`Phelan_Townsend_91`. + replicate Figures 1--4 of {cite:t}`Phelan_Townsend_91`. * We solve the *repeated* economy and replicate - Figures 5--12 of {cite}`Phelan_Townsend_91`. + Figures 5--12 of {cite:t}`Phelan_Townsend_91`. ## Promised-utility Recursion -{cite}`Spear_Srivastava_87` showed how to write an infinitely repeated, +{cite:t}`Spear_Srivastava_87` showed how to write an infinitely repeated, discounted principal-agent problem recursively. * A **principal** owns a technology that produces output $q_t$ at @@ -68,7 +70,9 @@ surplus $E_0 \sum_{t=0}^\infty \beta^t \{q_t - c_t\}$. Let $w$ denote the discounted expected continuation utility that the principal has promised to the agent at the start of a period. -The promised utility $w$ summarizes payoff-relevant history. Given +The promised utility $w$ summarizes payoff-relevant history. + +Given $w$, the principal chooses a recommended action $a(w)$, an output-contingent consumption rule $c(w,q)$, and an output-contingent next-period promise $\tilde w(w,q)$ subject to @@ -208,9 +212,14 @@ Their Theorem 4 gives the contraction result that justifies this iteration for the infinite-horizon problem. - ## Implementation +In addition to what's in Anaconda, this lecture will need the following libraries: + +```{code-cell} ipython3 +!pip install cvxpy +``` + We import some Python packages. ```{code-cell} ipython3 @@ -222,7 +231,6 @@ import matplotlib.pyplot as plt from warnings import filterwarnings ``` - ## The Static Economy This section replicates Sections II and III of @@ -430,7 +438,7 @@ simplex methods. We use HiGHS through CVXPY. At degenerate utility grid points, a different LP solver can select a different optimal lottery, so some consumption schedules can differ slightly even when the surplus function is unchanged. -``` +``` ```{code-cell} ipython3 W_static = np.linspace(1, 5, 100) @@ -531,7 +539,6 @@ plt.title("Figure 4\n Full Information Consumption", y=-0.2) plt.show() ``` - ## The Repeated Economy We now move from the one-period economy to the finite- and @@ -1278,7 +1285,6 @@ plt.text(15, 6.5, "Infinity Unobserved Action", size=12) plt.show() ``` - ### Recovering $\Pi^w(a, q, c, w')$ The two-step algorithm returns From 57dee6723c4653cd39781602425e2f7333b8198a Mon Sep 17 00:00:00 2001 From: HumphreyYang Date: Thu, 21 May 2026 17:01:42 +1000 Subject: [PATCH 05/25] updates --- lectures/_toc.yml | 4 +- lectures/atkeson_1991.bib | 100 ++++ lectures/atkeson_1991.md | 888 ++++++++++++++++++++++++++++++++ lectures/tsyrennikov_2013.bib | 77 +++ lectures/tsyrennikov_2013.md | 920 ++++++++++++++++++++++++++++++++++ 5 files changed, 1988 insertions(+), 1 deletion(-) create mode 100644 lectures/atkeson_1991.bib create mode 100644 lectures/atkeson_1991.md create mode 100644 lectures/tsyrennikov_2013.bib create mode 100644 lectures/tsyrennikov_2013.md diff --git a/lectures/_toc.yml b/lectures/_toc.yml index d0ede1f6..8b47631b 100644 --- a/lectures/_toc.yml +++ b/lectures/_toc.yml @@ -71,7 +71,9 @@ parts: - caption: Dynamic Programming Squared numbered: true chapters: - - file: repeat_mh + - file: repeat_mh + - file: atkeson_1991 + - file: tsyrennikov_2013 - file: un_insure - file: dyn_stack - file: calvo_machine_learn diff --git a/lectures/atkeson_1991.bib b/lectures/atkeson_1991.bib new file mode 100644 index 00000000..26d1ad6e --- /dev/null +++ b/lectures/atkeson_1991.bib @@ -0,0 +1,100 @@ +@article{Atkeson1991, + author = {Andrew Atkeson}, + title = {International Lending with Moral Hazard and Risk of Repudiation}, + journal = {Econometrica}, + year = {1991}, + volume = {59}, + number = {4}, + pages = {1069--1089} +} + +@article{APS1986, + author = {Dilip Abreu and David Pearce and Ennio Stacchetti}, + title = {Optimal Cartel Equilibria with Imperfect Monitoring}, + journal = {Journal of Economic Theory}, + year = {1986}, + volume = {39}, + pages = {251--269} +} + +@article{BulowRogoff1989a, + author = {Jeremy Bulow and Kenneth Rogoff}, + title = {A Constant Recontracting Model of Sovereign Debt}, + journal = {Journal of Political Economy}, + year = {1989}, + volume = {97}, + number = {1}, + pages = {155--178} +} + +@article{BulowRogoff1989b, + author = {Jeremy Bulow and Kenneth Rogoff}, + title = {Sovereign Debt: Is to Forgive to Forget?}, + journal = {American Economic Review}, + year = {1989}, + volume = {79}, + number = {1}, + pages = {43--50} +} + +@article{GrossmanVanHuyck1988, + author = {Herschel I. Grossman and John B. Van Huyck}, + title = {Sovereign Debt as a Contingent Claim: Excusable Default, + Repudiation, and Reputation}, + journal = {American Economic Review}, + year = {1988}, + volume = {78}, + number = {5}, + pages = {1088--1097} +} + +@article{GrossmanHart1983, + author = {Sanford J. Grossman and Oliver D. Hart}, + title = {An Analysis of the Principal-Agent Problem}, + journal = {Econometrica}, + year = {1983}, + volume = {51}, + number = {1}, + pages = {7--45} +} + +@article{Rogerson1985, + author = {William P. Rogerson}, + title = {The First-Order Approach to Principal-Agent Problems}, + journal = {Econometrica}, + year = {1985}, + volume = {53}, + number = {6}, + pages = {1357--1367} +} + +@article{FudenbergHolmstromMilgrom1990, + author = {Drew Fudenberg and Bengt Holmstrom and Paul Milgrom}, + title = {Short-Term Contracts and Long-Term Agency Relationships}, + journal = {Journal of Economic Theory}, + year = {1990}, + volume = {51}, + number = {1}, + pages = {1--31} +} + +@article{EichengrehenPortes1986, + author = {Barry Eichengreen and Richard Portes}, + title = {Debt and Default in the 1930s: Causes and Consequences}, + journal = {European Economic Review}, + year = {1986}, + volume = {30}, + number = {3}, + pages = {599--640} +} + +@incollection{LindertMorton1989, + author = {Peter H. Lindert and Peter J. Morton}, + title = {How Sovereign Debt Has Worked}, + booktitle = {Developing Country Debt and Economic Performance, + Volume 1: The International Financial System}, + editor = {Jeffrey D. Sachs}, + publisher = {University of Chicago Press}, + year = {1989}, + pages = {39--106} +} diff --git a/lectures/atkeson_1991.md b/lectures/atkeson_1991.md new file mode 100644 index 00000000..35960023 --- /dev/null +++ b/lectures/atkeson_1991.md @@ -0,0 +1,888 @@ +--- +jupytext: + text_representation: + extension: .md + format_name: myst +kernelspec: + display_name: Python 3 + language: python + name: python3 +--- + +(atkeson_1991)= +```{raw} html + +``` + +# International Lending with Moral Hazard and Risk of Repudiation + +## Overview + +This lecture studies {cite}`Atkeson1991`, which examines the **constrained +optimal pattern of capital flows** between an international lender and a +sovereign borrower subject to two frictions: + +1. **Moral hazard** — lenders cannot observe whether the borrower invests or + simply consumes loan proceeds. +2. **Risk of repudiation** — as a sovereign, the borrower can unilaterally + renounce its debt at any time. + +The central result is striking: a debt-crisis pattern — in which a borrowing +country must **export capital** after suffering an adverse output shock — arises +as a necessary feature of the *constrained optimal* contract, not as a simple +market failure. Capital outflows after bad output realizations are the +mechanism that incentivizes investment. + +The model extends recursive techniques from {cite}`APS1986`, {cite}`APS1990`, +and {cite}`Spear_Srivastava_87` to an environment with a **physical state +variable** that changes across periods. + +```{note} +Atkeson (1991) uses $\delta$ for the discount factor. We follow the +QuantEcon convention and write $\beta$ throughout. +``` + +## The Environment + +### Technology + +Time is discrete, $t = 0, 1, 2, \ldots$. In each period the borrower chooses +investment $I_t \geq 0$. Given investment $I_t$, next period's output +$Y_{t+1}$ is drawn from the conditional distribution + +$$ +g(Y';\,I) \;=\; \lambda(I)\,g_0(Y') + \bigl[1 - \lambda(I)\bigr]\,g_1(Y'), +$$ + +where $Y' \in \mathcal{Y} = \{Y_1, \ldots, Y_N\}$ with $Y_N \geq \cdots \geq +Y_1 > 0$. The weight $\lambda : [0,I_{\max}] \to [0,1]$ is strictly +increasing, so higher investment shifts the distribution toward higher outputs. + +The ratio $g_0(Y_i')/g_1(Y_i')$ is assumed to be **monotone increasing in +$i$** (monotone likelihood ratio property). This means low output is a +relatively strong signal that the borrower invested little. + +### Agents and Preferences + +**The borrower** is an infinitely-lived, risk-averse agent with normalised +discounted utility + +$$ +U^B(\sigma) \;=\; (1 - \beta)\,\mathbb{E}_0^{\sigma} + \sum_{t=0}^{\infty} \beta^t \, u(c_t), +$$ + +where $u$ is strictly concave with $u'(0) = +\infty$. + +**Lenders** are a sequence of short-lived, risk-neutral agents, one born each +period. The lender born at $t$ extends loan $b_t$ when young and collects +state-contingent repayment $d_{t+1}(Y_{t+1})$ when old. The lender's +participation (zero-profit) constraint is + +$$ +-b_t + \beta \sum_{Y'} d_{t+1}(Y')\,g(Y';\,I_t) \;\geq\; 0. +$$ + +### State Variable and Feasibility + +Define + +$$ +Q_t \;\equiv\; Y_t - d_t(Y_t) +$$ + +as **output net of repayment** — the resources available to the borrower +after settling the old lender's claim. An allocation +$\sigma = \{c_t(Q^t),\,I_t(Q^t),\,b_t(Q^t),\,d_{t+1}(Y_{t+1};Q^t)\}$ +is **feasible** if for all $t$ and histories: + +$$ +c_t + I_t - b_t \;\leq\; Q_t, \quad c_t,\, I_t \geq 0, + \quad b_t,\; -d_{t+1}(Y') \leq M, +$$ + +where $M$ is the lender's endowment per period. + +### Autarky Value + +The value the borrower can attain without credit access satisfies + +$$ +U^B_{\text{aut}}(Z) \;=\; \max_{I \in [0, Z]} + \Bigl[(1-\beta)\,u(Z - I) + \beta \sum_{Y'} U^B_{\text{aut}}(Y')\,g(Y';\,I)\Bigr]. +$$ + +## Two Impediments to Contracting + +### Moral Hazard + +Because lenders can observe only $Y_t$, not the borrower's investment $I_t$, +any feasible allocation must be **incentive compatible**: the borrower prefers +the prescribed $(c_t, I_t)$ to any alternative consumption-investment plan +given the loan and repayment schedule. + +### Risk of Repudiation + +If the borrower repudiates its debt after $Y_{t+1}$ is realized, future +lenders refuse credit and the borrower is confined to autarky. An allocation +is **immune from repudiation** if, for every output realization $Y_{t+1}$, + +$$ +U^B\bigl(\sigma\,\big|\,_{Q^t;\,Y_{t+1}}\bigr) + \;\geq\; U^B_{\text{aut}}(Y_{t+1}). +$$ + +The continuation value of the contract — evaluated at the post-repayment state +$Q_{t+1} = Y_{t+1} - d_{t+1}(Y_{t+1})$ — must weakly exceed what the +borrower would obtain by repudiating and retaining all of $Y_{t+1}$. + +## The Constrained Pareto Problem + +An allocation is **constrained Pareto optimal** if it maximises the borrower's +payoff $U^B(\sigma)$ subject to: + +1. Feasibility +2. Individual rationality (lenders earn at least zero) +3. Immunity from repudiation +4. Incentive compatibility + +## Recursive Formulation + +### Self-Generation and Factorization + +Let $V(Q)$ be the set of payoffs the borrower can achieve from allocations +satisfying constraints (1)–(4) when the state is $Q$. Atkeson adapts the +**self-generation** and **factorization** results of {cite}`APS1990` to this +setting with a physical state variable. Define a pair $(A, U)$ of current +controls and a continuation value function to be *admissible with respect to* +$W$ at $Q$ if it satisfies one-period versions of constraints (1)–(4) and +$U(Q') \in W(Q')$ for all $Q'$. Let $B(W)(Q)$ be the set of payoffs +generated by admissible pairs. Then: + +- **Proposition 1 (Self-generation):** If $W$ is self-generating + ($W(Q) \subseteq B(W)(Q)$ for all $Q$), then $B(W)(Q) \subseteq V(Q)$. +- **Proposition 2 (Factorization):** $V(Q) \subseteq B(V)(Q)$ for all $Q$. + +These propositions imply $V = B(V)$, characterising the utility possibility +correspondence as the fixed point of $B$. + +### Program P* + +**Proposition 5** ({cite}`Atkeson1991`): The value function $\bar V(Q)$ of the +constrained Pareto optimal contract satisfies the functional equation + +$$ +\bar{V}(Q) \;=\; \max_{c,\,I,\,b,\,d'(\cdot)} + \;(1-\beta)\,u(c) + \beta \sum_{Y'} \bar{V}\!\bigl(Y' - d'(Y')\bigr)\,g(Y';\,I) +$$ + +subject to feasibility, lender participation, no-repudiation, and incentive +compatibility. Moreover, the optimal *continuation* value function equals +$\bar{V}$ itself. + +This mirrors Bellman's principle: the **continuation of the optimal contract +is itself optimal** at the updated state. + +### Capital Outflows After Low Output + +The first-order condition of the Lagrangian for Program P* with respect to the +continuation value $U_d(Y_i')$ reveals that the no-repudiation Lagrange +multiplier $\mu_3(Y_i') > 0$ whenever + +$$ +1 + \mu_4 \,\frac{g_1(Y_i';\,I)}{g(Y_i';\,I)} < 0. +$$ + +The ratio $g_1(Y_i';I)/g(Y_i';I)$ measures the likelihood that output $Y_i'$ +signals *low* investment. By the monotone likelihood ratio property, this +ratio is largest for the **lowest output states**. When $\mu_3(Y_i') > 0$, +the no-repudiation constraint binds: $\bar{V}(Y_i' - d'(Y_i')) = +U^B_{\text{aut}}(Y_i')$. Repayment $d'(Y_i')$ is then at its maximum and the +new loan available at the continuation state is limited, producing a net +**capital outflow**: + +$$ +\underbrace{d'(Y_i')}_{\text{repayment to old lender}} + \;>\; \underbrace{b'\!\bigl(Q'\bigr)}_{\text{new loan from young lender}}. +$$ + +## Computation + +We illustrate these results with a binary-investment, two-output version of +the model. + +### Setup + +```{code-cell} ipython3 +import numpy as np +from scipy.interpolate import interp1d +from scipy.optimize import minimize +import matplotlib.pyplot as plt + +plt.rcParams.update({'font.size': 12, 'figure.dpi': 100}) + +# ── Discount factor ────────────────────────────────────────────────────────── +β = 0.9 + +# ── Binary investment (high I_h or zero) ───────────────────────────────────── +I_h = 0.2 # resource cost of high investment + +# ── Output states ───────────────────────────────────────────────────────────── +Y_L, Y_H = 0.5, 1.0 +Y = np.array([Y_L, Y_H]) + +# ── Output distributions ────────────────────────────────────────────────────── +# g_h[j] = Pr(Y[j] | invest I_h), g_l[j] = Pr(Y[j] | invest 0) +g_h = np.array([0.25, 0.75]) # high investment → likely high output +g_l = np.array([0.75, 0.25]) # low investment → likely low output + +# Monotone likelihood ratio: g_l[0]/g_h[0] = 3 > 1 > g_l[1]/g_h[1] = 1/3 +# → Y_L strongly signals low investment; Y_H signals high investment +Δg = g_h - g_l # [−0.5, 0.5] + +# ── Lender endowment (large: b, −d ≤ M) ────────────────────────────────────── +M = 10.0 + +# ── State grid: Q = Y − d (resources after repaying old debt) ───────────────── +N_Q = 200 +Q_MIN = 0.02 +Q_MAX = 1.8 +Q_grid = np.linspace(Q_MIN, Q_MAX, N_Q) + +# ── Utility ──────────────────────────────────────────────────────────────────── +def u(c): + return np.log(np.maximum(c, 1e-12)) + +print(f"Likelihood ratios g_l / g_h : {g_l / g_h}") +print(f"Y_L signals low investment with ratio {g_l[0]/g_h[0]:.1f}x") +``` + +### Autarky Value Function + +In autarky the borrower has no access to credit. Starting each period with +resources $Q$, the borrower solves + +$$ +U_{\text{aut}}(Q) = + \max_{I \in \{0,\,I_h\}} + \Bigl[(1-\beta)\,u(Q - I) + \beta + \bigl[g(I)_L\,U_{\text{aut}}(Y_L) + g(I)_H\,U_{\text{aut}}(Y_H)\bigr]\Bigr]. +$$ + +Note that the continuation values depend only on $Y_L$ and $Y_H$, not on the +current $Q$, because next period's state is simply the realised output. + +```{code-cell} ipython3 +def autarky_vfi(tol=1e-8, max_iter=3000): + """Value function iteration for the autarky problem (binary investment).""" + V = np.zeros(N_Q) + + for it in range(max_iter): + Vf = interp1d(Q_grid, V, fill_value='extrapolate', bounds_error=False) + EV_h = float(g_h @ Vf(Y)) # E[V(Y') | invest I_h] + EV_l = float(g_l @ Vf(Y)) # E[V(Y') | invest 0 ] + + V_new = np.empty(N_Q) + for k, Q in enumerate(Q_grid): + val_h = (1-β)*u(Q - I_h) + β*EV_h if Q > I_h + 1e-10 else -np.inf + val_l = (1-β)*u(Q) + β*EV_l + V_new[k] = max(val_h, val_l) + + diff = np.max(np.abs(V_new - V)) + V = V_new + if diff < tol: + print(f"Autarky VFI converged in {it+1} iterations (diff={diff:.2e})") + break + + return V + +V_aut = autarky_vfi() +``` + +### Constrained Pareto Optimal Contract + +We solve Program P* iteratively. At each state $Q$, the planner chooses +continuation states $(Q'_L, Q'_H)$ — equivalently, state-contingent +repayments $d_j = Y_j - Q'_j$ — to maximise the borrower's payoff. + +Taking lender participation **binding** (Proposition 5 implies this is without +loss of generality), the loan is determined by + +$$ +b^* \;=\; \beta\bigl[g_{h,L}(Y_L - Q'_L) + g_{h,H}(Y_H - Q'_H)\bigr], +$$ + +and current consumption is $c^* = Q + b^* - I_h$. The optimisation reduces +to a two-dimensional problem in $(Q'_L, Q'_H)$: + +$$ +\max_{Q'_L,\,Q'_H} + (1-\beta)\,u(c^*) + \beta\bigl[V(Q'_L)\,g_{h,L} + V(Q'_H)\,g_{h,H}\bigr] +$$ + +subject to: + +- **(NR)** $V(Q'_j) \geq U_{\text{aut}}(Y_j)$, i.e. $Q'_j \geq Q^*_j$ +- **(IC)** $\beta\,\Delta g \cdot V(Q') \geq (1-\beta)\,[u(c^*+I_h) - u(c^*)]$ +- **(F)** $c^* \geq 0$ + +where $\Delta g_j = g_{h,j} - g_{l,j}$ and $Q^*_j = V^{-1}(U_{\text{aut}}(Y_j))$. + +```{code-cell} ipython3 +def find_Qmin(V_arr, v_thresh): + """Return min Q on grid with V(Q) >= v_thresh (no-repudiation lower bound).""" + if v_thresh <= V_arr[0]: + return float(Q_MIN) + if v_thresh >= V_arr[-1]: + return float(Q_MAX) + # Use searchsorted on a monotone version of V + V_mono = np.maximum.accumulate(V_arr) # enforce monotone for inversion + idx = np.searchsorted(V_mono, v_thresh) + idx = np.clip(idx, 1, N_Q - 1) + denom = V_mono[idx] - V_mono[idx-1] + if abs(denom) < 1e-14: + return float(Q_grid[idx-1]) + t = (v_thresh - V_mono[idx-1]) / denom + return float(Q_grid[idx-1] + t * (Q_grid[idx] - Q_grid[idx-1])) + + +def pareto_bellman(V, V_aut_arr): + """ + One application of the constrained Pareto Bellman operator. + Returns updated V, optimal loan policy pol_b, and continuation + states pol_Qp[:,0] (after Y_L) and pol_Qp[:,1] (after Y_H). + """ + Vf = interp1d(Q_grid, V, fill_value='extrapolate', + bounds_error=False) + Vaut_f = interp1d(Q_grid, V_aut_arr, fill_value='extrapolate', + bounds_error=False) + + # No-repudiation lower bounds on Q'_j + Vaut_Y = np.array([float(Vaut_f(yj)) for yj in Y]) + Qp_min = np.array([find_Qmin(V, v) for v in Vaut_Y]) + Qp_min = np.clip(Qp_min, Q_MIN, Q_MAX - 1e-4) + + V_new = np.empty(N_Q) + pol_b = np.empty(N_Q) + pol_Qp = np.empty((N_Q, 2)) + + for k, Q in enumerate(Q_grid): + + def bc(Qp): + """Compute loan b* and consumption c* from continuation states.""" + b = β * (g_h[0]*(Y_L - Qp[0]) + g_h[1]*(Y_H - Qp[1])) + c = Q + b - I_h + return b, c + + def neg_obj(Qp): + b, c = bc(Qp) + if c < 1e-10: + return 1e10 + VQp = np.array([float(Vf(Qp[0])), float(Vf(Qp[1]))]) + return -((1-β)*u(c) + β*(VQp @ g_h)) + + def ic_slack(Qp): + """IC: β Δg·V(Q') − (1−β)[u(c+I_h)−u(c)] ≥ 0.""" + b, c = bc(Qp) + if c < 1e-10: + return -1e10 + VQp = np.array([float(Vf(Qp[0])), float(Vf(Qp[1]))]) + return β*(Δg @ VQp) - (1-β)*(u(c + I_h) - u(c)) + + def feas_slack(Qp): + """Feasibility: c* ≥ 0.""" + _, c = bc(Qp) + return c + + constraints = [ + {'type': 'ineq', 'fun': ic_slack}, + {'type': 'ineq', 'fun': feas_slack}, + ] + bounds = [(Qp_min[0], Q_MAX), (Qp_min[1], Q_MAX)] + + # Candidate starting points + x_inits = [ + np.array([Qp_min[0], Qp_min[1]]), + np.array([min(Y_L * 0.9, Q_MAX), min(Y_H * 0.9, Q_MAX)]), + np.array([Qp_min[0], min(Y_H * 0.8, Q_MAX)]), + ] + + best_val = float(Vaut_f(Q)) # fallback: autarky payoff + best_Qp = np.array([min(max(Y_L, Qp_min[0]), Q_MAX), + min(max(Y_H, Qp_min[1]), Q_MAX)]) + + for x0 in x_inits: + x0 = np.clip(x0, [Qp_min[0], Qp_min[1]], + [Q_MAX - 1e-5, Q_MAX - 1e-5]) + try: + res = minimize( + neg_obj, x0, method='SLSQP', + bounds=bounds, constraints=constraints, + options={'ftol': 1e-10, 'maxiter': 400}) + if (res.success or res.status in (0, 9)): + val = -res.fun + if (val > best_val + and ic_slack(res.x) >= -1e-5 + and feas_slack(res.x) >= -1e-5): + best_val = val + best_Qp = res.x + except Exception: + pass + + V_new[k] = best_val + pol_Qp[k] = best_Qp + b_opt, _ = bc(best_Qp) + pol_b[k] = b_opt + + return V_new, pol_b, pol_Qp + + +def pareto_vfi(V_aut_arr, tol=1e-3, max_iter=60): + """Value function iteration for Program P*.""" + V = V_aut_arr.copy() + + for it in range(max_iter): + V_new, pol_b, pol_Qp = pareto_bellman(V, V_aut_arr) + diff = np.max(np.abs(V_new - V)) + V = V_new + print(f" iter {it+1:3d}, max|ΔV| = {diff:.5f}") + if diff < tol: + print(f"Pareto VFI converged in {it+1} iterations.") + break + + return V, pol_b, pol_Qp + + +print("Running constrained Pareto VFI …") +V_pareto, pol_b, pol_Qp = pareto_vfi(V_aut) +``` + +### Value Functions + +```{code-cell} ipython3 +fig, ax = plt.subplots(figsize=(8, 5)) + +ax.plot(Q_grid, V_aut, lw=2, label=r'Autarky $U_{\rm aut}(Q)$') +ax.plot(Q_grid, V_pareto, lw=2, ls='--', + label=r'Optimal contract $\bar{V}(Q)$') + +ax.set_xlabel(r'State $Q$ (output net of repayment)') +ax.set_ylabel('Normalised utility') +ax.set_title('Value Functions') +ax.legend() +plt.tight_layout() +plt.show() +``` + +The optimal contract strictly dominates autarky, $\bar{V}(Q) > U_{\text{aut}}(Q)$, +because access to credit allows the borrower to share risk with lenders and +smooth consumption across output realisations. + +### Optimal Continuation States and the No-Repudiation Constraint + +```{code-cell} ipython3 +# Compute no-repudiation floors +Vaut_at_Y = np.array([float(interp1d(Q_grid, V_aut, + fill_value='extrapolate', bounds_error=False)(yj)) for yj in Y]) +Qp_min_L = find_Qmin(V_pareto, Vaut_at_Y[0]) +Qp_min_H = find_Qmin(V_pareto, Vaut_at_Y[1]) + +fig, axes = plt.subplots(1, 2, figsize=(12, 5)) + +# Left: Q'_L (continuation state after low output) +axes[0].plot(Q_grid, pol_Qp[:, 0], lw=2, label=r"$Q'_L = Y_L - d_L$") +axes[0].axhline(Qp_min_L, ls='--', color='C3', + label=fr"NR floor $Q^*_L \approx {Qp_min_L:.3f}$") +axes[0].set_xlabel(r'State $Q$') +axes[0].set_ylabel(r"$Q'_L$") +axes[0].set_title(r'Continuation state after low output $Y_L$') +axes[0].legend() + +# Right: Q'_H (continuation state after high output) +axes[1].plot(Q_grid, pol_Qp[:, 1], lw=2, color='C1', + label=r"$Q'_H = Y_H - d_H$") +axes[1].axhline(Qp_min_H, ls='--', color='C3', + label=fr"NR floor $Q^*_H \approx {Qp_min_H:.3f}$") +axes[1].set_xlabel(r'State $Q$') +axes[1].set_ylabel(r"$Q'_H$") +axes[1].set_title(r'Continuation state after high output $Y_H$') +axes[1].legend() + +plt.tight_layout() +plt.show() +``` + +After a **low-output** realisation, the continuation state $Q'_L$ is pinned at the no-repudiation floor $Q^*_L$. This means the repayment $d_L = Y_L - Q'_L$ is as large as the repudiation constraint allows. After a **high-output** realisation, $Q'_H > Q^*_H$: the constraint is slack and the borrower retains more resources, rewarding the high investment that produced good output. + +### Optimal Loan and Net Capital Flows + +```{code-cell} ipython3 +# Repayments at the two output states as functions of current state Q +d_L_policy = Y_L - pol_Qp[:, 0] # d_L(Q) = Y_L − Q'_L(Q) +d_H_policy = Y_H - pol_Qp[:, 1] # d_H(Q) = Y_H − Q'_H(Q) + +fig, axes = plt.subplots(1, 2, figsize=(12, 5)) + +axes[0].plot(Q_grid, pol_b, lw=2, label='Loan $b^*(Q)$') +axes[0].plot(Q_grid, d_L_policy, lw=2, ls='--', label=r'Repayment $d_L$') +axes[0].plot(Q_grid, d_H_policy, lw=2, ls=':', label=r'Repayment $d_H$') +axes[0].axhline(0, color='k', lw=0.6, ls=':') +axes[0].set_xlabel(r'State $Q$') +axes[0].set_title('Loan and Repayment Policies') +axes[0].legend() + +# Net capital outflow at continuation state +pol_b_fn = interp1d(Q_grid, pol_b, fill_value='extrapolate', bounds_error=False) +net_out_L = d_L_policy - pol_b_fn(pol_Qp[:, 0]) +net_out_H = d_H_policy - pol_b_fn(pol_Qp[:, 1]) + +axes[1].plot(Q_grid, net_out_L, lw=2, label=r'After $Y_L$ (low output)') +axes[1].plot(Q_grid, net_out_H, lw=2, ls='--', label=r'After $Y_H$ (high output)') +axes[1].axhline(0, color='k', lw=0.8, ls=':') +axes[1].set_xlabel(r'State $Q$') +axes[1].set_ylabel(r'Net outflow $d(Y') - b'(Q')$') +axes[1].set_title('Capital Flows in the Optimal Contract') +axes[1].legend() + +plt.tight_layout() +plt.show() +``` + +After a low-output realisation, $d_L > b'(Q'_L)$: the net capital flow is an +**outflow**. After a high-output realisation, the borrower typically receives +a net capital inflow. This is the numerical counterpart of Proposition 7 in +{cite}`Atkeson1991`. + +### Simulation + +```{code-cell} ipython3 +def simulate_contract(V_pareto, pol_b, pol_Qp, T=150, seed=0): + """ + Simulate the constrained optimal contract. + At each period the borrower invests I_h and output is drawn from g_h. + Returns time series for Q, Y, consumption c, loan b, repayment d, + and net capital outflow. + """ + rng = np.random.default_rng(seed) + + Qp_fn = [interp1d(Q_grid, pol_Qp[:, j], + fill_value='extrapolate', bounds_error=False) + for j in range(2)] + b_fn = interp1d(Q_grid, pol_b, fill_value='extrapolate', bounds_error=False) + + Q = float(np.median(Q_grid)) # start at median state + + out = {'Q': [], 'Y': [], 'c': [], 'b': [], 'd': [], 'net_out': []} + + for _ in range(T): + b = float(b_fn(Q)) + c = Q + b - I_h + c = max(c, 1e-10) + + j = int(rng.choice(2, p=g_h)) # draw next output index + Yp = Y[j] + Qp = float(Qp_fn[j](Q)) # next state + + d = Yp - Qp # repayment at start of next period + b_next = float(b_fn(Qp)) + net_out = d - b_next # net capital outflow at next period + + out['Q'].append(Q); out['Y'].append(Yp); out['c'].append(c) + out['b'].append(b); out['d'].append(d); out['net_out'].append(net_out) + + Q = Qp + + return {k: np.array(v) for k, v in out.items()} + + +sim = simulate_contract(V_pareto, pol_b, pol_Qp, T=150) +t = np.arange(len(sim['Q'])) + +fig, axes = plt.subplots(3, 1, figsize=(12, 9), sharex=True) + +axes[0].plot(t, sim['Y'], alpha=0.6, label='Output $Y_{t+1}$') +axes[0].plot(t, sim['c'], lw=1.8, label='Consumption $c_t$') +axes[0].set_ylabel('Level') +axes[0].legend(ncol=2, loc='upper right') +axes[0].set_title('Simulation of the Constrained Optimal Contract') + +axes[1].plot(t, sim['d'], lw=1.8, label='Repayment $d_t$') +axes[1].plot(t, sim['b'], lw=1.8, ls='--', label='New loan $b_t$') +axes[1].axhline(0, color='k', lw=0.5) +axes[1].set_ylabel('Level') +axes[1].legend(ncol=2) + +colors = ['#d73027' if x > 0 else '#4575b4' for x in sim['net_out']] +axes[2].bar(t, sim['net_out'], color=colors, label='Net capital outflow') +axes[2].axhline(0, color='k', lw=0.6) +axes[2].set_xlabel('Period $t$') +axes[2].set_ylabel('Net outflow') +axes[2].legend() + +plt.tight_layout() +plt.show() + +# Tabulate statistics +low_out_frac = np.mean(sim['net_out'] > 0) +print(f"\nFraction of periods with capital outflow: {low_out_frac:.2%}") +print(f"Fraction of low-output periods: " + f"{np.mean(sim['Y'] == Y_L):.2%}") +``` + +Red bars (capital outflows) in the bottom panel systematically coincide with +periods in which low output $Y_L$ is realised, confirming the key prediction +of the model. + +## Exercises + +````{admonition} Exercise 1 +:class: exercise + +**Patience and the severity of debt crises.** + +Redo the analysis with $\beta = 0.8$ and $\beta = 0.95$ (keep all other +parameters fixed). + +1. For each value of $\beta$, compute the autarky and optimal contract value + functions. +2. Compute the no-repudiation lower bounds $Q^*_L$ and $Q^*_H$. +3. Plot $Q'_L(Q)$ for the three values of $\beta$ on a single figure. +4. Discuss: how does the borrower's patience affect how tightly the + no-repudiation constraint binds after low output? +```` + +````{dropdown} Solution to Exercise 1 +:class: dropdown + +```{code-cell} ipython3 +fig, ax = plt.subplots(figsize=(8, 5)) + +for β_val, ls, color in [(0.8, '-', 'C0'), (0.9, '--', 'C1'), (0.95, ':', 'C2')]: + β_orig = β + import builtins + # Temporarily override β in module scope + globals()['β'] = β_val + globals()['Δg'] = g_h - g_l # unchanged but recomputed for clarity + + V_a = autarky_vfi() + V_p, _, pQp = pareto_vfi(V_a) + + globals()['β'] = β_orig + globals()['Δg'] = g_h - g_l + + Vaut_fn_tmp = interp1d(Q_grid, V_a, fill_value='extrapolate', + bounds_error=False) + Vaut_Y_tmp = np.array([float(Vaut_fn_tmp(yj)) for yj in Y]) + Qmin_L_tmp = find_Qmin(V_p, Vaut_Y_tmp[0]) + + ax.plot(Q_grid, pQp[:, 0], ls=ls, color=color, + label=fr'$\beta = {β_val}$ (NR floor $\approx {Qmin_L_tmp:.3f}$)') + +ax.set_xlabel(r'State $Q$') +ax.set_ylabel(r"$Q'_L$ (continuation state after low output)") +ax.set_title('Effect of Patience on No-Repudiation Constraint') +ax.legend() +plt.tight_layout() +plt.show() +``` + +More patient borrowers ($\beta$ closer to 1) value the continuation of the +contract more highly, which relaxes the no-repudiation constraint: the +no-repudiation floor $Q^*_L$ falls and the capital outflow after low output is +less severe. Impatient borrowers more readily prefer autarky, tightening the +constraint and worsening debt-crisis dynamics. +```` + +````{admonition} Exercise 2 +:class: exercise + +**Signal quality and capital flows.** + +Replace the output distribution with the more symmetric values +$g_h = (0.40, 0.60)$ and $g_l = (0.60, 0.40)$, so that output is a +weaker signal of investment. + +1. Recompute the autarky and optimal contract value functions. +2. Plot the net capital outflow curves $d(Y_j) - b'(Q'_j)$ as a function + of $Q$ for both the baseline and the weak-signal specification. +3. Explain intuitively why weaker signal quality changes the capital flow + pattern. +```` + +````{dropdown} Solution to Exercise 2 +:class: dropdown + +```{code-cell} ipython3 +g_h_base, g_l_base = g_h.copy(), g_l.copy() +Δg_base = Δg.copy() + +# Weak-signal specification +globals()['g_h'] = np.array([0.40, 0.60]) +globals()['g_l'] = np.array([0.60, 0.40]) +globals()['Δg'] = g_h - g_l + +print("Weak-signal likelihood ratios g_l/g_h:", g_l / g_h) + +V_aut_ws = autarky_vfi() +V_par_ws, pb_ws, pQp_ws = pareto_vfi(V_aut_ws) + +pb_fn_ws = interp1d(Q_grid, pb_ws, fill_value='extrapolate', bounds_error=False) +net_L_ws = (Y_L - pQp_ws[:, 0]) - pb_fn_ws(pQp_ws[:, 0]) +net_H_ws = (Y_H - pQp_ws[:, 1]) - pb_fn_ws(pQp_ws[:, 1]) + +# Restore baseline +globals()['g_h'] = g_h_base +globals()['g_l'] = g_l_base +globals()['Δg'] = Δg_base + +pb_fn_bl = interp1d(Q_grid, pol_b, fill_value='extrapolate', bounds_error=False) +net_L_bl = (Y_L - pol_Qp[:, 0]) - pb_fn_bl(pol_Qp[:, 0]) + +fig, ax = plt.subplots(figsize=(8, 5)) +ax.plot(Q_grid, net_L_bl, lw=2, label=r'After $Y_L$, baseline (strong signal)') +ax.plot(Q_grid, net_L_ws, lw=2, ls='--', label=r'After $Y_L$, weak signal') +ax.axhline(0, color='k', lw=0.8, ls=':') +ax.set_xlabel(r'State $Q$') +ax.set_ylabel('Net capital outflow') +ax.set_title('Capital Flows: Baseline vs Weak Signal') +ax.legend() +plt.tight_layout() +plt.show() +``` + +With a weaker signal ($g_l/g_h$ closer to 1), low output is less informative +about past investment. The moral hazard problem is milder, incentive +constraints are easier to satisfy, and the no-repudiation constraint binds less +tightly. Capital outflows after bad output realisations are smaller in +magnitude. +```` + +````{admonition} Exercise 3 +:class: exercise + +**Debt forgiveness and welfare.** + +A debt relief programme can be modelled as an exogenous upward shift in the +no-repudiation threshold: suppose the borrower's outside option improves to +$\tilde{U}_{\text{aut}}(Y_j) = U_{\text{aut}}(Y_j) + \varepsilon$ for a small +$\varepsilon > 0$. + +1. For $\varepsilon \in \{0, 0.05, 0.10\}$, compute the constrained optimal + value function under the tightened repudiation constraint. +2. Plot $\bar{V}(Q)$ for each $\varepsilon$. +3. Discuss: when is debt forgiveness welfare improving for the borrower? + What is the cost to lenders? + +*Hint:* implement the shift by adding $\varepsilon$ to `Vaut_at_Y` inside +`pareto_bellman`. +```` + +````{dropdown} Solution to Exercise 3 +:class: dropdown + +```{code-cell} ipython3 +fig, ax = plt.subplots(figsize=(8, 5)) + +for eps, ls, color in [(0.0, '-', 'C0'), (0.05, '--', 'C1'), (0.10, ':', 'C2')]: + + def pareto_bellman_shifted(V, V_aut_arr, epsilon=eps): + """Same as pareto_bellman but with tightened NR threshold.""" + Vf = interp1d(Q_grid, V, fill_value='extrapolate', + bounds_error=False) + Vaut_f = interp1d(Q_grid, V_aut_arr, fill_value='extrapolate', + bounds_error=False) + + Vaut_Y = np.array([float(Vaut_f(yj)) for yj in Y]) + epsilon + Qp_min = np.array([find_Qmin(V, v) for v in Vaut_Y]) + Qp_min = np.clip(Qp_min, Q_MIN, Q_MAX - 1e-4) + + V_new = np.empty(N_Q) + pol_b_ = np.empty(N_Q) + pol_Qp_= np.empty((N_Q, 2)) + + for k, Q in enumerate(Q_grid): + def bc(Qp): + b = β * (g_h[0]*(Y_L-Qp[0]) + g_h[1]*(Y_H-Qp[1])) + return b, Q + b - I_h + + def neg_obj(Qp): + b, c = bc(Qp) + if c < 1e-10: return 1e10 + VQp = np.array([float(Vf(Qp[0])), float(Vf(Qp[1]))]) + return -((1-β)*u(c) + β*(VQp @ g_h)) + + def ic_slack(Qp): + b, c = bc(Qp) + if c < 1e-10: return -1e10 + VQp = np.array([float(Vf(Qp[0])), float(Vf(Qp[1]))]) + return β*(Δg @ VQp) - (1-β)*(u(c+I_h) - u(c)) + + def feas_slack(Qp): + return bc(Qp)[1] + + constr = [{'type': 'ineq', 'fun': ic_slack}, + {'type': 'ineq', 'fun': feas_slack}] + bounds = [(Qp_min[0], Q_MAX), (Qp_min[1], Q_MAX)] + + best_val = float(Vaut_f(Q)) - epsilon # rough fallback + best_Qp = np.clip([max(Qp_min[0], Y_L), max(Qp_min[1], Y_H)], + [Qp_min[0], Qp_min[1]], [Q_MAX]*2) + + for x0 in [np.array([Qp_min[0], Qp_min[1]]), + np.array([min(Y_L, Q_MAX), min(Y_H*0.9, Q_MAX)])]: + x0 = np.clip(x0, [Qp_min[0], Qp_min[1]], [Q_MAX-1e-5]*2) + try: + res = minimize(neg_obj, x0, method='SLSQP', + bounds=bounds, constraints=constr, + options={'ftol': 1e-10, 'maxiter': 300}) + if (res.success or res.status in (0, 9)): + val = -res.fun + if val > best_val and ic_slack(res.x) >= -1e-5: + best_val = val + best_Qp = res.x + except Exception: + pass + + V_new[k] = best_val + pol_Qp_[k] = best_Qp + pol_b_[k] = bc(best_Qp)[0] + + return V_new, pol_b_, pol_Qp_ + + V_eps = V_aut.copy() + for it in range(50): + V_new_eps, _, _ = pareto_bellman_shifted(V_eps, V_aut, epsilon=eps) + diff = np.max(np.abs(V_new_eps - V_eps)) + V_eps = V_new_eps + if diff < 1e-3: + break + + ax.plot(Q_grid, V_eps, ls=ls, color=color, + label=fr'$\varepsilon = {eps}$') + +ax.plot(Q_grid, V_aut, lw=1, color='k', ls=':', label='Autarky') +ax.set_xlabel(r'State $Q$') +ax.set_ylabel(r'$\bar{V}(Q)$') +ax.set_title('Effect of Tighter Repudiation Constraint on Welfare') +ax.legend() +plt.tight_layout() +plt.show() +``` + +Tightening the no-repudiation threshold ($\varepsilon > 0$) shrinks the set of +feasible contracts, reducing $\bar{V}(Q)$. Debt forgiveness improves the +borrower's outside option but makes lenders less willing to extend credit +(smaller loans at higher cost), leaving the borrower worse off in equilibrium. +This illustrates the {cite}`BulowRogoff1989b` result that debt forgiveness +need not benefit the borrowing country. +```` + +## References + +```{bibliography} +:filter: docname in docnames +``` diff --git a/lectures/tsyrennikov_2013.bib b/lectures/tsyrennikov_2013.bib new file mode 100644 index 00000000..946aee53 --- /dev/null +++ b/lectures/tsyrennikov_2013.bib @@ -0,0 +1,77 @@ +@article{Tsyrennikov2013, + author = {Viktor Tsyrennikov}, + title = {Capital flows under moral hazard}, + journal = {Journal of Monetary Economics}, + year = {2013}, + volume = {60}, + pages = {92--108} +} + +@article{GertlerRogoff1990, + author = {Mark Gertler and Kenneth Rogoff}, + title = {North-South lending and endogenous domestic capital market inefficiencies}, + journal = {Journal of Monetary Economics}, + year = {1990}, + volume = {26}, + pages = {245--266} +} + +@article{AtkesonLucas1992, + author = {Andrew Atkeson and Robert E. Lucas}, + title = {On Efficient Distribution with Private Information}, + journal = {Review of Economic Studies}, + year = {1992}, + volume = {59}, + number = {3}, + pages = {427--453} +} + +@article{ThomasWorrall1990, + author = {Jonathan Thomas and Tim Worrall}, + title = {Income fluctuation and asymmetric information}, + journal = {Journal of Economic Theory}, + year = {1990}, + volume = {51}, + number = {2}, + pages = {367--390} +} + +@article{EatonGersowitz1981, + author = {Jonathan Eaton and Mark Gersovitz}, + title = {Debt with Potential Repudiation: Theoretical and Empirical Analysis}, + journal = {Review of Economic Studies}, + year = {1981}, + volume = {48}, + number = {2}, + pages = {289--309} +} + +@article{NeuemeyerPerri2005, + author = {Andr{\'e}s Neumeyer and Fabrizio Perri}, + title = {Business cycles in emerging economies: the role of interest rates}, + journal = {Journal of Monetary Economics}, + year = {2005}, + volume = {52}, + number = {2}, + pages = {345--380} +} + +@article{AguiarGopinath2006, + author = {Mark Aguiar and Gita Gopinath}, + title = {Defaultable Debt, Interest Rates and the Current Account}, + journal = {Journal of International Economics}, + year = {2006}, + volume = {69}, + number = {1}, + pages = {64--83} +} + +@article{AguiarGopinath2007, + author = {Mark Aguiar and Gita Gopinath}, + title = {Emerging Market Business Cycles: The Cycle Is the Trend}, + journal = {Journal of Political Economy}, + year = {2007}, + volume = {115}, + number = {1}, + pages = {69--102} +} diff --git a/lectures/tsyrennikov_2013.md b/lectures/tsyrennikov_2013.md new file mode 100644 index 00000000..d60ca12f --- /dev/null +++ b/lectures/tsyrennikov_2013.md @@ -0,0 +1,920 @@ +--- +jupytext: + text_representation: + extension: .md + format_name: myst +kernelspec: + display_name: Python 3 + language: python + name: python3 +--- + +(tsyrennikov_2013)= +```{raw} html + +``` + +# Capital Flows Under Moral Hazard + +## Overview + +This lecture studies {cite}`Tsyrennikov2013`, which extends {cite}`Atkeson1991` +(see the companion lecture {ref}`atkeson_1991`) in two directions: + +1. **Continuous investment** — the borrower chooses a continuous investment + level rather than a binary one, and the paper proves that the + **first-order approach** (FOA) to the incentive-compatibility constraint is + valid. This brings the model much closer to empirically relevant calibrations. +2. **Calibration and quantitative analysis** — the model is calibrated to + Argentina's business cycle data and compared against a limited-enforcement + (Eaton–Gersowitz-style) model. + +The central finding is that **moral hazard, not limited enforcement, drives the +key empirical regularities of emerging market economies**: high and volatile +interest rate spreads, limited consumption risk-sharing, and crisis-like +dynamics in which capital inflows suddenly stop. + +The key mechanism: moral hazard severely restricts *state contingency* in +repayment schedules. In the language of {cite}`Atkeson1991`, the optimal +contract is nearly *non-contingent* on output — a theoretical justification for +why simple debt contracts dominate in practice. + +```{note} +This lecture uses the same notation as the {ref}`atkeson_1991` lecture, +writing $\beta$ for the borrower's discount factor (Tsyrennikov writes $\beta$ +for the borrower and $\beta_c$ for the lender). +``` + +## The Model + +### Technology and Preferences + +The environment is a small open economy with an infinitely-lived borrower. +The borrower starts each period with net worth $n$ (output net of debt +repayment), borrows $b$ from a short-lived risk-neutral lender, invests $I$, +and consumes + +$$ +c \;=\; n + b - \theta I, \quad \theta > 0. +$$ + +Given investment $I$, next period's output $Y'$ is drawn from + +$$ +g(Y_j \mid I) \;=\; \bigl(1 - \lambda(I)\bigr)\,g_{0j} + + \lambda(I)\,g_{1j}, \qquad j = 1, 2, +$$ + +where $\lambda : \mathbb{R}_+ \to [0,1]$ is strictly increasing and strictly +concave, so higher investment stochastically dominates lower investment. +Tsyrennikov restricts to two output states and sets + +$$ +\text{Pr}(Y_1 \mid I) = 1 - \lambda(I), \qquad +\text{Pr}(Y_2 \mid I) = \lambda(I), \qquad Y_1 < Y_2, +$$ + +so $g_{0,1}=1,\;g_{0,2}=0,\;g_{1,1}=0,\;g_{1,2}=1$ and +$\Delta g_j \equiv g_{1j} - g_{0j} = (-1, 1)$. +The functional form $\lambda(I) = \min(I^\nu, 1)$ with $\nu \in (0,1)$ +is strictly concave and gives an interior optimum. + +The borrower's preferences are CRRA: + +$$ +U^B = \mathbb{E}_0 \sum_{t=0}^\infty \beta^t \, u(c_t), + \quad u(c) = \frac{c^{1-\gamma}}{1-\gamma}, \quad \gamma > 1. +$$ + +Lenders discount at rate $\beta_c \geq \beta$ (the international risk-free +rate) and have endowment $M$ each period, so $b \leq M$. + +### Two Frictions + +**Moral hazard (MH)**: lenders observe output but not investment. The +incentive-compatibility (IC) constraint requires that the borrower finds the +contracted investment $I$ to be in their own best interest. + +**Limited enforcement (LE)**: the borrower can default, suffering a one-time +output penalty: if default occurs when output is $Y_j$, the borrower retains +only $\delta Y_j$ (with $\delta \in (0,1)$) and then lives in autarky. +The participation constraint requires + +$$ +V(Y_j - d_j) \;\geq\; V_{\text{aut}}(\delta\,Y_j), \quad \forall j, +$$ + +where $V$ is the contract value function and $V_{\text{aut}}$ is the autarky +value function. + +### The Autarky Value Function + +Without access to credit ($b = 0$), the borrower solves + +$$ +V_{\text{aut}}(n) = \max_{I \in [0,\,n/\theta]} + \Bigl[u(n - \theta I) + \beta\,\bigl[(1-\lambda(I))\,V_{\text{aut}}(Y_1) + + \lambda(I)\,V_{\text{aut}}(Y_2)\bigr]\Bigr]. +$$ + +Note that the continuation values depend only on $Y_1$ and $Y_2$, not on $n$. + +### The Recursive Contract + +The state variable is net worth $n$. The value function satisfies the Bellman +equation + +$$ +V(n) = \max_{b,\,d,\,I} + \Bigl[u(n+b-\theta I) + \beta\,\sum_j g(Y_j\mid I)\,V(Y_j - d_j)\Bigr] +$$ + +subject to feasibility, lender participation ($b \leq \beta_c \sum_j +g_j(I)\,d_j$), incentive compatibility, and enforcement constraints. + +## The First-Order Approach + +A key contribution of {cite}`Tsyrennikov2013` is **Lemma 1**, which shows that +replacing the full IC constraint with the first-order condition + +$$ +-\theta\,u'(c) + \beta\,\lambda'(I)\,\sum_j \Delta g_j\,V(Y_j-d_j) \geq 0 +$$ + +does **not** alter the solution. The key step is showing that at any feasible +contract, $\sum_j \Delta g_j\,V(Y_j-d_j) \geq 0$, which ensures the +borrower's objective is strictly concave in $I$ and the FOC holds with +equality. This result (analogous to {cite}`Rogerson1985`) validates the +relaxed formulation used in the numerical solution. + +With the FOA, the optimality condition for investments is + +$$ +\theta\,u'(c) \;=\; \beta\,\lambda'(I)\,\bigl[V(n_2') - V(n_1')\bigr], +\tag{FOA} +$$ + +where $n_j' = Y_j - d_j$ is next period's net worth after state $j$. A +higher spread $V(n_2') - V(n_1')$ — more reward in the high state — +supports a higher investment level. + +## The Euler Equation and Implied Interest Rate + +The Euler equation (Appendix A of {cite}`Tsyrennikov2013`) for the MH model is + +$$ +V'(n) \;=\; V'(n_j')\!\left[1 + \mu\, + \frac{\lambda'(I)\,\Delta g_j}{g(Y_j\mid I)}\right] + \phi, +$$ + +where $\mu \geq 0$ is the multiplier on the FOA constraint and $\phi \geq 0$ +on the lender endowment $b \leq M$. + +Because $\Delta g_1 = -1 < 0$, the factor for the low state is less than one: +$V'(n_1') > V'(n)$. By concavity of $V$, the borrower's net worth falls in +the low state. This is the **immiseration** property: moral hazard forces +the borrower to bear more risk than would be optimal with full information +(cf.\ {cite}`ThomasWorrall1990`, {cite}`AtkesonLucas1992`). + +The borrower faces an **implied interest rate** + +$$ +R(n) \;=\; \frac{u'(c(n))}{\beta\,\sum_j g(Y_j\mid I(n))\,u'(c(n_j'(n)))}, +$$ + +where $c(n_j'(n))$ is next period's consumption if state $j$ is realised. +This rate is counter-cyclical: when $n$ is low, past incentive provision has +depressed the continuation values, raising the marginal utility spread and +increasing $R$. + +## Computation + +We now implement these ideas numerically using the parameterisation from +{cite}`Tsyrennikov2013`. + +### Parameters + +```{code-cell} ipython3 +import numpy as np +from scipy.interpolate import interp1d +from scipy.optimize import minimize_scalar, minimize, brentq +import matplotlib.pyplot as plt + +plt.rcParams.update({'font.size': 12, 'figure.dpi': 100}) + +# ── Preferences ────────────────────────────────────────────────────────────── +β = 0.980 # borrower discount factor +β_c = 0.990 # lender (world) discount factor +γ = 2.0 # CRRA coefficient +θ = 0.105 # investment resource cost (θ in budget n+b = c+θI) + +# ── Investment technology ───────────────────────────────────────────────────── +# λ(I) = I^ν (probability of high output) +ν = 0.950 + +def lam(I): + return np.minimum(I**ν, 1.0) + +def dlam(I): + """λ'(I) = ν * I^{ν−1} (for I > 0).""" + return ν * I**(ν - 1.0) + +# ── Default penalty ─────────────────────────────────────────────────────────── +δ = 0.795 # borrower keeps fraction δ of output on default + +# ── Lender endowment ───────────────────────────────────────────────────────── +M = 0.465 + +# ── Output states: ln(Y_j) = ±0.054, normalised so parameter θ gives E[Y]=1 ─ +Y1 = np.exp(-0.054) # ≈ 0.9474 +Y2 = np.exp(+0.054) # ≈ 1.0555 +Y = np.array([Y1, Y2]) + +# ── Utility ─────────────────────────────────────────────────────────────────── +def u(c): + c = np.maximum(c, 1e-12) + return c**(1.0 - γ) / (1.0 - γ) + +def u_prime(c): + return np.maximum(c, 1e-12)**(-γ) + +# ── Net-worth grid ──────────────────────────────────────────────────────────── +N_n = 150 +n_lo = 0.08 +n_hi = 1.30 +n_grid = np.linspace(n_lo, n_hi, N_n) + +print(f"Output states: Y1 = {Y1:.4f}, Y2 = {Y2:.4f}") +print(f"β = {β}, β_c = {β_c}, γ = {γ}, θ = {θ}, ν = {ν}") +``` + +### Autarky Value Function + +```{code-cell} ipython3 +def autarky_bellman_at_n(n, Vf): + """ + Solve the autarky Bellman at state n given current iterate Vf. + Returns (V_new, I_opt). + Uses the fact that continuation values only depend on Y1, Y2. + """ + EV1 = float(Vf(Y1)) + EV2 = float(Vf(Y2)) + + def neg_obj(I): + c = n - θ * I + if c < 1e-10: + return 1e10 + l = lam(I) + return -(u(c) + β * ((1.0 - l) * EV1 + l * EV2)) + + I_max = n / θ - 1e-8 + if I_max <= 0: + return u(n) + β * ((1 - lam(0)) * EV1 + lam(0) * EV2), 0.0 + + res = minimize_scalar(neg_obj, bounds=(1e-8, I_max), method='bounded', + options={'xatol': 1e-9}) + return -res.fun, res.x + + +def autarky_vfi(tol=1e-8, max_iter=3000): + V = np.zeros(N_n) + + for it in range(max_iter): + Vf = interp1d(n_grid, V, fill_value='extrapolate', bounds_error=False) + V_new = np.array([autarky_bellman_at_n(n, Vf)[0] for n in n_grid]) + diff = np.max(np.abs(V_new - V)) + V = V_new + if diff < tol: + print(f"Autarky VFI converged in {it+1} iterations (diff = {diff:.2e})") + break + + return V + + +V_aut = autarky_vfi() +``` + +### Moral Hazard Model + +For each state $n$, we optimise over continuation states +$(n_1', n_2')$ where $n_j' = Y_j - d_j$. For every candidate +$(n_1', n_2')$: + +1. Compute $\Delta V = V(n_2') - V(n_1')$. +2. With lender participation binding, the loan is + $b^* = \beta_c\bigl[(1-\lambda(I))(Y_1-n_1') + \lambda(I)(Y_2-n_2')\bigr]$. +3. Substitute into the FOA equation and solve for $I^*$: + +$$ +\theta\,\bigl[A + \lambda(I^*)\,\Delta B - \theta I^*\bigr]^{-\gamma} + \;=\; \beta\,\lambda'(I^*)\,\Delta V, +$$ + +where $A \equiv n + \beta_c (Y_1-n_1')$ and +$\Delta B \equiv \beta_c\bigl[(Y_2-n_2') - (Y_1-n_1')\bigr]$. + +This reduces the optimisation to two dimensions. + +```{code-cell} ipython3 +def solve_I_star(n, n1p, n2p, Vf): + """ + Solve the FOA equation for I*, given (n, n1', n2') and current V. + Returns (I_star, c_star, b_star, lam_star) or None if infeasible. + """ + dV = float(Vf(n2p)) - float(Vf(n1p)) + if dV <= 1e-10: # no interior solution (V flat or decreasing) + return None + + A = n + β_c * (Y1 - n1p) + ΔB = β_c * ((Y2 - n2p) - (Y1 - n1p)) + + def c_star_of_I(I): + return A + I**ν * ΔB - θ * I + + # Upper bound on I: c* > 0 + # Approximate: I_max ≈ A/θ (rough; refine by bisection) + I_hi = min(A / θ * 0.999, 1.0 - 1e-6) + while I_hi > 1e-6 and c_star_of_I(I_hi) < 1e-8: + I_hi *= 0.9 + if I_hi < 1e-6: + return None + + I_lo = 1e-7 + + def foa(I): + c = c_star_of_I(I) + if c < 1e-10: + return 1e10 + return θ * u_prime(c) - β * dlam(I) * dV + + # foa(I_lo) < 0 (RHS → +∞), foa(I_hi) should be > 0 eventually + if foa(I_lo) >= 0 or foa(I_hi) <= 0: + return None + + try: + I_star = brentq(foa, I_lo, I_hi, xtol=1e-10) + except ValueError: + return None + + c = c_star_of_I(I_star) + l = lam(I_star) + b = β_c * ((1-l)*(Y1-n1p) + l*(Y2-n2p)) + return I_star, c, b, l + + +def mh_bellman_at_n(n, Vf, Vaut_f, thresh1, thresh2): + """ + Solve the MH Bellman equation at state n. + thresh1, thresh2 are enforcement lower bounds on n1', n2'. + Returns (V_new, n1p_opt, n2p_opt, I_opt, b_opt). + """ + def neg_obj(x): + n1p, n2p = x + sol = solve_I_star(n, n1p, n2p, Vf) + if sol is None: + return 1e10 + I_star, c, b, l = sol + if c < 1e-10 or b > M + 1e-6: + return 1e10 + EV = (1-l)*float(Vf(n1p)) + l*float(Vf(n2p)) + return -(u(c) + β * EV) + + # Enforcement lower bounds on continuation states + lo = np.array([max(thresh1, n_lo), max(thresh2, n_lo)]) + hi = np.array([min(Y1 * 1.1, n_hi - 1e-4), + min(Y2 * 1.05, n_hi - 1e-4)]) + + # Starting points + x_inits = [ + lo, + np.array([min(Y1 * 0.95, hi[0]), min(Y2 * 0.95, hi[1])]), + np.array([lo[0], min(Y2 * 0.85, hi[1])]), + ] + + best_val = float(Vaut_f(n)) # fallback: autarky + best_x = np.array([min(Y1, hi[0]), min(Y2, hi[1])]) + + for x0 in x_inits: + x0 = np.clip(x0, lo, hi) + try: + res = minimize(neg_obj, x0, method='Nelder-Mead', + options={'xatol': 1e-7, 'fatol': 1e-7, + 'maxiter': 800, 'adaptive': True}) + val = -res.fun + if val > best_val: + # Verify enforcement constraints hold + n1p_try, n2p_try = np.clip(res.x, lo, hi) + sol = solve_I_star(n, n1p_try, n2p_try, Vf) + if sol is not None and sol[1] > 1e-8: + best_val = val + best_x = np.array([n1p_try, n2p_try]) + except Exception: + pass + + n1p, n2p = best_x + sol = solve_I_star(n, n1p, n2p, Vf) + if sol is None: + return best_val, n1p, n2p, 0.0, 0.0 + I_star, c, b, l = sol + return best_val, n1p, n2p, I_star, b + + +def mh_vfi(V_aut, tol=1e-3, max_iter=60): + """Value function iteration for the moral hazard model.""" + V = V_aut.copy() + Vf_aut = interp1d(n_grid, V_aut, fill_value='extrapolate', bounds_error=False) + + # Enforcement bounds: V(n_j') >= V_aut(δ*Y_j) + # => n_j' >= V^{-1}(V_aut(δ*Y_j)) + # Since V(n) >= V_aut(n), the bound V_aut(δ*Y_j) is automatically + # satisfied at n_j' >= δ*Y_j; use this conservative bound initially. + thresh1_fixed = δ * Y1 + thresh2_fixed = δ * Y2 + + pol_n1p = np.empty(N_n) + pol_n2p = np.empty(N_n) + pol_I = np.empty(N_n) + pol_b = np.empty(N_n) + + for it in range(max_iter): + Vf = interp1d(n_grid, V, fill_value='extrapolate', bounds_error=False) + V_new = np.empty(N_n) + + for k, n in enumerate(n_grid): + vn, n1p, n2p, I_star, b = mh_bellman_at_n( + n, Vf, Vf_aut, thresh1_fixed, thresh2_fixed) + V_new[k] = vn + pol_n1p[k] = n1p + pol_n2p[k] = n2p + pol_I[k] = I_star + pol_b[k] = b + + diff = np.max(np.abs(V_new - V)) + V = V_new + print(f" iter {it+1:3d}, max|ΔV| = {diff:.5f}") + if diff < tol: + print(f"MH VFI converged in {it+1} iterations.") + break + + return V, pol_n1p, pol_n2p, pol_I, pol_b + + +print("Running moral hazard VFI…") +V_mh, pol_n1p, pol_n2p, pol_I, pol_b = mh_vfi(V_aut) +``` + +### Value Functions and Insurance + +```{code-cell} ipython3 +fig, axes = plt.subplots(1, 2, figsize=(12, 5)) + +# Left: value functions +axes[0].plot(n_grid, V_aut, lw=2, label='Autarky') +axes[0].plot(n_grid, V_mh, lw=2, ls='--', label='Moral hazard') +axes[0].set_xlabel('Net worth $n$') +axes[0].set_ylabel('Value') +axes[0].set_title('Value Functions') +axes[0].legend() + +# Right: Risk-sharing index RSI = (d_2 - d_1) / (Y_2 - Y_1) +# d_j = Y_j - n_j', so RSI = (n_1' - n_2') / (Y_2 - Y_1) + 1 +# ... actually RSI = (d_2 - d_1)/(Y_2-Y_1) = ((Y2-n2p)-(Y1-n1p))/(Y2-Y1) +d1_mh = Y1 - pol_n1p +d2_mh = Y2 - pol_n2p +RSI_mh = (d2_mh - d1_mh) / (Y2 - Y1) + +axes[1].plot(n_grid, RSI_mh, lw=2, color='C1') +axes[1].axhline(1.0, ls=':', color='k', lw=1, label='Full insurance (RSI=1)') +axes[1].axhline(0.0, ls='--', color='k', lw=1, label='Non-contingent debt (RSI=0)') +axes[1].set_xlabel('Net worth $n$') +axes[1].set_ylabel('Risk-sharing index') +axes[1].set_title('State Contingency of Repayment') +axes[1].set_ylim(-0.1, 1.2) +axes[1].legend() + +plt.tight_layout() +plt.show() + +print(f"\nMean RSI (moral hazard): {np.mean(RSI_mh):.4f}") +print("→ Repayment is nearly state non-contingent (RSI ≈ 0)") +print("→ Moral hazard justifies why simple non-contingent debt is optimal") +``` + +A key finding of {cite}`Tsyrennikov2013` emerges immediately: the risk-sharing +index is close to **zero** throughout the state space. Moral hazard requires +spreading continuation values to incentivise investment, but this is achieved +by differentiating *net worth* $n_j'$, not repayment $d_j = Y_j - n_j'$. +The near-equality $d_1 \approx d_2$ means repayment is essentially +**non-contingent on output** — the model rationalises why emerging market +borrowers use plain debt instruments rather than GDP-linked securities. + +### Optimal Investment + +```{code-cell} ipython3 +fig, axes = plt.subplots(1, 2, figsize=(12, 5)) + +# Compute autarky optimal investment +I_aut = np.array([autarky_bellman_at_n(n, + interp1d(n_grid, V_aut, fill_value='extrapolate', bounds_error=False))[1] + for n in n_grid]) + +axes[0].plot(n_grid, lam(I_aut), lw=2, label='Autarky') +axes[0].plot(n_grid, lam(pol_I), lw=2, ls='--', label='Moral hazard') +axes[0].set_xlabel('Net worth $n$') +axes[0].set_ylabel(r'$\lambda(I) = \Pr(Y_2 \mid I)$') +axes[0].set_title('Investment (probability weight on high output)') +axes[0].legend() + +axes[1].plot(n_grid, pol_n1p, lw=2, label=r"$n_1' = Y_1 - d_1$ (after low output)") +axes[1].plot(n_grid, pol_n2p, lw=2, ls='--', label=r"$n_2' = Y_2 - d_2$ (after high output)") +axes[1].plot(n_grid, n_grid, lw=1, ls=':', color='k', label='45° line') +axes[1].set_xlabel('Net worth $n$') +axes[1].set_ylabel("Continuation net worth $n_j'$") +axes[1].set_title('Optimal Continuation Net Worth') +axes[1].legend(fontsize=10) + +plt.tight_layout() +plt.show() +``` + +Investment under moral hazard is *lower* than in autarky at high net worth +levels and more sensitive to $n$ at low levels. After a low output +realisation, net worth drops sharply ($n_1' \ll n$), depressing future +investment and perpetuating the crisis — the model's **internal propagation +mechanism**. + +### Implied Interest Rate + +```{code-cell} ipython3 +# Compute implied interest rate R(n) +# R(n) = u'(c(n)) / [β * Σ_j g_j(I(n)) * u'(c'(n_j'(n)))] +# where c'(n_j') = n_j' + b*(n_j') - θ I*(n_j') (next period's consumption) + +pol_b_fn = interp1d(n_grid, pol_b, fill_value='extrapolate', bounds_error=False) +pol_I_fn = interp1d(n_grid, pol_I, fill_value='extrapolate', bounds_error=False) +pol_n1p_fn = interp1d(n_grid, pol_n1p, fill_value='extrapolate', bounds_error=False) +pol_n2p_fn = interp1d(n_grid, pol_n2p, fill_value='extrapolate', bounds_error=False) + +def next_period_c(np_val): + """Consumption at the start of next period given continuation n'.""" + b_next = float(pol_b_fn(np_val)) + I_next = float(pol_I_fn(np_val)) + return np_val + b_next - θ * I_next + +R_n = np.empty(N_n) +for k, n in enumerate(n_grid): + b = pol_b[k] + I = pol_I[k] + c = n + b - θ * I + l = lam(I) + n1p = pol_n1p[k] + n2p = pol_n2p[k] + c1p = next_period_c(n1p) + c2p = next_period_c(n2p) + denom = β * ((1-l)*u_prime(c1p) + l*u_prime(c2p)) + R_n[k] = u_prime(c) / denom if denom > 1e-10 else np.nan + +# Annualised spread over world rate +R_world = 1.0 / β_c # gross world rate +spread_ann = (R_n**4 - R_world**4) # approximate annualised spread + +fig, axes = plt.subplots(1, 2, figsize=(12, 5)) + +axes[0].plot(n_grid, R_n, lw=2) +axes[0].axhline(R_world, ls='--', color='k', lw=1, label=f'World rate $1/\\beta_c = {R_world:.3f}$') +axes[0].set_xlabel('Net worth $n$') +axes[0].set_ylabel('Implied gross interest rate $R(n)$') +axes[0].set_title('Interest Rate Schedule') +axes[0].legend() + +axes[1].plot(n_grid, np.clip(spread_ann * 100, -1, 50), lw=2) +axes[1].axhline(0, ls='--', color='k', lw=0.8) +axes[1].set_xlabel('Net worth $n$') +axes[1].set_ylabel('Annualised spread over world rate (%)') +axes[1].set_title('Interest Rate Spread') + +plt.tight_layout() +plt.show() +``` + +The interest rate spread rises sharply at low net worth levels, consistent with +the Argentine data. The mechanism is the **MH Euler equation**: when $n$ is +low, the borrower's continuation value is depressed and the spread in marginal +utilities across future states increases $R(n)$. + +### Crisis Dynamics + +{cite}`Tsyrennikov2013` shows that a string of low output realisations +generates gradual debt accumulation followed by a sudden stop in which capital +inflows cease and interest rates spike — a pattern consistent with the +Argentina 2001 experience. + +```{code-cell} ipython3 +def simulate_crisis(T_crisis=8): + """ + Simulate crisis path: T_crisis periods of low output (Y_1) starting + from zero debt (n_0 = Y2, high initial net worth). + """ + n = Y2 # start with high net worth + records = {'n': [n], 'debt_over_Y': [], 'R': [], 'ca_over_Y': [], + 'lam': []} + + for t in range(T_crisis): + b = float(pol_b_fn(n)) + I = float(pol_I_fn(n)) + n1p = float(pol_n1p_fn(n)) + n2p = float(pol_n2p_fn(n)) + + c = n + b - θ * I + l = lam(I) + c1p = next_period_c(n1p) + c2p = next_period_c(n2p) + denom = β * ((1-l)*u_prime(c1p) + l*u_prime(c2p)) + R = u_prime(c) / denom if denom > 1e-10 else np.nan + + # Debt = promised repayment − principal rolled over + # Approximate debt/output = b / Y1 (loan at current period) + debt_Y = b / Y1 + + # Current account = d_t − b_t (repayment received − new loan given) + # At t=0, d_0 = 0 (no old contract); approximate d_t = Y1 - n + d_approx = Y1 - n + ca = d_approx - b + + records['debt_over_Y'].append(debt_Y) + records['R'].append(R) + records['ca_over_Y'].append(ca / Y1) + records['lam'].append(l) + + n = n1p # low output path + + records['n'] += [float(pol_n1p_fn(records['n'][-1]))] + return records + + +crisis = simulate_crisis(T_crisis=8) +t_ax = np.arange(len(crisis['R'])) + +fig, axes = plt.subplots(2, 2, figsize=(12, 8), sharex=True) +fig.suptitle('Crisis Scenario: 8 Consecutive Low-Output Periods', fontsize=13) + +axes[0,0].plot(t_ax, crisis['debt_over_Y'], 'o-', lw=2) +axes[0,0].set_ylabel('Debt / output') +axes[0,0].set_title('(A) Debt accumulation') + +axes[0,1].plot(t_ax, np.array(crisis['R'])**4 * 100, 's-', lw=2, color='C1') +axes[0,1].axhline((1/β_c)**4 * 100, ls='--', color='k', lw=0.8, + label='World rate') +axes[0,1].set_ylabel('Annualised gross rate (%)') +axes[0,1].set_title('(B) Interest rate') +axes[0,1].legend(fontsize=9) + +axes[1,0].plot(t_ax, crisis['ca_over_Y'], '^-', lw=2, color='C2') +axes[1,0].axhline(0, ls='--', color='k', lw=0.8) +axes[1,0].set_xlabel('Quarter') +axes[1,0].set_ylabel('Current account / output') +axes[1,0].set_title('(C) Current account') + +axes[1,1].plot(t_ax, crisis['lam'], 'D-', lw=2, color='C3') +axes[1,1].set_xlabel('Quarter') +axes[1,1].set_ylabel(r'$\lambda(I) = \Pr(Y_2 \mid I)$') +axes[1,1].set_title('(D) Investment (prob. of high output)') + +plt.tight_layout() +plt.show() +``` + +The simulation reproduces the stylised crisis pattern of {cite}`Tsyrennikov2013`, +Fig. 4: + +- **Panel A**: Debt steadily accumulates as the borrower is pushed toward the + borrowing limit by repeated low output. +- **Panel B**: Interest rates remain near the world rate initially but spike + sharply once the borrower approaches the borrowing limit — the + **late-warning** property. +- **Panel C**: The current account first worsens gradually (capital inflows + shrink) and then abruptly turns around as the borrowing limit is reached. +- **Panel D**: Investment collapses as net worth falls, further reducing the + probability of high future output — the **internal propagation mechanism**. + +### MH Versus Limited Enforcement + +A crucial result of {cite}`Tsyrennikov2013` is that **limited enforcement adds +little** to the model's performance relative to moral hazard alone. The +intuition: under LE (no moral hazard), optimal repayments are *highly state +contingent* (RSI ≈ 0.8), providing near-full insurance. The borrower's net +worth drifts *upward* under LE (unlike MH where it drifts downward), so +interest rate spreads are transitory rather than persistent. + +The following code illustrates the key theoretical distinction by computing +the Euler equation implications under each friction separately. + +```{code-cell} ipython3 +# Illustrate the Euler equation implications theoretically +fig, ax = plt.subplots(figsize=(8, 5)) + +# Under MH (μ > 0, γ_j = 0): +# V'(n) = V'(n_j') [1 + μ λ'(I) Δg_j / g_j(I)] + φ +# → low-state factor < 1: V'(n1') > V'(n) → n1' < n (net worth falls) +# → high-state factor > 1: V'(n2') < V'(n) → n2' > n (net worth rises) +# +# Under LE (μ = 0, γ_j > 0): +# V'(n) = V'(n_j') [1 + γ_j] + φ ≥ V'(n_j') +# → V'(n_j') ≤ V'(n) → n_j' ≥ n (net worth drifts upward) + +# Stylised illustration using the computed MH policy +Vf_mh = interp1d(n_grid, V_mh, fill_value='extrapolate', bounds_error=False) + +expected_np_mh = (1 - lam(pol_I)) * pol_n1p + lam(pol_I) * pol_n2p + +ax.plot(n_grid, expected_np_mh, lw=2, label='MH: E[n\'] (drifts down)') +ax.plot(n_grid, n_grid, lw=1, ls=':', color='k', label='45° line') + +# Under LE (approximate): net worth is a supermartingale, E[n'] ≥ n * β/β_c +expected_np_le = n_grid * (β / β_c) +ax.plot(n_grid, expected_np_le, lw=2, ls='--', color='C2', + label=r'LE: E[n\'] $\approx$ $(\beta/\beta_c)\,n$ (drifts up)') + +ax.set_xlabel('Current net worth $n$') +ax.set_ylabel("Expected continuation net worth $E[n']$") +ax.set_title('Drift of Net Worth Under MH vs LE') +ax.legend(fontsize=10) +plt.tight_layout() +plt.show() +``` + +Under moral hazard, $\mathbb{E}[n'] < n$: net worth drifts down and the +borrower spends substantial time near the borrowing limit, generating +persistent interest rate spreads. Under limited enforcement, +$\mathbb{E}[n'] \geq (\beta/\beta_c)\,n$: net worth drifts toward a stationary +level and the borrower eventually escapes financial stress. + +## Empirical Test + +{cite}`Tsyrennikov2013` proposes a simple test to distinguish moral hazard from +limited enforcement. After a low past output realisation ($y_{t-1} = Y_1$), +the MH contract lowers net worth sharply, reducing future consumption +smoothing. This prediction is: + +$$ +\text{MH economy}: \quad + \rho(c_t, y_t \mid y_{t-1} = Y_1) \;>\; \rho(c_t, y_t \mid y_{t-1} = Y_2), +$$ + +while the LE economy gives the opposite ordering (insurance is better after +low realisations). Using Argentine quarterly data (1993–2005), the observed +correlations are 0.98 (after low output) vs. 0.91 (after high output) — +**consistent with moral hazard**. + +## Exercises + +````{admonition} Exercise 1 +:class: exercise + +**Effect of default penalty.** The parameter $\delta \in (0,1)$ controls +the severity of the output loss upon default. + +1. Compute $V_{\text{aut}}$ for $\delta \in \{0.5,\, 0.795,\, 0.95\}$. +2. For each $\delta$, evaluate the enforcement threshold + $V_{\text{aut}}(\delta Y_1)$ and $V_{\text{aut}}(\delta Y_2)$. +3. Discuss: how does a harsher default penalty affect the tightness of the + enforcement constraint and (via the Euler equation) the interest rate + spread? At $\delta = 1$ the LE constraint becomes $V(n_j') \geq V_{\text{aut}}(Y_j)$; + at $\delta \to 0$ it is vacuous. +```` + +````{dropdown} Solution to Exercise 1 +:class: dropdown + +```{code-cell} ipython3 +fig, ax = plt.subplots(figsize=(8, 5)) + +Vaut_f_global = interp1d(n_grid, V_aut, fill_value='extrapolate', bounds_error=False) + +for δ_val, ls, color in [(0.50, ':', 'C0'), (0.795, '--', 'C1'), (0.95, '-', 'C2')]: + thresh1 = float(Vaut_f_global(δ_val * Y1)) + thresh2 = float(Vaut_f_global(δ_val * Y2)) + # Net worth lower bound from enforcement: n_j' >= V^{-1}(thresh_j) + # For illustration plot the thresholds + print(f"δ={δ_val:.3f}: V_aut(δ·Y1)={thresh1:.3f}, V_aut(δ·Y2)={thresh2:.3f}") + +ax.plot(n_grid, V_aut, lw=2) +for δ_val, label in [(0.50, 'δ=0.50'), (0.795, 'δ=0.795'), (0.95, 'δ=0.95')]: + t1 = float(Vaut_f_global(δ_val * Y1)) + t2 = float(Vaut_f_global(δ_val * Y2)) + ax.axhline(t1, ls=':', lw=1.5, label=f'{label}: V_aut(δ·Y1)') + +ax.set_xlabel('Net worth $n$'); ax.set_ylabel('$V_{\\rm aut}(n)$') +ax.set_title('Enforcement Thresholds for Different Default Penalties δ') +ax.legend(fontsize=9) +plt.tight_layout() +plt.show() +``` + +A harsher default penalty (larger $\delta$) raises the enforcement thresholds, +tightening the participation constraints and reducing the scope for +state-contingent repayment. Paradoxically, this may *reduce* the interest +rate spread by forcing the lender to offer more consumption insurance to keep +the borrower from defaulting. At $\delta \to 0$ the enforcement constraint is +vacuous and the model collapses to pure moral hazard. +```` + +````{admonition} Exercise 2 +:class: exercise + +**Discounting wedge and impatience.** + +1. Re-solve the MH model for $\beta = \beta_c = 0.990$ (equal discounting — + no impatience wedge) and for $\beta = 0.950$ (larger wedge). +2. For each case, plot the expected continuation net worth + $\mathbb{E}[n'] = (1-\lambda(I^*))n_1' + \lambda(I^*)n_2'$ against $n$. +3. Discuss: how does the discount wedge $\beta_c - \beta$ interact with moral + hazard in determining the stationary distribution of net worth? + +*Hint*: When $\beta = \beta_c$ the only force pushing net worth down is moral +hazard (immiseration). When $\beta < \beta_c$ there is an additional +front-loading incentive that the lender can exploit. +```` + +````{dropdown} Solution to Exercise 2 +:class: dropdown + +```{code-cell} ipython3 +fig, ax = plt.subplots(figsize=(8, 5)) + +for β_val, ls, color in [(0.990, '-', 'C0'), (0.980, '--', 'C1'), (0.950, ':', 'C2')]: + globals()['β'] = β_val + V_a_tmp = autarky_vfi() + V_mh_tmp, pol_n1p_tmp, pol_n2p_tmp, pol_I_tmp, _ = mh_vfi(V_a_tmp) + globals()['β'] = 0.980 # restore + + E_np = ((1 - lam(pol_I_tmp)) * pol_n1p_tmp + + lam(pol_I_tmp) * pol_n2p_tmp) + ax.plot(n_grid, E_np, ls=ls, color=color, + label=fr'$\beta={β_val}$') + +ax.plot(n_grid, n_grid, lw=1, ls=':', color='k', label='45° line') +ax.set_xlabel('Net worth $n$') +ax.set_ylabel("$E[n']$") +ax.set_title('Expected Continuation Net Worth for Different Discount Factors') +ax.legend() +plt.tight_layout() +plt.show() +``` + +The larger the discount wedge $\beta_c - \beta$, the faster net worth drifts +toward the borrowing limit. When $\beta = \beta_c$ moral hazard alone drives +immiseration, while impatience accelerates it further. A small wedge +(as calibrated by Tsyrennikov) is significant: it is *equivalent to +increasing the borrower's discount rate by 2% per annum* (even though +the assumed difference in quarterly rates is only 0.010). +```` + +````{admonition} Exercise 3 +:class: exercise + +**Non-contingency of optimal debt.** + +The *risk-sharing index* $\text{RSI}(n) = (d_2(n) - d_1(n)) / (Y_2 - Y_1)$ +measures how state-contingent the repayment schedule is. RSI = 1 is full +insurance; RSI = 0 is non-contingent debt. + +1. Compute RSI for the MH model you have already solved. +2. Now set $\beta = \beta_c$ (equal discounting) and recompute. Does + removing the impatience wedge change the near-zero RSI result? +3. Explain *theoretically* why moral hazard drives RSI toward zero. + +*Hint*: From the Euler equation, the spread in marginal utilities +$u'(c_1') / u'(c_2')$ depends on the IC multiplier $\mu$. A larger $\mu$ +spreads continuation values but *not necessarily* repayments. +```` + +````{dropdown} Solution to Exercise 3 +:class: dropdown + +```{code-cell} ipython3 +# RSI for the baseline MH model +d1_mh = Y1 - pol_n1p +d2_mh = Y2 - pol_n2p +RSI = (d2_mh - d1_mh) / (Y2 - Y1) + +print(f"Baseline MH: mean RSI = {np.mean(RSI):.4f}, max RSI = {np.max(RSI):.4f}") +print() +print("Theoretical explanation:") +print(" Under moral hazard the planner must spread V(n2') - V(n1') to") +print(" incentivise investment via the FOA. This is achieved by setting") +print(" n2' > n1', i.e. spreading *net worth*, not repayments.") +print(" Since d_j = Y_j - n_j', if n2' - n1' = Y2 - Y1 then d1 = d2 (RSI=0).") +print(" Moral hazard forces this near-equality, making debt non-contingent.") +``` +```` + +## References + +```{bibliography} +:filter: docname in docnames +``` From 519425c854e6e82a1eb235c3fef4286d63df180b Mon Sep 17 00:00:00 2001 From: thomassargent30 Date: Thu, 21 May 2026 11:49:33 +0200 Subject: [PATCH 06/25] Tom's second May 21 edits --- lectures/_static/quant-econ.bib | 178 ++++++++++++++++++++++++++++++++ 1 file changed, 178 insertions(+) diff --git a/lectures/_static/quant-econ.bib b/lectures/_static/quant-econ.bib index 043a866b..717e008c 100644 --- a/lectures/_static/quant-econ.bib +++ b/lectures/_static/quant-econ.bib @@ -2907,3 +2907,181 @@ @article{szoke2022estimating year = {2022}, doi = {10.1016/j.jet.2021.105225} } + +@article{Atkeson1991, + author = {Andrew Atkeson}, + title = {International Lending with Moral Hazard and Risk of Repudiation}, + journal = {Econometrica}, + year = {1991}, + volume = {59}, + number = {4}, + pages = {1069--1089} +} + +@article{APS1986, + author = {Dilip Abreu and David Pearce and Ennio Stacchetti}, + title = {Optimal Cartel Equilibria with Imperfect Monitoring}, + journal = {Journal of Economic Theory}, + year = {1986}, + volume = {39}, + number = {1}, + pages = {251--269} +} + +@article{BulowRogoff1989a, + author = {Jeremy Bulow and Kenneth Rogoff}, + title = {A Constant Recontracting Model of Sovereign Debt}, + journal = {Journal of Political Economy}, + year = {1989}, + volume = {97}, + number = {1}, + pages = {155--178} +} + +@article{BulowRogoff1989b, + author = {Jeremy Bulow and Kenneth Rogoff}, + title = {Sovereign Debt: Is to Forgive to Forget?}, + journal = {American Economic Review}, + year = {1989}, + volume = {79}, + number = {1}, + pages = {43--50} +} + +@article{GrossmanVanHuyck1988, + author = {Herschel I. Grossman and John B. Van Huyck}, + title = {Sovereign Debt as a Contingent Claim: Excusable Default, Repudiation, and Reputation}, + journal = {American Economic Review}, + year = {1988}, + volume = {78}, + number = {5}, + pages = {1088--1097} +} + +@article{GrossmanHart1983, + author = {Sanford J. Grossman and Oliver D. Hart}, + title = {An Analysis of the Principal-Agent Problem}, + journal = {Econometrica}, + year = {1983}, + volume = {51}, + number = {1}, + pages = {7--45} +} + +@article{Rogerson1985, + author = {William P. Rogerson}, + title = {Repeated Moral Hazard}, + journal = {Econometrica}, + year = {1985}, + volume = {53}, + number = {1}, + pages = {69--76} +} + +@article{FudenbergHolmstromMilgrom1990, + author = {Drew Fudenberg and Bengt Holmstrom and Paul Milgrom}, + title = {Short-Term Contracts and Long-Term Agency Relationships}, + journal = {Journal of Economic Theory}, + year = {1990}, + volume = {51}, + number = {1}, + pages = {1--31} +} + +@article{EichengrehenPortes1986, + author = {Barry Eichengreen and Richard Portes}, + title = {Debt and Default in the 1930s: Causes and Consequences}, + journal = {European Economic Review}, + year = {1986}, + volume = {30}, + number = {3}, + pages = {599--640} +} + +@incollection{LindertMorton1989, + author = {Peter H. Lindert and Peter J. Morton}, + title = {How Sovereign Debt Has Worked}, + booktitle = {Developing Country Debt and Economic Performance, Vol.\ 1}, + editor = {Jeffrey D. Sachs}, + publisher = {University of Chicago Press}, + year = {1989}, + pages = {39--106} +} + +@article{Tsyrennikov2013, + author = {Viktor Tsyrennikov}, + title = {Capital flows under moral hazard}, + journal = {Journal of Monetary Economics}, + year = {2013}, + volume = {60}, + pages = {92--108} +} + +@article{GertlerRogoff1990, + author = {Mark Gertler and Kenneth Rogoff}, + title = {North-South lending and endogenous domestic capital market inefficiencies}, + journal = {Journal of Monetary Economics}, + year = {1990}, + volume = {26}, + pages = {245--266} +} + +@article{AtkesonLucas1992, + author = {Andrew Atkeson and Robert E. Lucas}, + title = {On Efficient Distribution with Private Information}, + journal = {Review of Economic Studies}, + year = {1992}, + volume = {59}, + number = {3}, + pages = {427--453} +} + +@article{ThomasWorrall1990, + author = {Jonathan Thomas and Tim Worrall}, + title = {Income fluctuation and asymmetric information}, + journal = {Journal of Economic Theory}, + year = {1990}, + volume = {51}, + number = {2}, + pages = {367--390} +} + +@article{EatonGersowitz1981, + author = {Jonathan Eaton and Mark Gersovitz}, + title = {Debt with Potential Repudiation: Theoretical and Empirical Analysis}, + journal = {Review of Economic Studies}, + year = {1981}, + volume = {48}, + number = {2}, + pages = {289--309} +} + +@article{NeuemeyerPerri2005, + author = {Andr{\'e}s Neumeyer and Fabrizio Perri}, + title = {Business cycles in emerging economies: the role of interest rates}, + journal = {Journal of Monetary Economics}, + year = {2005}, + volume = {52}, + number = {2}, + pages = {345--380} +} + +@article{AguiarGopinath2006, + author = {Mark Aguiar and Gita Gopinath}, + title = {Defaultable Debt, Interest Rates and the Current Account}, + journal = {Journal of International Economics}, + year = {2006}, + volume = {69}, + number = {1}, + pages = {64--83} +} + +@article{AguiarGopinath2007, + author = {Mark Aguiar and Gita Gopinath}, + title = {Emerging Market Business Cycles: The Cycle Is the Trend}, + journal = {Journal of Political Economy}, + year = {2007}, + volume = {115}, + number = {1}, + pages = {69--102} +} From 6ff143d89b6614835bcebbd96692eda9a408404c Mon Sep 17 00:00:00 2001 From: thomassargent30 Date: Fri, 22 May 2026 16:34:41 -0400 Subject: [PATCH 07/25] Tom's May 22 edits of toc and two lectures --- lectures/_toc.yml | 6 +-- lectures/atkeson_1991.md | 89 ++++++++++++++++++++++++------------ lectures/tsyrennikov_2013.md | 73 ++++++++++++++++++++--------- 3 files changed, 113 insertions(+), 55 deletions(-) diff --git a/lectures/_toc.yml b/lectures/_toc.yml index 8b47631b..3d7c0f8d 100644 --- a/lectures/_toc.yml +++ b/lectures/_toc.yml @@ -71,9 +71,6 @@ parts: - caption: Dynamic Programming Squared numbered: true chapters: - - file: repeat_mh - - file: atkeson_1991 - - file: tsyrennikov_2013 - file: un_insure - file: dyn_stack - file: calvo_machine_learn @@ -85,6 +82,9 @@ parts: - file: amss3 - file: chang_ramsey - file: chang_credible + - file: repeat_mh + - file: atkeson_1991 + - file: tsyrennikov_2013 - file: dovis_accounting_mf - caption: Other numbered: true diff --git a/lectures/atkeson_1991.md b/lectures/atkeson_1991.md index 35960023..147c3c93 100644 --- a/lectures/atkeson_1991.md +++ b/lectures/atkeson_1991.md @@ -18,7 +18,7 @@ kernelspec: ``` -# International Lending with Moral Hazard and Risk of Repudiation +# Lending with Moral Hazard and Risk of Repudiation ## Overview @@ -31,10 +31,10 @@ sovereign borrower subject to two frictions: 2. **Risk of repudiation** — as a sovereign, the borrower can unilaterally renounce its debt at any time. -The central result is striking: a debt-crisis pattern — in which a borrowing -country must **export capital** after suffering an adverse output shock — arises -as a necessary feature of the *constrained optimal* contract, not as a simple -market failure. Capital outflows after bad output realizations are the +A central result is tha, under an optimal contract, a ''sudden stop'' or ''debt-crisis'' emerges in which a borrowing +country must **export capital** after suffering an adverse output shock. + +Outflows of capital after bad output realizations are the mechanism that incentivizes investment. The model extends recursive techniques from {cite}`APS1986`, {cite}`APS1990`, @@ -50,8 +50,12 @@ QuantEcon convention and write $\beta$ throughout. ### Technology -Time is discrete, $t = 0, 1, 2, \ldots$. In each period the borrower chooses -investment $I_t \geq 0$. Given investment $I_t$, next period's output +Time is discrete, $t = 0, 1, 2, \ldots$. + +In each period the borrower chooses +investment $I_t \geq 0$. + +Given investment $I_t$, next period's output $Y_{t+1}$ is drawn from the conditional distribution $$ @@ -59,11 +63,15 @@ g(Y';\,I) \;=\; \lambda(I)\,g_0(Y') + \bigl[1 - \lambda(I)\bigr]\,g_1(Y'), $$ where $Y' \in \mathcal{Y} = \{Y_1, \ldots, Y_N\}$ with $Y_N \geq \cdots \geq -Y_1 > 0$. The weight $\lambda : [0,I_{\max}] \to [0,1]$ is strictly +Y_1 > 0$. + +The weight $\lambda : [0,I_{\max}] \to [0,1]$ is strictly increasing, so higher investment shifts the distribution toward higher outputs. The ratio $g_0(Y_i')/g_1(Y_i')$ is assumed to be **monotone increasing in -$i$** (monotone likelihood ratio property). This means low output is a +$i$** (monotone likelihood ratio property). + +This means low output is a relatively strong signal that the borrower invested little. ### Agents and Preferences @@ -80,7 +88,7 @@ where $u$ is strictly concave with $u'(0) = +\infty$. **Lenders** are a sequence of short-lived, risk-neutral agents, one born each period. The lender born at $t$ extends loan $b_t$ when young and collects -state-contingent repayment $d_{t+1}(Y_{t+1})$ when old. The lender's +state-contingent repayment $d_{t+1}(Y_{t+1})$ when old. A lender's participation (zero-profit) constraint is $$ @@ -96,7 +104,9 @@ Q_t \;\equiv\; Y_t - d_t(Y_t) $$ as **output net of repayment** — the resources available to the borrower -after settling the old lender's claim. An allocation +after settling the old lender's claim. + +An allocation $\sigma = \{c_t(Q^t),\,I_t(Q^t),\,b_t(Q^t),\,d_{t+1}(Y_{t+1};Q^t)\}$ is **feasible** if for all $t$ and histories: @@ -128,8 +138,9 @@ given the loan and repayment schedule. ### Risk of Repudiation If the borrower repudiates its debt after $Y_{t+1}$ is realized, future -lenders refuse credit and the borrower is confined to autarky. An allocation -is **immune from repudiation** if, for every output realization $Y_{t+1}$, +lenders refuse credit and the borrower is confined to autarky. + +An allocation is **immune from repudiation** if, for every output realization $Y_{t+1}$, $$ U^B\bigl(\sigma\,\big|\,_{Q^t;\,Y_{t+1}}\bigr) @@ -155,13 +166,21 @@ payoff $U^B(\sigma)$ subject to: ### Self-Generation and Factorization Let $V(Q)$ be the set of payoffs the borrower can achieve from allocations -satisfying constraints (1)–(4) when the state is $Q$. Atkeson adapts the +satisfying constraints (1)–(4) when the state is $Q$. + +Atkeson adapts the **self-generation** and **factorization** results of {cite}`APS1990` to this -setting with a physical state variable. Define a pair $(A, U)$ of current +setting with a physical state variable. + +Define a pair $(A, U)$ of current controls and a continuation value function to be *admissible with respect to* $W$ at $Q$ if it satisfies one-period versions of constraints (1)–(4) and -$U(Q') \in W(Q')$ for all $Q'$. Let $B(W)(Q)$ be the set of payoffs -generated by admissible pairs. Then: +$U(Q') \in W(Q')$ for all $Q'$. + +Let $B(W)(Q)$ be the set of payoffs +generated by admissible pairs. + +Then: - **Proposition 1 (Self-generation):** If $W$ is self-generating ($W(Q) \subseteq B(W)(Q)$ for all $Q$), then $B(W)(Q) \subseteq V(Q)$. @@ -181,7 +200,9 @@ $$ $$ subject to feasibility, lender participation, no-repudiation, and incentive -compatibility. Moreover, the optimal *continuation* value function equals +compatibility. + +Moreover, the optimal *continuation* value function equals $\bar{V}$ itself. This mirrors Bellman's principle: the **continuation of the optimal contract @@ -198,10 +219,16 @@ $$ $$ The ratio $g_1(Y_i';I)/g(Y_i';I)$ measures the likelihood that output $Y_i'$ -signals *low* investment. By the monotone likelihood ratio property, this -ratio is largest for the **lowest output states**. When $\mu_3(Y_i') > 0$, +signals *low* investment. + +By the monotone likelihood ratio property, this +ratio is largest for the **lowest output states**. + +When $\mu_3(Y_i') > 0$, the no-repudiation constraint binds: $\bar{V}(Y_i' - d'(Y_i')) = -U^B_{\text{aut}}(Y_i')$. Repayment $d'(Y_i')$ is then at its maximum and the +U^B_{\text{aut}}(Y_i')$. + +Repayment $d'(Y_i')$ is then at its maximum and the new loan available at the continuation state is limited, producing a net **capital outflow**: @@ -263,7 +290,9 @@ print(f"Y_L signals low investment with ratio {g_l[0]/g_h[0]:.1f}x") ### Autarky Value Function -In autarky the borrower has no access to credit. Starting each period with +In autarky the borrower has no access to credit. + +Starting each period with resources $Q$, the borrower solves $$ @@ -305,7 +334,9 @@ V_aut = autarky_vfi() ### Constrained Pareto Optimal Contract -We solve Program P* iteratively. At each state $Q$, the planner chooses +We solve Program P* iteratively. + +At each state $Q$, the planner chooses continuation states $(Q'_L, Q'_H)$ — equivalently, state-contingent repayments $d_j = Y_j - Q'_j$ — to maximise the borrower's payoff. @@ -316,7 +347,9 @@ $$ b^* \;=\; \beta\bigl[g_{h,L}(Y_L - Q'_L) + g_{h,H}(Y_H - Q'_H)\bigr], $$ -and current consumption is $c^* = Q + b^* - I_h$. The optimisation reduces +and current consumption is $c^* = Q + b^* - I_h$. + +The optimisation reduces to a two-dimensional problem in $(Q'_L, Q'_H)$: $$ @@ -874,15 +907,11 @@ plt.show() ``` Tightening the no-repudiation threshold ($\varepsilon > 0$) shrinks the set of -feasible contracts, reducing $\bar{V}(Q)$. Debt forgiveness improves the +feasible contracts, reducing $\bar{V}(Q)$. +Debt forgiveness improves the borrower's outside option but makes lenders less willing to extend credit (smaller loans at higher cost), leaving the borrower worse off in equilibrium. This illustrates the {cite}`BulowRogoff1989b` result that debt forgiveness need not benefit the borrowing country. ```` -## References - -```{bibliography} -:filter: docname in docnames -``` diff --git a/lectures/tsyrennikov_2013.md b/lectures/tsyrennikov_2013.md index d60ca12f..aa699941 100644 --- a/lectures/tsyrennikov_2013.md +++ b/lectures/tsyrennikov_2013.md @@ -38,8 +38,10 @@ key empirical regularities of emerging market economies**: high and volatile interest rate spreads, limited consumption risk-sharing, and crisis-like dynamics in which capital inflows suddenly stop. -The key mechanism: moral hazard severely restricts *state contingency* in -repayment schedules. In the language of {cite}`Atkeson1991`, the optimal +The key mechanism is that moral hazard severely restricts *state contingency* in +repayment schedules. + +In the language of {cite}`Atkeson1991`, the optimal contract is nearly *non-contingent* on output — a theoretical justification for why simple debt contracts dominate in practice. @@ -54,6 +56,7 @@ for the borrower and $\beta_c$ for the lender). ### Technology and Preferences The environment is a small open economy with an infinitely-lived borrower. + The borrower starts each period with net worth $n$ (output net of debt repayment), borrows $b$ from a short-lived risk-neutral lender, invests $I$, and consumes @@ -71,6 +74,7 @@ $$ where $\lambda : \mathbb{R}_+ \to [0,1]$ is strictly increasing and strictly concave, so higher investment stochastically dominates lower investment. + Tsyrennikov restricts to two output states and sets $$ @@ -80,6 +84,7 @@ $$ so $g_{0,1}=1,\;g_{0,2}=0,\;g_{1,1}=0,\;g_{1,2}=1$ and $\Delta g_j \equiv g_{1j} - g_{0j} = (-1, 1)$. + The functional form $\lambda(I) = \min(I^\nu, 1)$ with $\nu \in (0,1)$ is strictly concave and gives an interior optimum. @@ -125,7 +130,10 @@ Note that the continuation values depend only on $Y_1$ and $Y_2$, not on $n$. ### The Recursive Contract -The state variable is net worth $n$. The value function satisfies the Bellman +The state variable is net worth $n$. + + +The value function satisfies the Bellman equation $$ @@ -145,10 +153,14 @@ $$ -\theta\,u'(c) + \beta\,\lambda'(I)\,\sum_j \Delta g_j\,V(Y_j-d_j) \geq 0 $$ -does **not** alter the solution. The key step is showing that at any feasible +does **not** alter the solution. + +The key step is showing that at any feasible contract, $\sum_j \Delta g_j\,V(Y_j-d_j) \geq 0$, which ensures the borrower's objective is strictly concave in $I$ and the FOC holds with -equality. This result (analogous to {cite}`Rogerson1985`) validates the +equality. + +This result (analogous to {cite}`Rogerson1985`) validates the relaxed formulation used in the numerical solution. With the FOA, the optimality condition for investments is @@ -158,7 +170,9 @@ $$ \tag{FOA} $$ -where $n_j' = Y_j - d_j$ is next period's net worth after state $j$. A +where $n_j' = Y_j - d_j$ is next period's net worth after state $j$. + +A higher spread $V(n_2') - V(n_1')$ — more reward in the high state — supports a higher investment level. @@ -175,8 +189,12 @@ where $\mu \geq 0$ is the multiplier on the FOA constraint and $\phi \geq 0$ on the lender endowment $b \leq M$. Because $\Delta g_1 = -1 < 0$, the factor for the low state is less than one: -$V'(n_1') > V'(n)$. By concavity of $V$, the borrower's net worth falls in -the low state. This is the **immiseration** property: moral hazard forces +$V'(n_1') > V'(n)$. + +By concavity of $V$, the borrower's net worth falls in +the low state. + +This is the **immiseration** property: moral hazard forces the borrower to bear more risk than would be optimal with full information (cf.\ {cite}`ThomasWorrall1990`, {cite}`AtkesonLucas1992`). @@ -187,6 +205,8 @@ R(n) \;=\; \frac{u'(c(n))}{\beta\,\sum_j g(Y_j\mid I(n))\,u'(c(n_j'(n)))}, $$ where $c(n_j'(n))$ is next period's consumption if state $j$ is realised. + + This rate is counter-cyclical: when $n$ is low, past incentive provision has depressed the continuation values, raising the marginal utility spread and increasing $R$. @@ -505,9 +525,12 @@ print("→ Moral hazard justifies why simple non-contingent debt is optimal") ``` A key finding of {cite}`Tsyrennikov2013` emerges immediately: the risk-sharing -index is close to **zero** throughout the state space. Moral hazard requires +index is close to **zero** throughout the state space. + +Moral hazard requires spreading continuation values to incentivise investment, but this is achieved by differentiating *net worth* $n_j'$, not repayment $d_j = Y_j - n_j'$. + The near-equality $d_1 \approx d_2$ means repayment is essentially **non-contingent on output** — the model rationalises why emerging market borrowers use plain debt instruments rather than GDP-linked securities. @@ -704,9 +727,12 @@ Fig. 4: ### MH Versus Limited Enforcement A crucial result of {cite}`Tsyrennikov2013` is that **limited enforcement adds -little** to the model's performance relative to moral hazard alone. The -intuition: under LE (no moral hazard), optimal repayments are *highly state -contingent* (RSI ≈ 0.8), providing near-full insurance. The borrower's net +little** to the model's performance relative to moral hazard alone. + +Under LE (no moral hazard), optimal repayments are *highly state +contingent* (RSI ≈ 0.8), providing near-full insurance. + +The borrower's net worth drifts *upward* under LE (unlike MH where it drifts downward), so interest rate spreads are transitory rather than persistent. @@ -749,16 +775,22 @@ plt.show() Under moral hazard, $\mathbb{E}[n'] < n$: net worth drifts down and the borrower spends substantial time near the borrowing limit, generating -persistent interest rate spreads. Under limited enforcement, +persistent interest rate spreads. + +Under limited enforcement, $\mathbb{E}[n'] \geq (\beta/\beta_c)\,n$: net worth drifts toward a stationary level and the borrower eventually escapes financial stress. ## Empirical Test -{cite}`Tsyrennikov2013` proposes a simple test to distinguish moral hazard from -limited enforcement. After a low past output realisation ($y_{t-1} = Y_1$), +{cite}`Tsyrennikov2013` proposes a test to distinguish moral hazard from +limited enforcement. + +After a low past output realisation ($y_{t-1} = Y_1$), the MH contract lowers net worth sharply, reducing future consumption -smoothing. This prediction is: +smoothing. + +This prediction is: $$ \text{MH economy}: \quad @@ -766,7 +798,9 @@ $$ $$ while the LE economy gives the opposite ordering (insurance is better after -low realisations). Using Argentine quarterly data (1993–2005), the observed +low realisations). + +Using Argentine quarterly data (1993–2005), the observed correlations are 0.98 (after low output) vs. 0.91 (after high output) — **consistent with moral hazard**. @@ -913,8 +947,3 @@ print(" Moral hazard forces this near-equality, making debt non-contingent.") ``` ```` -## References - -```{bibliography} -:filter: docname in docnames -``` From e0bcf82adb03593a3e8edafaf22f9d5f832342eb Mon Sep 17 00:00:00 2001 From: HumphreyYang Date: Wed, 27 May 2026 12:03:40 +1000 Subject: [PATCH 08/25] updates --- lectures/_config.yml | 7 ++ lectures/_static/quant-econ.bib | 193 ++++++++++++++++++++++++++++++++ lectures/atkeson_1991.bib | 100 ----------------- lectures/atkeson_1991.md | 26 +++-- lectures/repeat_mh.md | 14 ++- lectures/tsyrennikov_2013.bib | 77 ------------- lectures/tsyrennikov_2013.md | 22 ++-- 7 files changed, 242 insertions(+), 197 deletions(-) delete mode 100644 lectures/atkeson_1991.bib delete mode 100644 lectures/tsyrennikov_2013.bib diff --git a/lectures/_config.yml b/lectures/_config.yml index 8493f847..5fb7444b 100644 --- a/lectures/_config.yml +++ b/lectures/_config.yml @@ -33,6 +33,13 @@ latex: sphinx: extra_extensions: [sphinx_multitoc_numbering, sphinxext.rediraffe, sphinx_tojupyter, sphinx_exercise, sphinx_togglebutton, sphinx_proof, sphinx.ext.intersphinx] config: + exclude_patterns: + - _build + - _build/** + - .DS_Store + - "**.ipynb_checkpoints" + - lecture-python-programming.myst + - lecture-python-programming.myst/** intersphinx_mapping: intro: - "https://intro.quantecon.org/" diff --git a/lectures/_static/quant-econ.bib b/lectures/_static/quant-econ.bib index 717e008c..5ac915e3 100644 --- a/lectures/_static/quant-econ.bib +++ b/lectures/_static/quant-econ.bib @@ -1373,6 +1373,199 @@ @article{APS1990 pages = {1041-1063} } +@article{APS1986, + author = {Dilip Abreu and David Pearce and Ennio Stacchetti}, + title = {Optimal Cartel Equilibria with Imperfect Monitoring}, + journal = {Journal of Economic Theory}, + volume = {39}, + number = {1}, + pages = {251--269}, + year = {1986}, + month = {June}, + doi = {10.1016/0022-0531(86)90028-1} +} + +@article{AguiarGopinath2006, + author = {Mark Aguiar and Gita Gopinath}, + title = {Defaultable Debt, Interest Rates and the Current Account}, + journal = {Journal of International Economics}, + volume = {69}, + number = {1}, + pages = {64--83}, + year = {2006}, + doi = {10.1016/j.jinteco.2005.05.005} +} + +@article{AguiarGopinath2007, + author = {Mark Aguiar and Gita Gopinath}, + title = {Emerging Market Business Cycles: The Cycle Is the Trend}, + journal = {Journal of Political Economy}, + volume = {115}, + number = {1}, + pages = {69--102}, + year = {2007}, + doi = {10.1086/511283} +} + +@article{Atkeson1991, + author = {Andrew Atkeson}, + title = {International Lending with Moral Hazard and Risk of Repudiation}, + journal = {Econometrica}, + volume = {59}, + number = {4}, + pages = {1069--1089}, + year = {1991}, + doi = {10.2307/2938174} +} + +@article{AtkesonLucas1992, + author = {Andrew Atkeson and Robert E. Lucas, Jr.}, + title = {On Efficient Distribution with Private Information}, + journal = {Review of Economic Studies}, + volume = {59}, + number = {3}, + pages = {427--453}, + year = {1992}, + doi = {10.2307/2297858} +} + +@article{BulowRogoff1989a, + author = {Jeremy Bulow and Kenneth Rogoff}, + title = {A Constant Recontracting Model of Sovereign Debt}, + journal = {Journal of Political Economy}, + volume = {97}, + number = {1}, + pages = {155--178}, + year = {1989}, + doi = {10.1086/261596} +} + +@article{BulowRogoff1989b, + author = {Jeremy Bulow and Kenneth Rogoff}, + title = {Sovereign Debt: Is to Forgive to Forget?}, + journal = {American Economic Review}, + volume = {79}, + number = {1}, + pages = {43--50}, + year = {1989} +} + +@article{EatonGersowitz1981, + author = {Jonathan Eaton and Mark Gersovitz}, + title = {Debt with Potential Repudiation: Theoretical and Empirical Analysis}, + journal = {Review of Economic Studies}, + volume = {48}, + number = {2}, + pages = {289--309}, + year = {1981} +} + +@article{EichengreenPortes1986, + author = {Barry Eichengreen and Richard Portes}, + title = {Debt and Default in the 1930s: Causes and Consequences}, + journal = {European Economic Review}, + volume = {30}, + number = {3}, + pages = {599--640}, + year = {1986} +} + +@article{FudenbergHolmstromMilgrom1990, + author = {Drew Fudenberg and Bengt Holmstrom and Paul Milgrom}, + title = {Short-Term Contracts and Long-Term Agency Relationships}, + journal = {Journal of Economic Theory}, + volume = {51}, + number = {1}, + pages = {1--31}, + year = {1990}, + doi = {10.1016/0022-0531(90)90048-O} +} + +@article{GertlerRogoff1990, + author = {Mark Gertler and Kenneth Rogoff}, + title = {North-South Lending and Endogenous Domestic Capital Market Inefficiencies}, + journal = {Journal of Monetary Economics}, + volume = {26}, + number = {2}, + pages = {245--266}, + year = {1990}, + doi = {10.1016/0304-3932(90)90022-V} +} + +@article{GrossmanHart1983, + author = {Sanford J. Grossman and Oliver D. Hart}, + title = {An Analysis of the Principal-Agent Problem}, + journal = {Econometrica}, + volume = {51}, + number = {1}, + pages = {7--45}, + year = {1983}, + doi = {10.2307/1912246} +} + +@article{GrossmanVanHuyck1988, + author = {Herschel I. Grossman and John B. Van Huyck}, + title = {Sovereign Debt as a Contingent Claim: Excusable Default, Repudiation, and Reputation}, + journal = {American Economic Review}, + volume = {78}, + number = {5}, + pages = {1088--1097}, + year = {1988} +} + +@incollection{LindertMorton1989, + author = {Peter H. Lindert and Peter J. Morton}, + title = {How Sovereign Debt Has Worked}, + booktitle = {Developing Country Debt and Economic Performance, Volume 1: The International Financial System}, + editor = {Jeffrey D. Sachs}, + publisher = {University of Chicago Press}, + pages = {39--106}, + year = {1989} +} + +@article{NeuemeyerPerri2005, + author = {Andr{\'e}s Neumeyer and Fabrizio Perri}, + title = {Business Cycles in Emerging Economies: The Role of Interest Rates}, + journal = {Journal of Monetary Economics}, + volume = {52}, + number = {2}, + pages = {345--380}, + year = {2005}, + doi = {10.1016/j.jmoneco.2004.04.011} +} + +@article{Rogerson1985, + author = {William P. Rogerson}, + title = {The First-Order Approach to Principal-Agent Problems}, + journal = {Econometrica}, + volume = {53}, + number = {6}, + pages = {1357--1367}, + year = {1985}, + doi = {10.2307/1913212} +} + +@article{ThomasWorrall1990, + author = {Jonathan Thomas and Tim Worrall}, + title = {Income Fluctuation and Asymmetric Information: An Example of a Repeated Principal-Agent Problem}, + journal = {Journal of Economic Theory}, + volume = {51}, + number = {2}, + pages = {367--390}, + year = {1990} +} + +@article{Tsyrennikov2013, + author = {Viktor Tsyrennikov}, + title = {Capital Flows Under Moral Hazard}, + journal = {Journal of Monetary Economics}, + volume = {60}, + number = {1}, + pages = {92--108}, + year = {2013}, + doi = {10.1016/j.jmoneco.2012.11.006} +} + @article{HarrisonKreps1979, author={Harrison, J. Michael and Kreps, David M.}, title={Martingales and arbitrage in multiperiod securities markets}, diff --git a/lectures/atkeson_1991.bib b/lectures/atkeson_1991.bib deleted file mode 100644 index 26d1ad6e..00000000 --- a/lectures/atkeson_1991.bib +++ /dev/null @@ -1,100 +0,0 @@ -@article{Atkeson1991, - author = {Andrew Atkeson}, - title = {International Lending with Moral Hazard and Risk of Repudiation}, - journal = {Econometrica}, - year = {1991}, - volume = {59}, - number = {4}, - pages = {1069--1089} -} - -@article{APS1986, - author = {Dilip Abreu and David Pearce and Ennio Stacchetti}, - title = {Optimal Cartel Equilibria with Imperfect Monitoring}, - journal = {Journal of Economic Theory}, - year = {1986}, - volume = {39}, - pages = {251--269} -} - -@article{BulowRogoff1989a, - author = {Jeremy Bulow and Kenneth Rogoff}, - title = {A Constant Recontracting Model of Sovereign Debt}, - journal = {Journal of Political Economy}, - year = {1989}, - volume = {97}, - number = {1}, - pages = {155--178} -} - -@article{BulowRogoff1989b, - author = {Jeremy Bulow and Kenneth Rogoff}, - title = {Sovereign Debt: Is to Forgive to Forget?}, - journal = {American Economic Review}, - year = {1989}, - volume = {79}, - number = {1}, - pages = {43--50} -} - -@article{GrossmanVanHuyck1988, - author = {Herschel I. Grossman and John B. Van Huyck}, - title = {Sovereign Debt as a Contingent Claim: Excusable Default, - Repudiation, and Reputation}, - journal = {American Economic Review}, - year = {1988}, - volume = {78}, - number = {5}, - pages = {1088--1097} -} - -@article{GrossmanHart1983, - author = {Sanford J. Grossman and Oliver D. Hart}, - title = {An Analysis of the Principal-Agent Problem}, - journal = {Econometrica}, - year = {1983}, - volume = {51}, - number = {1}, - pages = {7--45} -} - -@article{Rogerson1985, - author = {William P. Rogerson}, - title = {The First-Order Approach to Principal-Agent Problems}, - journal = {Econometrica}, - year = {1985}, - volume = {53}, - number = {6}, - pages = {1357--1367} -} - -@article{FudenbergHolmstromMilgrom1990, - author = {Drew Fudenberg and Bengt Holmstrom and Paul Milgrom}, - title = {Short-Term Contracts and Long-Term Agency Relationships}, - journal = {Journal of Economic Theory}, - year = {1990}, - volume = {51}, - number = {1}, - pages = {1--31} -} - -@article{EichengrehenPortes1986, - author = {Barry Eichengreen and Richard Portes}, - title = {Debt and Default in the 1930s: Causes and Consequences}, - journal = {European Economic Review}, - year = {1986}, - volume = {30}, - number = {3}, - pages = {599--640} -} - -@incollection{LindertMorton1989, - author = {Peter H. Lindert and Peter J. Morton}, - title = {How Sovereign Debt Has Worked}, - booktitle = {Developing Country Debt and Economic Performance, - Volume 1: The International Financial System}, - editor = {Jeffrey D. Sachs}, - publisher = {University of Chicago Press}, - year = {1989}, - pages = {39--106} -} diff --git a/lectures/atkeson_1991.md b/lectures/atkeson_1991.md index 147c3c93..df107401 100644 --- a/lectures/atkeson_1991.md +++ b/lectures/atkeson_1991.md @@ -9,7 +9,6 @@ kernelspec: name: python3 --- -(atkeson_1991)= ```{raw} html ``` -# Lending with Moral Hazard and Risk of Repudiation +(atkeson_1991)= +# International Lending with Moral Hazard and Risk of Repudiation ## Overview @@ -577,7 +577,7 @@ axes[1].plot(Q_grid, net_out_L, lw=2, label=r'After $Y_L$ (low output)') axes[1].plot(Q_grid, net_out_H, lw=2, ls='--', label=r'After $Y_H$ (high output)') axes[1].axhline(0, color='k', lw=0.8, ls=':') axes[1].set_xlabel(r'State $Q$') -axes[1].set_ylabel(r'Net outflow $d(Y') - b'(Q')$') +axes[1].set_ylabel(r"Net outflow $d(Y') - b'(Q')$") axes[1].set_title('Capital Flows in the Optimal Contract') axes[1].legend() @@ -688,8 +688,9 @@ parameters fixed). no-repudiation constraint binds after low output? ```` -````{dropdown} Solution to Exercise 1 +```{solution-start} atkeson_1991_ex1 :class: dropdown +``` ```{code-cell} ipython3 fig, ax = plt.subplots(figsize=(8, 5)) @@ -728,7 +729,9 @@ contract more highly, which relaxes the no-repudiation constraint: the no-repudiation floor $Q^*_L$ falls and the capital outflow after low output is less severe. Impatient borrowers more readily prefer autarky, tightening the constraint and worsening debt-crisis dynamics. -```` + +```{solution-end} +``` ````{admonition} Exercise 2 :class: exercise @@ -746,8 +749,9 @@ weaker signal of investment. pattern. ```` -````{dropdown} Solution to Exercise 2 +```{solution-start} atkeson_1991_ex2 :class: dropdown +``` ```{code-cell} ipython3 g_h_base, g_l_base = g_h.copy(), g_l.copy() @@ -792,7 +796,9 @@ about past investment. The moral hazard problem is milder, incentive constraints are easier to satisfy, and the no-repudiation constraint binds less tightly. Capital outflows after bad output realisations are smaller in magnitude. -```` + +```{solution-end} +``` ````{admonition} Exercise 3 :class: exercise @@ -814,8 +820,9 @@ $\varepsilon > 0$. `pareto_bellman`. ```` -````{dropdown} Solution to Exercise 3 +```{solution-start} atkeson_1991_ex3 :class: dropdown +``` ```{code-cell} ipython3 fig, ax = plt.subplots(figsize=(8, 5)) @@ -913,5 +920,6 @@ borrower's outside option but makes lenders less willing to extend credit (smaller loans at higher cost), leaving the borrower worse off in equilibrium. This illustrates the {cite}`BulowRogoff1989b` result that debt forgiveness need not benefit the borrowing country. -```` +```{solution-end} +``` diff --git a/lectures/repeat_mh.md b/lectures/repeat_mh.md index c096cb0a..bfd58331 100644 --- a/lectures/repeat_mh.md +++ b/lectures/repeat_mh.md @@ -1913,8 +1913,9 @@ $$ promised utility. ```` -````{dropdown} Solution to Exercise 1 +```{solution-start} repeat_mh_ex1 :class: dropdown +``` ```{code-cell} ipython3 delta_W = s_W_full - s_W_unobs @@ -1940,7 +1941,9 @@ consumption-state variation to be incentivized. At low $w$ the agent is near subsistence and effort is low anyway; at high $w$ the agent is nearly fully insured and the marginal incentive cost of each additional unit of effort is small. -```` + +```{solution-end} +``` ````{admonition} Exercise 2 :class: exercise @@ -1968,8 +1971,9 @@ in which output is less informative about effort than in the baseline $P$. 3. Explain the economic intuition for any differences you find. ```` -````{dropdown} Solution to Exercise 2 +```{solution-start} repeat_mh_ex2 :class: dropdown +``` ```{code-cell} ipython3 P_flat = np.array([[0.70, 0.30], @@ -2016,4 +2020,6 @@ offered to deter deviations, crowding out insurance. As a result the principal extracts less surplus and induces less effort than under the baseline $P$ -- the surplus function shifts down and expected effort falls. -```` + +```{solution-end} +``` diff --git a/lectures/tsyrennikov_2013.bib b/lectures/tsyrennikov_2013.bib deleted file mode 100644 index 946aee53..00000000 --- a/lectures/tsyrennikov_2013.bib +++ /dev/null @@ -1,77 +0,0 @@ -@article{Tsyrennikov2013, - author = {Viktor Tsyrennikov}, - title = {Capital flows under moral hazard}, - journal = {Journal of Monetary Economics}, - year = {2013}, - volume = {60}, - pages = {92--108} -} - -@article{GertlerRogoff1990, - author = {Mark Gertler and Kenneth Rogoff}, - title = {North-South lending and endogenous domestic capital market inefficiencies}, - journal = {Journal of Monetary Economics}, - year = {1990}, - volume = {26}, - pages = {245--266} -} - -@article{AtkesonLucas1992, - author = {Andrew Atkeson and Robert E. Lucas}, - title = {On Efficient Distribution with Private Information}, - journal = {Review of Economic Studies}, - year = {1992}, - volume = {59}, - number = {3}, - pages = {427--453} -} - -@article{ThomasWorrall1990, - author = {Jonathan Thomas and Tim Worrall}, - title = {Income fluctuation and asymmetric information}, - journal = {Journal of Economic Theory}, - year = {1990}, - volume = {51}, - number = {2}, - pages = {367--390} -} - -@article{EatonGersowitz1981, - author = {Jonathan Eaton and Mark Gersovitz}, - title = {Debt with Potential Repudiation: Theoretical and Empirical Analysis}, - journal = {Review of Economic Studies}, - year = {1981}, - volume = {48}, - number = {2}, - pages = {289--309} -} - -@article{NeuemeyerPerri2005, - author = {Andr{\'e}s Neumeyer and Fabrizio Perri}, - title = {Business cycles in emerging economies: the role of interest rates}, - journal = {Journal of Monetary Economics}, - year = {2005}, - volume = {52}, - number = {2}, - pages = {345--380} -} - -@article{AguiarGopinath2006, - author = {Mark Aguiar and Gita Gopinath}, - title = {Defaultable Debt, Interest Rates and the Current Account}, - journal = {Journal of International Economics}, - year = {2006}, - volume = {69}, - number = {1}, - pages = {64--83} -} - -@article{AguiarGopinath2007, - author = {Mark Aguiar and Gita Gopinath}, - title = {Emerging Market Business Cycles: The Cycle Is the Trend}, - journal = {Journal of Political Economy}, - year = {2007}, - volume = {115}, - number = {1}, - pages = {69--102} -} diff --git a/lectures/tsyrennikov_2013.md b/lectures/tsyrennikov_2013.md index aa699941..91104ed4 100644 --- a/lectures/tsyrennikov_2013.md +++ b/lectures/tsyrennikov_2013.md @@ -9,7 +9,6 @@ kernelspec: name: python3 --- -(tsyrennikov_2013)= ```{raw} html ``` +(tsyrennikov_2013)= # Capital Flows Under Moral Hazard ## Overview @@ -821,8 +821,9 @@ the severity of the output loss upon default. at $\delta \to 0$ it is vacuous. ```` -````{dropdown} Solution to Exercise 1 +```{solution-start} tsyrennikov_2013_ex1 :class: dropdown +``` ```{code-cell} ipython3 fig, ax = plt.subplots(figsize=(8, 5)) @@ -855,7 +856,9 @@ state-contingent repayment. Paradoxically, this may *reduce* the interest rate spread by forcing the lender to offer more consumption insurance to keep the borrower from defaulting. At $\delta \to 0$ the enforcement constraint is vacuous and the model collapses to pure moral hazard. -```` + +```{solution-end} +``` ````{admonition} Exercise 2 :class: exercise @@ -874,8 +877,9 @@ hazard (immiseration). When $\beta < \beta_c$ there is an additional front-loading incentive that the lender can exploit. ```` -````{dropdown} Solution to Exercise 2 +```{solution-start} tsyrennikov_2013_ex2 :class: dropdown +``` ```{code-cell} ipython3 fig, ax = plt.subplots(figsize=(8, 5)) @@ -906,7 +910,9 @@ immiseration, while impatience accelerates it further. A small wedge (as calibrated by Tsyrennikov) is significant: it is *equivalent to increasing the borrower's discount rate by 2% per annum* (even though the assumed difference in quarterly rates is only 0.010). -```` + +```{solution-end} +``` ````{admonition} Exercise 3 :class: exercise @@ -927,8 +933,9 @@ $u'(c_1') / u'(c_2')$ depends on the IC multiplier $\mu$. A larger $\mu$ spreads continuation values but *not necessarily* repayments. ```` -````{dropdown} Solution to Exercise 3 +```{solution-start} tsyrennikov_2013_ex3 :class: dropdown +``` ```{code-cell} ipython3 # RSI for the baseline MH model @@ -945,5 +952,6 @@ print(" n2' > n1', i.e. spreading *net worth*, not repayments.") print(" Since d_j = Y_j - n_j', if n2' - n1' = Y2 - Y1 then d1 = d2 (RSI=0).") print(" Moral hazard forces this near-equality, making debt non-contingent.") ``` -```` +```{solution-end} +``` From d81c90aaae19b179db4926d26df9590ebeb20a9e Mon Sep 17 00:00:00 2001 From: HumphreyYang Date: Wed, 27 May 2026 20:10:27 +1000 Subject: [PATCH 09/25] updates --- lectures/repeat_mh.md | 1317 +++++++++++++++++++++-------------------- 1 file changed, 678 insertions(+), 639 deletions(-) diff --git a/lectures/repeat_mh.md b/lectures/repeat_mh.md index bfd58331..c52ddb34 100644 --- a/lectures/repeat_mh.md +++ b/lectures/repeat_mh.md @@ -11,14 +11,15 @@ kernelspec: language: python --- -# Repeated Moral Hazard +# Repeated moral hazard ## Overview -This lecture computes the information-constrained optima studied by -{cite:t}`Phelan_Townsend_91`. +This lecture computes information-constrained optima in the +Phelan-Townsend repeated moral-hazard environment +{cite}`Phelan_Townsend_91`. -Their paper studies a continuum-agent economy with unobserved effort. +The environment is a continuum-agent economy with unobserved effort. The planner chooses lotteries over individual histories, subject to promise-keeping and incentive-compatibility constraints, and maximizes @@ -31,19 +32,20 @@ Phelan and Townsend combine that idea with lotteries, finite grids, and linear programming to compute full-information, static unobserved-action, and repeated unobserved-action allocations. -We proceed as follows. +The lecture proceeds from the recursive formulation to the computational +implementation. * We review the promised-utility recursion of {cite:t}`Spear_Srivastava_87`. * We formulate the Phelan-Townsend lottery problem and its finite-grid linear-programming approximation. -* We solve the *static* version of the economy and - replicate Figures 1--4 of {cite:t}`Phelan_Townsend_91`. -* We solve the *repeated* economy and replicate - Figures 5--12 of {cite:t}`Phelan_Townsend_91`. +* We use the static economy to isolate the surplus cost of hidden + effort and the role of output-contingent consumption. +* We use the repeated economy to show how continuation promises become + an additional incentive instrument and generate dispersion over time. -## Promised-utility Recursion +## Promised-utility recursion {cite:t}`Spear_Srivastava_87` showed how to write an infinitely repeated, discounted principal-agent problem recursively. @@ -99,9 +101,10 @@ Equation {eq}`eq:eq2` is the **incentive-compatibility** constraint: the agent must prefer the recommended action $a(w)$ over any deviation $\hat a$. -The principal's value function $v(w)$ -- the maximum expected -discounted surplus attainable when the agent has been promised $w$ -- -satisfies the Bellman equation +The principal's value function $v(w)$ is the maximum expected +discounted surplus attainable when the agent has been promised $w$. + +It satisfies the Bellman equation $$ v(w) = \max_{a,\,c,\,\tilde{w}}\ @@ -113,28 +116,84 @@ subject to the promise-keeping constraint {eq}`eq:eq1` and the incentive-compatibility constraint {eq}`eq:eq2`. -## Phelan and Townsend (1991): Lotteries and Linear Programming +## Lotteries and linear programming A technical difficulty in problems like {eq}`eq:eq3` is that incentive constraints can make deterministic contract problems non-convex. -{cite}`Phelan_Townsend_91` instead formulate the planning problem in +```{prf:example} A non-convex deterministic contract set +:label: repeat_mh_nonconvex_example +:class: dropdown + +Consider a one-period version of the problem with two outputs, +$q_H$ and $q_L$, and two actions, high effort $H$ and low effort $L$. +Suppose that + +$$ +P(q_H \mid H)=3/4, +\qquad +P(q_H \mid L)=1/4, +$$ + +and that the agent's utility is + +$$ +u(c,H)=\sqrt c - 1/2, +\qquad +u(c,L)=\sqrt c. +$$ + +A deterministic contract that recommends high effort pays $c_H$ after +$q_H$ and $c_L$ after $q_L$. +Incentive compatibility requires + +$$ +\frac34 \sqrt{c_H}+\frac14\sqrt{c_L}-\frac12 +\geq +\frac14 \sqrt{c_H}+\frac34\sqrt{c_L}, +$$ + +or + +$$ +\sqrt{c_H}-\sqrt{c_L}\geq 1. +$$ + +The two contracts $(c_H,c_L)=(1,0)$ and $(c_H,c_L)=(9,4)$ both satisfy +this constraint. +But their midpoint, $(c_H,c_L)=(5,2)$, violates it because + +$$ +\sqrt 5-\sqrt 2 \approx 0.82 < 1. +$$ + +Thus the set of deterministic contracts satisfying incentive +compatibility is not convex. +``` + +The Phelan-Townsend approach formulates the planning problem in terms of **lotteries** over actions, outputs, consumptions, and continuation utilities. At the aggregate level these probabilities are also population fractions, so individual randomization creates no -aggregate uncertainty in their continuum-agent economy. +aggregate uncertainty in a continuum-agent economy. + +For computation, we "grid" the relevant sets of +possible utilities, allowing only finitely many points. -For computation, all relevant sets are restricted to finite grids. +With finite sets, or finite approximations to sets, $A$, $Q$, and $C$, +the planner's problem becomes a finite-dimensional optimization problem +with linear constraints. -On -those grids the Bellman step is a **linear program**. +Each stage of the computation therefore amounts to solving a finite +**linear programming** problem. -*Setup.* Let $P(q | a)$ be a family of discrete conditional -probability distributions over finite sets $Q$ (outputs) and $A$ -(actions). +We begin with the finite objects in the planning problem. + +Let $P(q | a)$ be a family of discrete conditional probability +distributions over finite sets $Q$ (outputs) and $A$ (actions). Let $C$ and $W'$ be finite grids for current consumption and next-period promised utility. @@ -185,8 +244,8 @@ The ratio $P(q\mid\hat a)/P(q\mid a)$ is the likelihood ratio that updates the probability of outcome $q$ when the agent deviates from the recommended action $a$ to $\hat a$. -*Bellman operator as a linear program.* The principal's value -function satisfies +The corresponding Bellman operator is also a linear program. +The principal's value function satisfies $$ v(w) = \max_{\Pi}\ @@ -205,8 +264,8 @@ Because $v(w')$ on the right side of {eq}`eq:bell2` is treated as a *fixed* vector from the previous iteration, the Bellman operator itself is a linear program. -Phelan and Townsend solve one LP for each grid point $w \in W$ and -iterate on the surplus function. +The algorithm solves one LP for each grid point $w \in W$ and iterates +on the surplus function. Their Theorem 4 gives the contraction result that justifies this iteration for the @@ -217,6 +276,8 @@ infinite-horizon problem. In addition to what's in Anaconda, this lecture will need the following libraries: ```{code-cell} ipython3 +:tags: [hide-output] + !pip install cvxpy ``` @@ -228,18 +289,20 @@ import cvxpy as cp from time import time import gc import matplotlib.pyplot as plt -from warnings import filterwarnings ``` -## The Static Economy +## The static economy -This section replicates Sections II and III of -{cite}`Phelan_Townsend_91`. +A one-period economy is the cleanest place to isolate the +informational friction. + +This isolates the static informational friction before the dynamic +promised-utility channel is introduced. -Section II studies the full-information benchmark. +We first compute the full-information benchmark, where effort can be +controlled directly. -Section III adds -unobserved actions and the resulting incentive constraints. +We then make effort private information and add incentive compatibility. ### Setting @@ -283,8 +346,7 @@ $$ ### Parameterisation -Following {cite}`Phelan_Townsend_91`, we use the period utility -function +The baseline utility specification is $$ U(a, c) = 2\sqrt{c} + 2\sqrt{1-a} @@ -307,11 +369,12 @@ and conditional output probabilities | 0.4 | 0.4 | 0.6 | | 0.6 | 0.25 | 0.75 | -These are the parameter values used to construct Figures 1--8 in the -paper. +These parameter values define the baseline numerical economy for the +static comparisons and the first dynamic calculations. The static grid of promised utility values below spans the -interval $[1,5]$, matching the horizontal scale in Figures 1--4. +interval $[1,5]$, covering the promise range emphasized in the +one-period analysis. ```{code-cell} ipython3 def u(a, c): @@ -326,229 +389,242 @@ P = np.array([[0.9, 0.1], [0.25, 0.75]]) ``` -### Solving the Static Problem +### Solving the static problem + +The function `solve_static_problem` solves one LP for each promised +utility value $w$. -The function `solve_static_problem` solves both the FIP and the -unobserved-action problem for an array of promised utility values $w$. +The code keeps the notation close to the mathematical problem: +`π[a_i][q_i, c_i]` is the lottery probability +$\Pi^w(a_i,q_i,c_i)$, `Φ[q_i, c_i]` is output net of consumption, +and `U[a_i, c_i]` is period utility. -It implements constraints C1--C3 for the full information case and -adds C4 for the unobserved-action case. +For the full-information problem we impose C1--C3. + +For the unobserved-action problem we add C4. ```{code-cell} ipython3 -# Define the function that solves the static problem -def solve_static_problem(W=None, - u=None, - A=None, - Q=None, - C=None, - P=None, - problem_type=None): - ''' - Function: Solve the static problem - - Parameters - ---------- - W: 1-D array - The expected utility. - u: function - The utility function in terms of actions and consumptions. - A: 1-D array - The finite set of possible actions. - Q: 1-D array - The finite set of possible outputs. - C: 1-D array - The finite set of possible consumptions. - P: 2-D array - The probability matrix of outputs given an action. - problem_type: str, "full information" or "unobserved-actions" - The problem type, i.e. the full information problem or the unobserved-action problem. - +def solve_static_problem(W, u, A, Q, C, P, problem_type): + """ + Solve the static Phelan-Townsend LP on a grid of promises W. + Returns ------- - s_W: 1-D array - The optimal values of surplus for each w in w_vec. - Pi: 4-D array - The probability of (a, q, c) given w. - ''' - - # Define parameter - n_A, n_Q, n_C = len(A), len(Q), len(C) - A_ind, Q_ind, C_ind = range(n_A), range(n_Q), range(n_C) - - Phi = np.array([[q-c for c in C] for q in Q]) + s_W : ndarray + Optimal surplus at each w in W. + π_W : ndarray + Lottery probabilities π_W[w_i, a_i, q_i, c_i]. + """ + n_a, n_q, n_c = len(A), len(Q), len(C) + A_i, Q_i = range(n_a), range(n_q) + + Φ = np.array([[q - c for c in C] for q in Q]) U = np.array([[u(a, c) for c in C] for a in A]) - - w = cp.Parameter() - - # Define variable Pi_x - Pi_list = list(np.zeros(n_A)) - - for a_ind in A_ind: - Pi_list[a_ind] = cp.Variable((n_Q, n_C)) - # Define objective function - obj_expr = cp.sum([cp.sum(cp.multiply(Pi_list[a_ind], Phi)) - for a_ind in A_ind]) - obj = cp.Maximize(obj_expr) - - # Define constraints - C1 = [cp.sum([cp.sum([cp.sum(cp.multiply(Pi_list[a_ind][q_ind, :], - U[a_ind, :])) for a_ind in A_ind]) - for q_ind in Q_ind]) == w] - C2 = [(cp.sum(Pi_list[a_ind], axis=1)[q_ind] == P[a_ind, q_ind] * cp.sum(Pi_list[a_ind])) - for a_ind in A_ind for q_ind in Q_ind] - C3 = [cp.sum([cp.sum(Pi_list[a_ind]) for a_ind in A_ind]) == 1] + \ - [(Pi_list[a_ind] >= 0) for a_ind in A_ind] - - problem_type = problem_type.lower() - if problem_type == "full information": - constraints = C1 + C2 + C3 - else: - C4 = [(cp.sum([cp.sum(cp.multiply(Pi_list[a_ind][q_ind, :], U[a_ind, :])) - for q_ind in Q_ind]) >= - cp.sum([cp.sum(cp.multiply(Pi_list[a_ind][q_ind, :], - U[a_ind_hat, :])) * P[a_ind_hat, q_ind]/P[a_ind, q_ind] - for q_ind in Q_ind])) - for a_ind in A_ind for a_ind_hat in A_ind] - constraints = C1 + C2 + C3 + C4 - - # Create the problem - problem = cp.Problem(obj, constraints) - - # Initialize output variables - s_W = np.zeros(len(W)) - Pi = np.zeros((len(W), len(A), len(Q), len(C))) - - # Solve the problem - for i in range(len(W)): - w.value = W[i] + w = cp.Parameter() + π = [cp.Variable((n_q, n_c), nonneg=True) for _ in A_i] + + surplus = cp.sum([ + cp.sum(cp.multiply(Φ, π[a_i])) + for a_i in A_i + ]) + + promise = [ + cp.sum([ + cp.sum(cp.multiply(U[a_i], π[a_i][q_i, :])) + for a_i in A_i + for q_i in Q_i + ]) == w + ] + + output_law = [ + cp.sum(π[a_i][q_i, :]) == P[a_i, q_i] * cp.sum(π[a_i]) + for a_i in A_i + for q_i in Q_i + ] + + probability = [cp.sum([cp.sum(π[a_i]) for a_i in A_i]) == 1] + + constraints = promise + output_law + probability + + if problem_type.lower() != "full information": + incentives = [] + for a_i in A_i: + for a_hat_i in A_i: + obey = cp.sum([ + cp.sum(cp.multiply(U[a_i], π[a_i][q_i, :])) + for q_i in Q_i + ]) + deviate = cp.sum([ + cp.sum(cp.multiply(U[a_hat_i], π[a_i][q_i, :])) + * P[a_hat_i, q_i] / P[a_i, q_i] + for q_i in Q_i + ]) + incentives.append(obey >= deviate) + constraints += incentives + + problem = cp.Problem(cp.Maximize(surplus), constraints) + + s_W = np.full(len(W), np.nan) + π_W = np.full((len(W), n_a, n_q, n_c), np.nan) + for w_i, w_value in enumerate(W): + w.value = w_value problem.solve(solver=cp.HIGHS) - s_W[i] = obj_expr.value - for a_ind in A_ind: - Pi[i, a_ind, :, :] = Pi_list[a_ind].value - - return s_W, Pi + if problem.status in (cp.OPTIMAL, cp.OPTIMAL_INACCURATE): + s_W[w_i] = surplus.value + for a_i in A_i: + π_W[w_i, a_i] = π[a_i].value + + return s_W, π_W ``` -### Figures 1-4 +### Static allocations ```{note} -Phelan and Townsend report solutions computed with standard revised -simplex methods. We use HiGHS through CVXPY. At degenerate utility -grid points, a different LP solver can select a different optimal -lottery, so some consumption schedules can differ slightly even when -the surplus function is unchanged. +The original calculations used standard revised simplex methods. + +We use HiGHS through CVXPY. + +At degenerate utility grid points, different LP solvers can select +different optimal lotteries. + +Some consumption schedules can therefore differ slightly even when the +surplus function is unchanged. ``` ```{code-cell} ipython3 W_static = np.linspace(1, 5, 100) -filterwarnings("ignore") -s_W_full, Pi_full = solve_static_problem(W_static, u, A, Q, C, P, +s_W_full, π_full = solve_static_problem(W_static, u, A, Q, C, P, "full information") -s_W_unobs, Pi_unobs = solve_static_problem(W_static, u, A, Q, C, P, +s_W_unobs, π_unobs = solve_static_problem(W_static, u, A, Q, C, P, "unobserved-actions") ``` +The arrays returned by `solve_static_problem` have a direct economic +interpretation. + +`s_W_full` and `s_W_unobs` are the optimized surplus frontiers. + +`π_full` and `π_unobs` store the optimal lotteries over +$(a,q,c)$ at each promised utility. + +Grid points outside a problem's feasible promise set are recorded as +`nan`, so Matplotlib leaves those parts of the graph blank. + +```{code-cell} ipython3 +def expected_consumption_static(π_W, C): + π0 = np.nan_to_num(π_W, nan=0.0) + mass = π0.sum(axis=-1) + numerator = np.einsum('c,waqc->waq', C, π0) + Ec = np.full(mass.shape, np.nan) + np.divide(numerator, mass, out=Ec, where=mass > 1e-10) + return Ec +``` + ```{code-cell} ipython3 -# Figure 1 – Surplus functions plt.figure(figsize=(6.5, 6.5)) -plt.plot(W_static, s_W_full) -plt.plot(W_static, s_W_unobs) +plt.plot(W_static, s_W_full, label="Full information") +plt.plot(W_static, s_W_unobs, label="Hidden effort") plt.hlines(0, 1.0, 5.0, linestyle="dashed") plt.xlabel("w") plt.ylabel("s(w)") plt.xlim([1.0, 5.0]) plt.ylim([-1.5, 2.0]) -plt.title("Figure 1\n Optimized surplus function", y=-0.2) -plt.text(2.5, 1.6, "Full Information", size=15) -plt.text(1.5, 0.8, "Unobserved Action", size=15) +plt.title("Surplus frontiers", y=-0.2) +plt.legend() plt.show() ``` +The full-information frontier is higher because the planner can choose +effort directly. + +The unobserved-action frontier lies below it because effort must be +induced with state-contingent rewards. + +The gap is the agency cost of private effort. + ```{code-cell} ipython3 -# Figure 2 – Expected effort -Ea_full = np.einsum('a,waqc->w', A, Pi_full) -Ea_unobs = np.einsum('a,waqc->w', A, Pi_unobs) +Ea_full = np.einsum('a,waqc->w', A, π_full) +Ea_unobs = np.einsum('a,waqc->w', A, π_unobs) plt.figure(figsize=(6.5, 6.5)) -plt.plot(W_static, Ea_full) -plt.plot(W_static, Ea_unobs) +plt.plot(W_static, Ea_full, label="Full information") +plt.plot(W_static, Ea_unobs, label="Hidden effort") plt.xlabel("w") plt.ylabel("E{a(w)}") plt.xlim([1.0, 5.0]) plt.ylim([0.0, 0.8]) -plt.title("Figure 2\n Actions", y=-0.2) -plt.text(2.3, 0.65, "Full Information", size=15) -plt.text(2.6, 0.15, "Unobserved Action", size=15) +plt.title("Expected effort", y=-0.2) +plt.legend() plt.show() ``` -```{code-cell} ipython3 -# Figure 3 – Unobserved-action consumption schedule -Pi_sum_unobs = Pi_unobs.sum(axis=-1) # shape (W, A, Q) -Ec_unobs = (np.einsum('c,waqc->waq', C, Pi_unobs) - / np.where(Pi_sum_unobs > 1e-12, Pi_sum_unobs, 1.0)) +Here the code integrates the action grid against the lottery +probabilities, producing $E\{a(w)\}$. -l, m = len(A), len(Q) -X, Y = range(l), range(m) +Under full information, effort is chosen to maximize surplus at each +promise. -plt.figure(figsize=(6.5, 6.5)) -for x in X: - for y in Y: - plt.plot(W_static, Ec_unobs[:, x, y]) -plt.xlabel("w") -plt.ylabel("E(c) given a, q, w") -plt.xlim([1.0, 5.0]) -plt.ylim([0.0, 2.25]) -plt.title("Figure 3\n Unobserved Action Consumption", y=-0.3) -plt.annotate("a=.4, q=2", xy=(2.5, 0.5), xytext=(1.3, 0.7), - arrowprops={"arrowstyle":"-"}) -plt.annotate("a=.2, q=2", xy=(3.7, 1.5), xytext=(2.2, 1.65), - arrowprops={"arrowstyle":"-"}) -plt.annotate("a=0, q=(1,2)", xy=(4.8, 2.05), xytext=(3.0, 2.15), - arrowprops={"arrowstyle":"-"}) -plt.annotate("a=.2,\nq=2", xy=(2.0, 0.15), xytext=(1.3, 0.24), - arrowprops={"arrowstyle":"-"}) -plt.annotate("a=.4, q=1", xy=(3.0, 0.10), xytext=(3.6, 0.2), - arrowprops={"arrowstyle":"-"}) -plt.annotate("a=.2,\nq=1", xy=(4.0, 0.9), xytext=(4.3, 0.75), - arrowprops={"arrowstyle":"-"}) -plt.annotate("a=0, q=(1,2)\na=.2, q=1", xy=(2.1, 0), - xytext=(1.8, -0.3), arrowprops={"arrowstyle":"-"}) -plt.annotate(r"$\{$", fontsize=25, xy=(2.1, 0), xytext=(1.6, -0.3)) -plt.annotate(r"$\}$", fontsize=25, xy=(2.1, 0), xytext=(2.5, -0.3)) +With unobserved action, expected effort is lower where incentives are +costly to provide. + +```{code-cell} ipython3 +Ec_unobs = expected_consumption_static(π_unobs, C) + +fig, axes = plt.subplots(1, len(Q), figsize=(11, 4), sharey=True) +for q_i, ax in enumerate(axes): + for a_i, a in enumerate(A): + ax.plot(W_static, Ec_unobs[:, a_i, q_i], label=f"a={a:g}") + ax.set_title(f"q={Q[q_i]:g}") + ax.set_xlabel("w") + ax.set_xlim([1.0, 5.0]) + ax.set_ylim([0.0, 2.25]) +axes[0].set_ylabel("E(c | w, a, q)") +axes[-1].legend(title="Action", loc="lower right") +fig.suptitle("Consumption when effort is hidden") +fig.tight_layout() plt.show() ``` -```{code-cell} ipython3 -# Figure 4 – Full-information consumption schedule -Pi_sum_full = Pi_full.sum(axis=-1) # shape (W, A, Q) -Ec_full = (np.einsum('c,waqc->waq', C, Pi_full) - / np.where(Pi_sum_full > 1e-12, Pi_sum_full, 1.0)) +This cell conditions on the recommended action and realized output, then +computes expected consumption. -plt.figure(figsize=(6.5, 6.5)) -for x in X: - for y in Y: - plt.plot(W_static, Ec_full[:, x, y]) -plt.xlabel("w") -plt.ylabel("E{c(w)}") -plt.xlim([1.0, 5.0]) -plt.ylim([0.0, 2.25]) -plt.title("Figure 4\n Full Information Consumption", y=-0.2) +The unobserved-action schedule uses current consumption as an incentive +device: high-output histories tend to receive higher consumption, while +low-output histories receive less. + +```{code-cell} ipython3 +Ec_full = expected_consumption_static(π_full, C) + +fig, axes = plt.subplots(1, len(Q), figsize=(11, 4), sharey=True) +for q_i, ax in enumerate(axes): + for a_i, a in enumerate(A): + ax.plot(W_static, Ec_full[:, a_i, q_i], label=f"a={a:g}") + ax.set_title(f"q={Q[q_i]:g}") + ax.set_xlabel("w") + ax.set_xlim([1.0, 5.0]) + ax.set_ylim([0.0, 2.25]) +axes[0].set_ylabel("E(c | w, a, q)") +axes[-1].legend(title="Action", loc="lower right") +fig.suptitle("Consumption under full information") +fig.tight_layout() plt.show() ``` -## The Repeated Economy +With full information, output does not need to carry incentive rewards. -We now move from the one-period economy to the finite- and -infinite-horizon economies studied in Section IV of -{cite}`Phelan_Townsend_91`. +Consumption therefore depends primarily on the promise $w$ rather than +on output. + +## The repeated economy + +We now move from the one-period economy to finite- and infinite-horizon +contracts. The planner maximizes discounted social surplus. -As in the paper, this -can be interpreted as allowing society to borrow and lend at the constant +This can be interpreted as allowing society to borrow and lend at the constant gross interest rate $\beta^{-1}$, so that discounted surplus is the right feasibility criterion. @@ -611,20 +687,23 @@ iterate on the Bellman operator until the surplus function is stable. At each iteration, a separate LP is solved for each grid point $w \in W$. -### The Two-Step Factored Algorithm +### The two-step factored algorithm Solving the full LP over $(a,q,c,w')$ at each grid point is computationally demanding. -Section VI of {cite}`Phelan_Townsend_91` proposes a factored -algorithm that splits each period into two sub-steps, exploiting the -additive separability of the utility function +We use a factored algorithm that splits each period into two sub-steps. + +The split exploits the additive separability of the utility function $$ U(a, c) = 2\sqrt{1-a} + 2\sqrt{c}. $$ -*Step 1* (action and output, before consumption is assigned). +#### Step 1: action and output + +Before consumption is assigned, the planner chooses the action, output, +and intermediate promised utility. Let $w^m$ be the **intermediate** promised utility after the output is observed but before consumption is allocated. @@ -661,7 +740,8 @@ $$ \end{aligned} $$ -*Step 2* (consumption allocation). +#### Step 2: consumption allocation + Given $w^m$, solve $$ @@ -693,14 +773,19 @@ to the exact solution. ### Functions -The function `solve_repeated_problem_2` implements one Bellman -iteration using the two-step algorithm. +The function `solve_repeated_problem_2` implements one Bellman step using +the two-step algorithm. + +The variables in the code follow the two sub-problems above. + +`π_w_m` is the lottery over $(c,w')$ conditional on $w^m$, while +`π_w` is the lottery over $(a,q,w^m)$ conditional on $w$. -The function `solve_multi_period_economy_2` iterates to convergence -(or for a fixed number of periods $T$). +The function `solve_multi_period_economy_2` then repeats this Bellman +step to convergence, or works backward for a fixed number of periods +$T$. ```{code-cell} ipython3 -# Define the function that solves the dynamic problem at one iteration def solve_repeated_problem_2(W=None, W_m=None, A=None, @@ -712,8 +797,8 @@ def solve_repeated_problem_2(W=None, problem_type=None, β=0.8): ''' - Function: Solve the dynamic problem at one iteration - + One Bellman update using the two-step algorithm. + Parameters ---------- W: 1-D array @@ -739,9 +824,9 @@ def solve_repeated_problem_2(W=None, ------- s_W: 1-D array The optimal values of surplus for each w in w_vec. - Pi_W_s1: 4-D array + π_W_s1: 4-D array The probability of (a, q, w_m) given w. - Pi_W_m_s2: 3-D array + π_W_m_s2: 3-D array The probability of (c, w_prime) given w_m. ''' @@ -750,42 +835,34 @@ def solve_repeated_problem_2(W=None, A_ind, Q_ind, C_ind = range(n_A), range(n_Q), range(n_C) W_ind, W_m_ind, W_prime_ind = range(n_W), range(n_W_m), range(n_W_prime) - # Problem of step 2 - - # Define parameters + # Step 2 Phi_s2 = np.array([[β * s_w_prime - c for s_w_prime in s_W_prime] for c in C]) U_disc_s2 = np.array([[2 * c**0.5 + β * w_prime for w_prime in W_prime] for c in C]) w_m_para = cp.Parameter() - - - # Define variables - Pi_w_m = cp.Variable((n_C, n_W_prime)) + π_w_m = cp.Variable((n_C, n_W_prime)) - # Define the objective function - obj_expr_s2 = cp.sum(cp.multiply(Phi_s2, Pi_w_m)) + obj_expr_s2 = cp.sum(cp.multiply(Phi_s2, π_w_m)) obj_s2 = cp.Maximize(obj_expr_s2) - # Define constraints - C5_s2 = [cp.sum(cp.multiply(U_disc_s2, Pi_w_m)) == w_m_para] - C7_s2 = [cp.sum(Pi_w_m) == 1] + [Pi_w_m >= 0] + C5_s2 = [cp.sum(cp.multiply(U_disc_s2, π_w_m)) == w_m_para] + C7_s2 = [cp.sum(π_w_m) == 1] + [π_w_m >= 0] - # Create the problem of step 2 problem_s2 = cp.Problem(obj_s2, C5_s2 + C7_s2) - # Solve the problem of step 2 s_W_m = np.zeros(n_W_m) - Pi_W_m_s2 = np.zeros((n_W_m, n_C, n_W_prime)) + π_W_m_s2 = np.zeros((n_W_m, n_C, n_W_prime)) for w_m, w_m_ind in zip(W_m, W_m_ind): w_m_para.value = w_m problem_s2.solve(solver = cp.HIGHS) + if problem_s2.status not in (cp.OPTIMAL, cp.OPTIMAL_INACCURATE): + raise RuntimeError(f"Step 2 LP failed at w_m={w_m}: " + f"{problem_s2.status}") s_W_m[w_m_ind] = obj_expr_s2.value - Pi_W_m_s2[w_m_ind, :, :] = Pi_w_m.value - - # Problem of step 1 + π_W_m_s2[w_m_ind, :, :] = π_w_m.value - # Define parameters + # Step 1 Phi_s1 = np.array([[(q+s_w_m) for s_w_m in s_W_m] for q in Q]) U_disc_s1 = np.array([[[2 * (1 - a)**0.5 + w_m for w_m in W_m] for q in Q] for a in A]) @@ -796,55 +873,52 @@ def solve_repeated_problem_2(W=None, for a_hat_ind in A_ind]) w_para = cp.Parameter() - - # Define variables - Pi_w_list = list(np.zeros(n_A)) + + π_w = list(np.zeros(n_A)) for a_ind in A_ind: - Pi_w_list[a_ind] = cp.Variable((n_Q, n_W_m)) + π_w[a_ind] = cp.Variable((n_Q, n_W_m)) - # Define the objective function - obj_expr_s1 = cp.sum([cp.sum(cp.multiply(Phi_s1, Pi_w_list[a_ind])) + obj_expr_s1 = cp.sum([cp.sum(cp.multiply(Phi_s1, π_w[a_ind])) for a_ind in A_ind]) obj_s1 = cp.Maximize(obj_expr_s1) - - # Define constraints + C5_s1 = [cp.sum([cp.sum(cp.multiply(U_disc_s1[a_ind, :, :], - Pi_w_list[a_ind])) + π_w[a_ind])) for a_ind in A_ind]) == w_para] - C6_s1 = [(cp.sum(Pi_w_list[a_ind][q_ind, :]) == P[a_ind, q_ind] *\ - cp.sum(Pi_w_list[a_ind])) + C6_s1 = [(cp.sum(π_w[a_ind][q_ind, :]) == P[a_ind, q_ind] *\ + cp.sum(π_w[a_ind])) for q_ind in Q_ind for a_ind in A_ind] - C7_s1 = [cp.sum([cp.sum(Pi_w_list[a_ind]) for a_ind in A_ind]) == 1] - C7_s1 = C7_s1 + [(Pi_w_list[a_ind] >= 0) for a_ind in A_ind] - + C7_s1 = [cp.sum([cp.sum(π_w[a_ind]) for a_ind in A_ind]) == 1] + C7_s1 = C7_s1 + [(π_w[a_ind] >= 0) for a_ind in A_ind] + problem_type = problem_type.lower() if problem_type == "full information": constraints_s1 = C5_s1 + C6_s1 + C7_s1 else: C8_s1 = [(cp.sum(cp.multiply(U_disc_s1[a_ind, :, :], - Pi_w_list[a_ind])) >= + π_w[a_ind])) >= cp.sum(cp.multiply(U_disc_hat_s1[a_hat_ind, a_ind, :, :], - Pi_w_list[a_ind]))) - for a_ind in A_ind for a_hat_ind in A_ind] + π_w[a_ind]))) + for a_ind in A_ind for a_hat_ind in A_ind] constraints_s1 = C5_s1 + C6_s1 + C7_s1 + C8_s1 - - # Create the problem of step 1 + problem_s1 = cp.Problem(obj_s1, constraints_s1) - - # Solve the problem of step 1 + s_W = np.zeros(n_W) - Pi_W_s1 = np.zeros((n_W, n_A, n_Q, n_W_m)) + π_W_s1 = np.zeros((n_W, n_A, n_Q, n_W_m)) for w, w_ind in zip(W, W_ind): w_para.value = w problem_s1.solve(solver = cp.HIGHS) + if problem_s1.status not in (cp.OPTIMAL, cp.OPTIMAL_INACCURATE): + raise RuntimeError(f"Step 1 LP failed at w={w}: " + f"{problem_s1.status}") s_W[w_ind] = obj_expr_s1.value for a_ind in A_ind: - Pi_W_s1[w_ind, a_ind, :, :] = Pi_w_list[a_ind].value - return s_W, Pi_W_s1, Pi_W_m_s2 + π_W_s1[w_ind, a_ind, :, :] = π_w[a_ind].value + return s_W, π_W_s1, π_W_m_s2 ``` ```{code-cell} ipython3 -# Define the function that solves the infinite-period or finite-period economy def solve_multi_period_economy_2(A=None, Q=None, C=None, @@ -858,7 +932,7 @@ def solve_multi_period_economy_2(A=None, tol=1e-8, verbose=False): ''' - Function: Solve the multi-period problem, either infinite-period or finite-period + Solve the finite- or infinite-horizon economy. Parameters ---------- @@ -892,21 +966,19 @@ def solve_multi_period_economy_2(A=None, ------- s_W: 1-D array The optimal values of convergent surplus for each w in w_vec. - Pi_W_s1: 4-D array + π_W_s1: 4-D array The probability of (a, q, w_m) given w. - Pi_W_m_s2: 3-D array + π_W_m_s2: 3-D array The probability of (c, w_prime) given w_m. ''' if β >= 1 or β <= 0: raise ValueError('β must lie in (0, 1)') - # Define the function u[a,c] def u(a, c): return c**0.5/0.5 + (1-a)**0.5/0.5 if T is None: - # Discretize the parameter space W and W_m problem_type = problem_type.lower() if problem_type == "full information": w_l = u(A.max(), C.min())/(1 - β) @@ -920,20 +992,18 @@ def solve_multi_period_economy_2(A=None, W_m_u = β * w_u + 2 * C.max()**0.5 W_m = np.linspace(W_m_l, W_m_u, N_m) - # Assign initial value for s_W if s_W_0 is not None: s_W_prime = s_W_0 else: s_W_prime = np.zeros(N) - # Iterate optimal = False iteration = 1 while not optimal: if verbose: print('Iteration %i in process' % iteration) start_time = time() - s_W, Pi_W_s1, Pi_W_m_s2 = solve_repeated_problem_2(W=W, W_m=W_m, + s_W, π_W_s1, π_W_m_s2 = solve_repeated_problem_2(W=W, W_m=W_m, A=A, Q=Q, C=C, W_prime=W, s_W_prime=s_W_prime, @@ -944,7 +1014,7 @@ def solve_multi_period_economy_2(A=None, if verbose: print('Iteration %i finished in:' % iteration, round(end_time - start_time, 3), 's') - print('---------') + print('---') if np.max(np.abs(s_W - s_W_prime)) <= tol: optimal = True @@ -954,7 +1024,6 @@ def solve_multi_period_economy_2(A=None, iteration += 1 if T is not None: - # Discretize the parameter space W W_mat = np.zeros((T, N)) problem_type = problem_type.lower() @@ -968,11 +1037,10 @@ def solve_multi_period_economy_2(A=None, np.linspace(w_l, w_u, N).reshape(1, N), axis=0) - # Solve the 1-period economy if verbose: print('Solving the 1-period economy') - print('-------') - s_W, Pi = solve_static_problem(W=W_mat[0, :], u=u, + print('---') + s_W, π = solve_static_problem(W=W_mat[0, :], u=u, A=A, Q=Q, C=C, P=P, problem_type=problem_type) @@ -980,12 +1048,12 @@ def solve_multi_period_economy_2(A=None, for t in range(2, T+1): if verbose: print('Solving the %i-period economy' % t) - print('-------') + print('---') s_W_prime = np.copy(s_W) W_m_l = β*W_mat[t-2,:].min() + 2*C.min()**0.5 W_m_u = β*W_mat[t-2,:].max() + 2*C.max()**0.5 W_m = np.linspace(W_m_l, W_m_u, N_m) - s_W, Pi_W_s1, Pi_W_m_s2 = solve_repeated_problem_2(W=W_mat[t-1,:], + s_W, π_W_s1, π_W_m_s2 = solve_repeated_problem_2(W=W_mat[t-1,:], W_m=W_m, A=A, Q=Q, C=C, W_prime=W_mat[t-2,:], @@ -993,14 +1061,16 @@ def solve_multi_period_economy_2(A=None, P=P, problem_type=problem_type, β=β) - return s_W, Pi_W_s1, Pi_W_m_s2 + return s_W, π_W_s1, π_W_m_s2 ``` -### Improved Solver: Pre-built Problems with Anderson Acceleration +### Improved solver: pre-built problems with Anderson acceleration The original solver rebuilds all CVXPY problem objects on every Bellman iteration, which causes memory to accumulate when many -iterations are needed -- a serious issue for $\beta$ close to 1. +iterations are needed. + +This is a serious issue for $\beta$ close to 1. The function `solve_multi_period_economy_vfi` fixes this by building the two sub-problems *once* with CVXPY `Parameter` objects for the @@ -1028,8 +1098,8 @@ def solve_multi_period_economy_vfi(A=None, Infinite-horizon VFI using the two-step factored algorithm. Improvements over solve_multi_period_economy_2: - * CVXPY problems are built once with Parameter objects -- - no memory leak across iterations. + * CVXPY problems are built once with Parameter objects, + so there is no memory leak across iterations. * Anderson acceleration (window m_anderson) reduces the number of Bellman iterations needed. * max_iter cap prevents unbounded runtime. @@ -1037,8 +1107,8 @@ def solve_multi_period_economy_vfi(A=None, Returns ------- s_W : 1-D array, converged surplus function on W - Pi_W_s1 : 4-D array, Pi(a, q, w_m | w) - Pi_W_m_s2 : 3-D array, Pi(c, w' | w_m) + π_W_s1 : 4-D array, π(a, q, w_m | w) + π_W_m_s2 : 3-D array, π(c, w' | w_m) W : 1-D array, the utility grid used """ if β >= 1 or β <= 0: @@ -1062,96 +1132,89 @@ def solve_multi_period_economy_vfi(A=None, n_A, n_Q, n_C = len(A), len(Q), len(C) A_ind, Q_ind = range(n_A), range(n_Q) - # Fixed arrays (depend only on grids and P, not on s_W) + # Terms that do not change across iterations. U_disc_s2 = np.array([[2 * c**0.5 + β * wp - for wp in W] for c in C]) # (n_C, N) + for wp in W] for c in C]) U_disc_s1 = np.array([[[2 * (1 - a)**0.5 + wm for wm in W_m] for q in Q] - for a in A]) # (n_A, n_Q, N_m) + for a in A]) U_disc_hat_s1 = np.array([[[[ (2 * (1 - A[ah])**0.5 + W_m[wmi]) * P[ah, qi] / P[ai, qi] for wmi in range(N_m)] for qi in Q_ind] - for ai in A_ind] for ah in A_ind]) # (n_A, n_A, n_Q, N_m) + for ai in A_ind] for ah in A_ind]) - # ---------------------------------------------------------- - # Step-2 CVXPY problem (built once) - # ---------------------------------------------------------- - Phi_s2_param = cp.Parameter((n_C, N)) # updated each outer iteration + # Step 2 problem. + Phi_s2_param = cp.Parameter((n_C, N)) w_m_para = cp.Parameter() - Pi_w_m = cp.Variable((n_C, N)) + π_w_m = cp.Variable((n_C, N)) - obj_expr_s2 = cp.sum(cp.multiply(Phi_s2_param, Pi_w_m)) - C5_s2 = [cp.sum(cp.multiply(U_disc_s2, Pi_w_m)) == w_m_para] - C7_s2 = [cp.sum(Pi_w_m) == 1, Pi_w_m >= 0] + obj_expr_s2 = cp.sum(cp.multiply(Phi_s2_param, π_w_m)) + C5_s2 = [cp.sum(cp.multiply(U_disc_s2, π_w_m)) == w_m_para] + C7_s2 = [cp.sum(π_w_m) == 1, π_w_m >= 0] problem_s2 = cp.Problem(cp.Maximize(obj_expr_s2), C5_s2 + C7_s2) - # ---------------------------------------------------------- - # Step-1 CVXPY problem (built once) - # ---------------------------------------------------------- - Phi_s1_param = cp.Parameter((n_Q, N_m)) # updated after step 2 + # Step 1 problem. + Phi_s1_param = cp.Parameter((n_Q, N_m)) w_para = cp.Parameter() - Pi_w_list = [cp.Variable((n_Q, N_m)) for _ in A_ind] + π_w = [cp.Variable((n_Q, N_m)) for _ in A_ind] - obj_expr_s1 = cp.sum([cp.sum(cp.multiply(Phi_s1_param, Pi_w_list[ai])) + obj_expr_s1 = cp.sum([cp.sum(cp.multiply(Phi_s1_param, π_w[ai])) for ai in A_ind]) - C5_s1 = [cp.sum([cp.sum(cp.multiply(U_disc_s1[ai], Pi_w_list[ai])) + C5_s1 = [cp.sum([cp.sum(cp.multiply(U_disc_s1[ai], π_w[ai])) for ai in A_ind]) == w_para] - C6_s1 = [(cp.sum(Pi_w_list[ai][qi, :]) == - P[ai, qi] * cp.sum(Pi_w_list[ai])) + C6_s1 = [(cp.sum(π_w[ai][qi, :]) == + P[ai, qi] * cp.sum(π_w[ai])) for qi in Q_ind for ai in A_ind] - C7_s1 = ([cp.sum([cp.sum(Pi_w_list[ai]) for ai in A_ind]) == 1] + - [Pi_w_list[ai] >= 0 for ai in A_ind]) + C7_s1 = ([cp.sum([cp.sum(π_w[ai]) for ai in A_ind]) == 1] + + [π_w[ai] >= 0 for ai in A_ind]) if problem_type == "full information": constraints_s1 = C5_s1 + C6_s1 + C7_s1 else: - C8_s1 = [(cp.sum(cp.multiply(U_disc_s1[ai], Pi_w_list[ai])) >= - cp.sum(cp.multiply(U_disc_hat_s1[ah, ai], Pi_w_list[ai]))) + C8_s1 = [(cp.sum(cp.multiply(U_disc_s1[ai], π_w[ai])) >= + cp.sum(cp.multiply(U_disc_hat_s1[ah, ai], π_w[ai]))) for ai in A_ind for ah in A_ind] constraints_s1 = C5_s1 + C6_s1 + C7_s1 + C8_s1 problem_s1 = cp.Problem(cp.Maximize(obj_expr_s1), constraints_s1) - # ---------------------------------------------------------- - # Initialise - # ---------------------------------------------------------- s_W_prime = (np.array(s_W_0, dtype=float) if s_W_0 is not None else np.zeros(N)) hist_x, hist_fx = [], [] err = np.inf - # ---------------------------------------------------------- - # Main iteration loop - # ---------------------------------------------------------- for iteration in range(1, max_iter + 1): t0 = time() - # --- Step 2: solve for s_W_m --- + # Step 2. Phi_s2_param.value = np.array([[β * sv - c for sv in s_W_prime] for c in C]) s_W_m = np.zeros(N_m) - Pi_W_m_s2 = np.zeros((N_m, n_C, N)) + π_W_m_s2 = np.zeros((N_m, n_C, N)) for i, wm in enumerate(W_m): w_m_para.value = wm problem_s2.solve(solver=cp.HIGHS, warm_start=True) - if obj_expr_s2.value is not None: - s_W_m[i] = obj_expr_s2.value - Pi_W_m_s2[i] = Pi_w_m.value + if problem_s2.status not in (cp.OPTIMAL, cp.OPTIMAL_INACCURATE): + raise RuntimeError(f"Step 2 LP failed at w_m={wm}: " + f"{problem_s2.status}") + s_W_m[i] = obj_expr_s2.value + π_W_m_s2[i] = π_w_m.value - # --- Step 1: solve for s_W --- + # Step 1. Phi_s1_param.value = np.array([[(q + swm) for swm in s_W_m] for q in Q]) s_W = np.zeros(N) - Pi_W_s1 = np.zeros((N, n_A, n_Q, N_m)) + π_W_s1 = np.zeros((N, n_A, n_Q, N_m)) for i, w in enumerate(W): w_para.value = w problem_s1.solve(solver=cp.HIGHS, warm_start=True) - if obj_expr_s1.value is not None: - s_W[i] = obj_expr_s1.value - for ai in A_ind: - if Pi_w_list[ai].value is not None: - Pi_W_s1[i, ai] = Pi_w_list[ai].value + if problem_s1.status not in (cp.OPTIMAL, cp.OPTIMAL_INACCURATE): + raise RuntimeError(f"Step 1 LP failed at w={w}: " + f"{problem_s1.status}") + s_W[i] = obj_expr_s1.value + for ai in A_ind: + π_W_s1[i, ai] = π_w[ai].value t1 = time() err = np.max(np.abs(s_W - s_W_prime)) @@ -1163,7 +1226,7 @@ def solve_multi_period_economy_vfi(A=None, print(f"Converged in {iteration} iterations.") break - # --- Anderson acceleration --- + # Anderson acceleration. hist_x.append(s_W_prime.copy()) hist_fx.append(s_W.copy()) mk = min(len(hist_x), m_anderson) @@ -1197,15 +1260,16 @@ def solve_multi_period_economy_vfi(A=None, print(f"Warning: did not converge after {max_iter} iterations. " f"Final max|ΔsW| = {err:.2e}") - return s_W, Pi_W_s1, Pi_W_m_s2, W + return s_W, π_W_s1, π_W_m_s2, W ``` -### Numerical Results +### Dynamic allocations We use the same parameters as for the static economy, plus a discount factor $\beta = 0.8$ and grids of $N = N_m = 100$ points. -*Initial values.* +#### Initial values + We initialise the value function iteration with the one-period (static) solution, scaled to discounted-sum units. @@ -1223,18 +1287,18 @@ W_m_u = β * W_u + 2 * C.max()**0.5 W_m = np.linspace(W_m_l, W_m_u, N_m) in_time = time() -s_W_0, Pi_0 = solve_static_problem(W * (1 - β), u, +s_W_0, π_0 = solve_static_problem(W * (1 - β), u, A, Q, C, P, "unobserved-actions") out_time = time() print("Time(s):", round(out_time - in_time, 3)) ``` -*Finite-period economy ($T = 3$).* +#### Finite-period economy ```{code-cell} ipython3 in_time = time() -s_W_T, Pi_W_s1_T, Pi_W_m_s2_T = solve_multi_period_economy_2( +s_W_T, π_W_s1_T, π_W_m_s2_T = solve_multi_period_economy_2( A, Q, C, P, "unobserved-actions", T=3, N=N, N_m=N_m) out_time = time() @@ -1252,19 +1316,26 @@ W_mat = np.cumsum( W_T = W_mat[2, :] plt.figure(figsize=(6.5, 6.5)) -plt.plot(W_T, s_W_T, "k-.") -plt.text(8, 3, "3-Period Unobserved Action", size=12) -plt.title("Figure\n Optimized surplus function", y=-0.2) +plt.plot(W_T, s_W_T, "k-.", label="Three-period hidden effort") +plt.title("Finite-horizon surplus frontier", y=-0.2) +plt.legend() plt.show() ``` -*Infinite-period economy.* +This finite-horizon computation is a useful check on the recursion. + +The three-period surplus function already has the shape of the +infinite-horizon frontier, but it is still affected by the approaching +terminal date because continuation promises have value for only a few +periods. + +#### Infinite-period economy ```{code-cell} ipython3 :tags: [hide-output] in_time = time() -s_W, Pi_W_s1, Pi_W_m_s2 = solve_multi_period_economy_2( +s_W, π_W_s1, π_W_m_s2 = solve_multi_period_economy_2( A, Q, C, P, "unobserved-actions", N=N, N_m=N_m, s_W_0=s_W_0 / (1 - β), @@ -1275,16 +1346,23 @@ print("Time(s):", round(out_time - in_time, 3)) ```{code-cell} ipython3 plt.figure(figsize=(6.5, 6.5)) -plt.plot(W, s_W, "k-.") +plt.plot(W, s_W, "k-.", label="Infinite-horizon hidden effort") plt.xlim([5.0, 25.0]) plt.ylim([-7.5, 10.0]) plt.xlabel("w") plt.ylabel("s(w)") -plt.title("Figure\n Optimized surplus function", y=-0.2) -plt.text(15, 6.5, "Infinity Unobserved Action", size=12) +plt.title("Infinite-horizon surplus frontier", y=-0.2) +plt.legend() plt.show() ``` +The infinite-horizon solution removes the terminal-date effect. + +At each promised utility, the surplus function is the fixed point of the +Bellman operator: the current lottery and the continuation promise are +jointly chosen so that tomorrow's promise is priced by the same surplus +function plotted here. + ### Recovering $\Pi^w(a, q, c, w')$ The two-step algorithm returns @@ -1299,28 +1377,21 @@ $$ $$ ```{code-cell} ipython3 -n_A, n_Q, n_C, n_W, n_W_prime = 4, 2, 81, N, N -A_ind, Q_ind, C_ind = range(n_A), range(n_Q), range(n_C) -W_ind, W_prime_ind = range(n_W), range(n_W_prime) - -Pi = np.array([[[[[ - Pi_W_s1[w_ind, a_ind, q_ind, :] @ - Pi_W_m_s2[:, c_ind, w_prime_ind] - for w_prime_ind in W_prime_ind] - for c_ind in C_ind] - for q_ind in Q_ind] - for a_ind in A_ind] - for w_ind in W_ind]) +π = np.einsum("waqm,mcx->waqcx", π_W_s1, π_W_m_s2) ``` -#### Figure 5 +The `einsum` line is just the law of total probability. + +It sums over the intermediate promise $w^m$ and reconstructs the full +lottery over $(a,q,c,w')$. + +#### Surplus and history dependence ```{code-cell} ipython3 -# Solve the static full information W_full = np.linspace(5, 25, N) in_time = time() -s_W_1, Pi_1 = solve_static_problem(W_full*(1-β), u, A, +s_W_1, π_1 = solve_static_problem(W_full*(1-β), u, A, Q, C, P, "full information") out_time = time() @@ -1329,55 +1400,47 @@ print("Time(s):", round(out_time - in_time, 3)) ```{code-cell} ipython3 plt.figure(figsize=(6.5, 6.5)) -plt.plot(W, s_W, "k-.") -plt.plot(W, s_W_0/(1 - β), "yellow") -plt.plot(W_full, s_W_1/(1 - β), "red") +plt.plot(W_full, s_W_1/(1 - β), label="Full information") +plt.plot(W, s_W, "k-.", label="Repeated hidden effort") +plt.plot(W, s_W_0/(1 - β), label="Static hidden effort") plt.xlim([5.0, 25.0]) plt.ylim([-7.5, 10.0]) plt.hlines(0, 5.0, 25.0, linestyle="dashed") plt.xlabel("w") plt.ylabel("s(w)") -plt.title("Figure 5\n Optimized surplus function", y=-0.2) -plt.text(5.4, -2.0, "Full Information (top)", size=12) -plt.text(5.4, -3.0, "T = infinity Unobserved Action (middle)", size=12) -plt.text(5.4, -4.0, "T = 1 Unobserved Action", size=12) +plt.title("History dependence and surplus", y=-0.2) +plt.legend() plt.show() ``` -Figure 5 compares three surplus functions. +This comparison separates two forces. -The full-information frontier is highest. +The full-information frontier is highest because effort can be controlled +directly. -The infinite-horizon unobserved-action frontier is -below it because incentive constraints are added, but it lies above the -frontier obtained by repeating the one-period unobserved-action contract. +The infinite-horizon hidden-effort frontier is below it because +incentive constraints remain, but it lies above the frontier obtained by +repeating the one-period hidden-effort contract. The difference between the two unobserved-action curves is the gain from history dependence. -#### Figure 6 +#### Effort and history dependence ```{code-cell} ipython3 -# Calculate expected efforts -# T=1 Unobserved Action X, Y = list(range(len(A))), list(range(len(Q))) -Z, N = list(range(len(C))), list(range(len(W))) -Ea_1 = np.array([np.sum([A[x]*Pi_0[i,x,:,:] for x in X]) for i in N]) +Ea_1 = np.einsum('a,waqc->w', A, π_0) +Ea_inf = np.einsum('a,waqcx->w', A, π) -# T=infinity unobserved Action -Ea_inf = np.array([np.sum([A[x]*Pi[i,x,:,:,:] for x in X]) for i in N]) - -# Plot expected efforts plt.figure(figsize=(6.5, 6.5)) -plt.plot(W, Ea_1) -plt.plot(W, Ea_inf) +plt.plot(W, Ea_inf, label="Repeated hidden effort") +plt.plot(W, Ea_1, label="Static hidden effort") plt.xlabel("w") plt.ylabel("E{a(w)}") plt.xlim([5.0, 25.0]) plt.ylim([0.0, 0.8]) -plt.title("Figure 6\n Actions", y=-0.2) -plt.text(14, 0.60, "T = infinity Unobserved Action (top)", size=10) -plt.text(14, 0.55, "T = 1 Unobserved Action (bottom)", size=10) +plt.title("Effort with and without history dependence", y=-0.2) +plt.legend() plt.show() ``` @@ -1388,142 +1451,99 @@ Near the lower utility bound, incentive compatibility forces low effort, but away from that bound continuation promises help provide incentives without relying only on current consumption. -#### Figure 7 +#### Current consumption + +The full lottery $\pi(w,a,q,c,w')$ is high-dimensional. + +To read it, we summarize it by conditional means. + +The next helper computes $E[c \mid w,a,q]$, first summing over +continuation promises and then normalizing by the probability of the +conditioning event. ```{code-cell} ipython3 -def ex_con(Pi, A, Q, C, W, type="infinity"): - X, Y = list(range(len(A))), list(range(len(Q))) - Z, N = list(range(len(C))), list(range(len(W))) - Ec = np.zeros((len(N), len(X), len(Y))) - for i in N: - for x in X: - for y in Y: - if type == "infinity": - total_prob = np.sum(Pi[i,x,y,:,:]) - if total_prob <= 1e-9: - Ec[i,x,y] = float("-inf") - else: - Ec[i,x,y] = np.sum([np.sum(C[z] * Pi[i, x, y, z, :]) - for z in Z])/total_prob - elif type == "one": - total_prob = np.sum(Pi[i,x,y,:]) - if total_prob <= 1e-9: - Ec[i,x,y] = float("-inf") - else: - Ec[i,x,y] = np.sum([C[z] * Pi[i, x, y, :] - for z in Z])/total_prob - return Ec +def expected_consumption(π, C): + """ + E[c | w, a, q] from either π(w,a,q,c) or π(w,a,q,c,w'). + """ + if π.ndim == 4: + mass = π.sum(axis=3) + total = np.einsum("c,waqc->waq", C, π) + else: + mass = π.sum(axis=(3, 4)) + total = np.einsum("c,waqcx->waq", C, π) + + return np.divide(total, mass, + out=np.full_like(total, np.nan, dtype=float), + where=mass > 1e-12) ``` ```{code-cell} ipython3 -Ec_inf = ex_con(Pi, A, Q, C, W) - -# Plot expected consumption -plt.figure(figsize=(10.5, 10.5)) -for x in X: - for y in Y: - plt.plot(W, Ec_inf[:, x, y]) -plt.xlabel("w") -plt.ylabel("E(c) given a, q, w") -plt.xlim([5.0, 25.0]) -plt.ylim([0.0, 2.25]) -plt.title("Figure 7\n Unobserved Action Consumption", y=-0.3) -plt.annotate("a=.4, q=2", xy=(13.5, 0.5), xytext=(10.5, 0.7), - arrowprops={"arrowstyle":"-"}) -plt.annotate("a=.2, q=2", xy=(20.0, 1.3), xytext=(15.5, 1.65), - arrowprops={"arrowstyle":"-"}) -plt.annotate("a=0, q=(1,2)", xy=(24, 2.15), xytext=(15.0, 2.15), - arrowprops={"arrowstyle":"-"}) -plt.annotate("a=.2, q=2", xy=(10.1, 0.01), xytext=(7.5, 0.03), - arrowprops={"arrowstyle":"-"}) -plt.annotate("a=.4, q=2", xy=(10.5, 0.10), xytext=(7.5, 0.15), - arrowprops={"arrowstyle":"-"}) -plt.annotate("a=.6, q=2", xy=(11.5, 0.25), xytext=(8.5, 0.30), - arrowprops={"arrowstyle":"-"}) -plt.annotate("a=.6, q=1", xy=(12.5, 0.05), xytext=(14.5, 0.10), - arrowprops={"arrowstyle":"-"}) -plt.annotate("a=.4, q=1", xy=(15.0, 0.35), xytext=(18, 0.2), - arrowprops={"arrowstyle":"-"}) -plt.annotate("a=.2, q=1", xy=(20.0, 1.1), xytext=(21.5, 0.75), - arrowprops={"arrowstyle":"-"}) -plt.annotate("", xy=(10.0, 0), xytext=(11.5, -0.1), - arrowprops={"arrowstyle":"-"}) -plt.annotate("a=0, q=(1,2)\na={.2,.4}, q=1", fontsize=15, xy=(10.5, 0), - xytext=(5.5, -0.3)) -plt.annotate(r"$\{$",fontsize=35, xy=(10.5, 0), xytext=(4.5, -0.3)) -plt.annotate(r"$\}$",fontsize=35, xy=(10.5, 0), xytext=(9.5, -0.3)) +Ec_inf = expected_consumption(π, C) + +fig, axes = plt.subplots(1, len(Q), figsize=(11, 4), sharey=True) +for q_i, ax in enumerate(axes): + for a_i, a in enumerate(A): + ax.plot(W, Ec_inf[:, a_i, q_i], label=f"a={a:g}") + ax.set_title(f"q={Q[q_i]:g}") + ax.set_xlabel("w") + ax.set_xlim([5.0, 25.0]) + ax.set_ylim([0.0, 2.25]) +axes[0].set_ylabel("E(c | w, a, q)") +axes[-1].legend(title="Action", loc="lower right") +fig.suptitle("Current consumption in the repeated contract") +fig.tight_layout() plt.show() ``` -Figure 7 shows how dynamic contracts smooth current consumption relative -to the static unobserved-action economy. +The repeated contract smooths current consumption relative to the static +hidden-effort economy. Output still affects rewards, but a large part of the reward and punishment is shifted into future promised utility. -#### Figure 8 +#### Continuation promises + +The parallel statistic for the dynamic margin is +$E[w' \mid w,a,q]$. + +This is the object that reveals how the contract uses future utility as a +reward or punishment. ```{code-cell} ipython3 -def ex_ut(Pi, A, Q, C, W): - X, Y = list(range(len(A))), list(range(len(Q))) - Z, N = list(range(len(C))), list(range(len(W))) - Ew = np.zeros((len(N),len(X),len(Y))) - for i in N: - for x in X: - for y in Y: - total_prob = np.sum(Pi[i, x, y, :, :]) - if total_prob <= 1e-9: - Ew[i,x,y] = float("-inf") - else: - Ew[i,x,y] = np.sum([np.sum(W[w] * Pi[i, x, y, :, w]) - for w in N])/total_prob - return Ew +def expected_promise(π, W): + """ + E[w' | w, a, q] from π(w,a,q,c,w'). + """ + mass = π.sum(axis=(3, 4)) + total = np.einsum("x,waqcx->waq", W, π) + return np.divide(total, mass, + out=np.full_like(total, np.nan, dtype=float), + where=mass > 1e-12) ``` ```{code-cell} ipython3 -Ew_inf = ex_ut(Pi, A, Q, C, W) - - -# Plot expected consumption -plt.figure(figsize=(7.5, 7.5)) -marker = [["o","v"],[">","<"],["x","1"],["2","3"]] -for x in X: - for y in Y: - plt.plot(W, Ew_inf[:,x,y],marker=marker[x][y]) -plt.plot(W,W,"k-.") -plt.xlabel("w") -plt.ylabel("E(w') given a, q, w") -plt.xlim([10.0, 25.0]) -plt.ylim([10.0, 25.0]) -plt.title("Figure 8\n Future Utility", y=-0.2) -plt.annotate("a=.4, q=2", xy=(14.0, 15.0), xytext=(10.5, 17.0), - arrowprops={"arrowstyle":"-"}) -plt.annotate("a=.2, q=2", xy=(19.5, 20.0), xytext=(15.0, 23.0), - arrowprops={"arrowstyle":"-"}) -plt.annotate("a=0, q=(1,2)", xy=(24.5, 24.5), xytext=(18.0, 24.5), - arrowprops={"arrowstyle":"-"}) -plt.annotate("a=.2, q=2", xy=(10.0, 10.7), xytext=(7.5, 10.7), - arrowprops={"arrowstyle":"-"}) -plt.annotate("a=.4, q=2", xy=(10.3, 11.2), xytext=(7.5, 11.2), - arrowprops={"arrowstyle":"-"}) -plt.annotate("a=.6, q=2", xy=(11.5, 12.2), xytext=(10.1, 14.0), - arrowprops={"arrowstyle":"-"}) -plt.annotate("a=.6, q=1", xy=(11.7, 10.7), xytext=(13.5, 10.7), - arrowprops={"arrowstyle":"-"}) -plt.annotate("a=.4, q=1", xy=(15.0, 14.0), xytext=(16.5, 12.5), - arrowprops={"arrowstyle":"-"}) -plt.annotate("a=.2, q=1", xy=(20.0, 19.5), xytext=(21.0, 18.0), - arrowprops={"arrowstyle":"-"}) -plt.annotate("", xy=(10.1, 10.1), xytext=(12.0, 9.2), - arrowprops={"arrowstyle":"-"}) -plt.annotate("a=0, q=(1,2)\na={.2,.4}, q=1", xy=(10.1, 10.1), xytext=(9.5, 8.5)) -plt.annotate(r"$\{$",fontsize=25, xy=(10.1, 10.1), xytext=(8.5, 8.5)) -plt.annotate(r"$\}$",fontsize=25, xy=(10.1, 10.1), xytext=(12.5, 8.5)) +Ew_inf = expected_promise(π, W) + + +fig, axes = plt.subplots(1, len(Q), figsize=(11, 4), sharey=True) +for q_i, ax in enumerate(axes): + for a_i, a in enumerate(A): + ax.plot(W, Ew_inf[:, a_i, q_i], label=f"a={a:g}") + ax.plot(W, W, "k-.", label="45-degree line") + ax.set_title(f"q={Q[q_i]:g}") + ax.set_xlabel("w") + ax.set_xlim([10.0, 25.0]) + ax.set_ylim([10.0, 25.0]) +axes[0].set_ylabel("E(w' | w, a, q)") +axes[-1].legend(title="Action", loc="lower right") +fig.suptitle("Continuation promises") +fig.tight_layout() plt.show() ``` -Figure 8 displays the expected next-period promise conditional on +This plot displays the expected next-period promise conditional on current $w$, recommended action $a$, and realized output $q$. High @@ -1533,14 +1553,14 @@ At the endpoints of the feasible promise set, the transition stays on the 45-degree line because only the corresponding extreme plan can deliver that endpoint. -For figures 9--12, {cite}`Phelan_Townsend_91` used $\beta = 0.95$. +For the simulations, we use a higher discount factor, $\beta = 0.95$. -They report that Figures 5--8 are easier to read at $\beta = 0.8$, while -the simulated individual paths and distributions in Figures 9--12 are -more informative at the higher discount factor. +The higher discount factor makes promised utility a stronger incentive +instrument and makes the evolution of individual histories easier to +see. -We now use `solve_multi_period_economy_vfi` -- which builds the CVXPY -problems once and applies Anderson acceleration -- to solve the +We now use `solve_multi_period_economy_vfi`, which builds the CVXPY +problems once and applies Anderson acceleration, to solve the infinite-horizon economy at $\beta = 0.95$ with a grid of $N = N_m = 50$ points. @@ -1552,7 +1572,6 @@ iteration converges to tolerance $10^{-4}$. N_95 = 50 N_m95 = 50 -# Initial value: static solution rescaled to infinite-horizon units w_l_95 = u(A.min(), C.min()) / (1 - β_95) w_u_95 = u(A.min(), C.max()) / (1 - β_95) W_95 = np.linspace(w_l_95, w_u_95, N_95) @@ -1561,7 +1580,7 @@ s_W_0_95, _ = solve_static_problem(W_95 * (1 - β_95), u, A, Q, C, P, "unobserved-actions") in_time = time() -s_W_new, Pi_W_s1_new, Pi_W_m_s2_new, W_new = solve_multi_period_economy_vfi( +s_W_new, π_W_s1_new, π_W_m_s2_new, W_new = solve_multi_period_economy_vfi( A, Q, C, P, "unobserved-actions", β=β_95, N=N_95, N_m=N_m95, s_W_0=s_W_0_95 / (1 - β_95), @@ -1572,113 +1591,71 @@ print("Time(s):", round(out_time - in_time, 3)) ``` ```{code-cell} ipython3 -# Recover full joint distribution Pi(a, q, c, w' | w) -N_new = len(W_new) -A_ind_new = range(n_A) -Q_ind_new = range(n_Q) -C_ind_new = range(n_C) -W_ind_new = range(N_new) -Wp_ind_new = range(N_new) - -Pi_new = np.array([[[[[ - Pi_W_s1_new[wi, ai, qi, :] @ Pi_W_m_s2_new[:, ci, wpi] - for wpi in Wp_ind_new] - for ci in C_ind_new] - for qi in Q_ind_new] - for ai in A_ind_new] - for wi in W_ind_new]) +π_new = np.einsum("waqm,mcx->waqcx", π_W_s1_new, π_W_m_s2_new) ``` -```{code-cell} ipython3 -Ew_beta = ex_ut(Pi_new, A, Q, C, W_new) -``` +For the simulation, a state is a current promise grid point. + +Given that state, the code draws an action, then output, then a pair +$(c,w')$ from the joint lottery. + +The next period's state is the realized $w'$. ```{code-cell} ipython3 -def simulation(W, C, s_W, T, Pi, Ew, seed=12345): - # initial w such that s(w)=0 - w_index = np.argwhere(np.abs(s_W) == np.min(np.abs(s_W)))[0][0] - w0 = W[w_index] - date = np.arange(T) +def draw_from(probabilities, rng): + probabilities = np.maximum(np.asarray(probabilities, dtype=float), 0.0) + total = probabilities.sum() + if total <= 1e-12: + return rng.integers(len(probabilities)) + probabilities = probabilities / total + return min(np.searchsorted(np.cumsum(probabilities), rng.random()), + len(probabilities) - 1) + + +def simulation(W, C, s_W, T, π, seed=12345): + w_index = np.nanargmin(np.abs(s_W)) + rng = np.random.default_rng(seed) - # set seed for random number - np.random.seed(seed) - randn = np.random.rand(T, 8) - - w_index1, w_index2 = w_index, w_index - w_series = w0*np.ones(T+1) - c_series = np.zeros(T) - Pi_c = list(np.zeros(T)) - Pi_w = list(np.zeros(T)) + w_series = np.empty(T + 1) + c_series = np.empty(T) + w_series[0] = W[w_index] for i in range(T): + joint = np.maximum(π[w_index], 0.0) - w_index_temp1 = w_index1 - - Pi_temp_a = Pi[w_index_temp1, :, :, :, :].sum( - axis=1).sum( - axis=1).sum( - axis=1) - Pi_temp_a_cum = np.cumsum(Pi_temp_a / np.sum(Pi_temp_a)) - a_index = np.sum(randn[i, 0] >= Pi_temp_a_cum) - Pi_temp_q = Pi[w_index_temp1, a_index, :, :, :].sum( - axis=1).sum( - axis=1) - Pi_temp_q_cum = np.cumsum(Pi_temp_q / np.sum(Pi_temp_q)) - q_index = np.sum(randn[i, 1] >= Pi_temp_q_cum) + a_index = draw_from(joint.sum(axis=(1, 2, 3)), rng) + q_index = draw_from(joint[a_index].sum(axis=(1, 2)), rng) - Pi_temp_w = Pi[w_index_temp1, a_index, q_index, :, :].sum( - axis=0) - Pi_temp_w_cum = np.cumsum(Pi_temp_w/np.sum(Pi_temp_w)) - w_index1 = np.sum(randn[i, 2] >= Pi_temp_w_cum) + cw_prob = joint[a_index, q_index] + cw_index = draw_from(cw_prob.ravel(), rng) + c_index, w_next_index = np.unravel_index(cw_index, cw_prob.shape) - # simulation for consumption as well as its distribution - Pi_c[i] = Pi[w_index_temp1, a_index, q_index, :, w_index1] - Pi_c[i] /= np.sum(Pi_c[i]) - Pi_temp_c_cum = np.cumsum(Pi_c[i]) - c_index = np.sum(randn[i, 3] >= Pi_temp_c_cum) c_series[i] = C[c_index] - - # simulation for expected utility - w_series[i+1] = Ew[w_index_temp1, a_index, q_index] - - # simulation for distribution over future utility - Pi_temp_a = Pi[w_index2, :, :, :, :].sum(axis=1).sum( - axis=1).sum(axis=1) - Pi_temp_a_cum = np.cumsum(Pi_temp_a / np.sum(Pi_temp_a)) - a_index = np.sum(randn[i, 4] >= Pi_temp_a_cum) - Pi_temp_q = Pi[w_index2, a_index, :, :, :].sum( - axis=1).sum(axis=1) - Pi_temp_q_cum = np.cumsum(Pi_temp_q / np.sum(Pi_temp_q)) - q_index = np.sum(randn[i, 5] >= Pi_temp_q_cum) - Pi_temp_c = Pi[w_index2, a_index, q_index, :, :].sum(axis=1) - Pi_temp_c_cum = np.cumsum(Pi_temp_c/np.sum(Pi_temp_c)) - c_index = np.sum(randn[i, 6] >= Pi_temp_c_cum) - Pi_w[i] = Pi[w_index2, a_index, q_index, c_index, :] - Pi_w[i] /= np.sum(Pi_w[i]) - w_index2 = np.sum(randn[i,7] >= np.cumsum(Pi_w[i])) + w_index = w_next_index + w_series[i + 1] = W[w_index] - return c_series, w_series, Pi_w, Pi_c + return c_series, w_series ``` ```{code-cell} ipython3 c_series = np.zeros((80, 4)) w_series = np.zeros((81, 4)) for i in range(4): - c_series[:, i], w_series[:, i], _, _ = simulation( - W_new, C, s_W_new, 80, Pi_new, Ew_beta, seed=(12345 + i)) + c_series[:, i], w_series[:, i] = simulation( + W_new, C, s_W_new, 80, π_new, seed=(12345 + i)) ``` The simulations start from the grid point at which surplus is closest to zero. -This corresponds to the ex ante symmetric, or "fair", allocation -in the paper: it is the highest common promised utility that can be -assigned while keeping discounted social surplus nonnegative. +This corresponds to the ex ante symmetric, or "fair", allocation. + +It is the highest common promised utility that can be assigned while +keeping discounted social surplus nonnegative. -#### Figure 9 +#### Simulated consumption histories ```{code-cell} ipython3 -# Plot consumption simulation date_c = np.arange(80) + 1 plt.figure(figsize=(6.5, 6.5)) plt.plot(date_c, c_series[:, 0]) @@ -1689,14 +1666,20 @@ plt.xlabel("date") plt.ylabel("consumption") plt.xlim([0, 80]) plt.ylim([0.00, 2.25]) -plt.title("Figure 9\n Individual Consumptions ($\\beta=0.95$)", y=-0.2) +plt.title("Individual consumption histories ($\\beta=0.95$)", y=-0.2) plt.show() ``` -#### Figure 10 +The four consumption paths differ even though all agents begin with the +same promised utility. + +Different output histories move agents to different continuation +promises, so the contract gradually creates heterogeneous consumption +histories. + +#### Simulated promised utilities ```{code-cell} ipython3 -# Plot expected utility simulation date_w = np.arange(81) plt.figure(figsize=(6.5, 6.5)) plt.plot(date_w, w_series[:, 0]) @@ -1705,18 +1688,56 @@ plt.plot(date_w, w_series[:, 2]) plt.plot(date_w, w_series[:, 3]) plt.xlabel("date") plt.ylabel("expected utility") -plt.title("Figure 10\n Individual Utilities ($\\beta=0.95$)", y=-0.2) +plt.ylim([40.0, 100.0]) +plt.title("Individual promised utilities ($\\beta=0.95$)", y=-0.2) plt.show() ``` +The promised-utility paths show the state variable moving directly. + +High-output histories tend to move the agent upward, while low-output +histories move the agent downward. + +This is the dynamic incentive mechanism in the model. + ```{code-cell} ipython3 -%time _, _, Pi_w, Pi_c = simulation(W_new, C, s_W_new, 80, Pi_new, Ew_beta) +def population_distributions(W, C, s_W, T, π): + w_index = np.nanargmin(np.abs(s_W)) + μ = np.zeros(len(W)) + μ[w_index] = 1.0 + + π_pos = np.maximum(π, 0.0) + row_sums = π_pos.sum(axis=(1, 2, 3, 4), keepdims=True) + π_pos = np.divide(π_pos, row_sums, + out=np.zeros_like(π_pos), + where=row_sums > 1e-12) + + π_c = np.zeros((T, len(C))) + π_w = np.zeros((T, len(W))) + + for t in range(T): + joint = np.tensordot(μ, π_pos, axes=(0, 0)) + π_c[t] = joint.sum(axis=(0, 1, 3)) + μ = joint.sum(axis=(0, 1, 2)) + μ = μ / μ.sum() + π_w[t] = μ + + return π_c, π_w + + +π_c, π_w = population_distributions(W_new, C, s_W_new, 80, π_new) ``` -#### Figure 11 +The distribution calculation above keeps the whole population rather +than drawing individual sample paths. + +Starting from a point mass over $w$, it applies the optimal lottery each +period and records the implied marginal distributions of consumption and +promised utility. + +#### Cross-sectional consumption distributions ```{code-cell} ipython3 -# Plotting distribution for consumption %matplotlib inline date_mat_c = np.reshape(np.arange(80) + 1, (80, 1)) * \ @@ -1725,20 +1746,28 @@ c_mat = np.ones((80, 1)) @ np.reshape(C, (1, len(C))) fig = plt.figure(figsize=(8, 5)) ax = fig.add_subplot(projection='3d') -plt.title("Figure 11 \n Consumptions over time ($\\beta=0.95$)", y=-0.3) +plt.title("Consumption distribution over time ($\\beta=0.95$)", y=-0.3) plt.xlabel('date') plt.ylabel('consumption') ax.set_zlabel('percentage') -surf = ax.plot_surface(date_mat_c, c_mat, np.array(Pi_c), - cmap='viridis') +ax.set_zlim(0.0, 1.0) +ax.view_init(elev=25, azim=-65) +wire = ax.plot_wireframe(date_mat_c, c_mat, π_c, + rstride=1, cstride=2, + color="black", linewidth=0.35) plt.show() ``` -#### Figure 12 +The consumption distribution spreads out over time because histories +receive different rewards and punishments. + +On the finite grid, some mass eventually reaches the edges of the +feasible promise set. + +#### Cross-sectional promise distributions ```{code-cell} ipython3 -# Plotting distribution for future utilities %matplotlib inline date_mat_w = np.reshape(np.arange(80) + 1, (80, 1)) * \ np.ones((1, len(W_new))) @@ -1746,32 +1775,41 @@ W_mat_12 = np.ones((80, 1)) @ np.reshape(W_new, (1, len(W_new))) fig = plt.figure(figsize=(8, 5)) ax = fig.add_subplot(projection='3d') -plt.title("Figure 12 \n Utilities over time ($\\beta=0.95$)", y=-0.3) +plt.title("Promise distribution over time ($\\beta=0.95$)", y=-0.3) plt.xlabel('date') plt.ylabel('w') ax.set_zlabel('percentage') -surf = ax.plot_surface(date_mat_w, W_mat_12, np.array(Pi_w), - cmap='viridis') +ax.set_zlim(0.0, 1.0) +ax.view_init(elev=25, azim=-65) +wire = ax.plot_wireframe(date_mat_w, W_mat_12, π_w, + rstride=1, cstride=2, + color="black", linewidth=0.35) plt.show() ``` -## Concluding Remarks +The promise distribution is the deeper state-space picture behind the +consumption distribution. + +It shows how repeated incentives convert a common initial promise into a +distribution of continuation utilities. + +## Concluding remarks ### Economics -*Moral hazard and the cost of private information.* +#### Moral hazard and the cost of private information When the principal cannot observe the agent's effort, the optimal contract must balance two competing objectives: *insurance* (smoothing the agent's consumption across output realizations) and *incentives* (rewarding high output to make effort attractive). -The unobserved-action surplus function in Figure 1 lies everywhere below -the full-information frontier, and the gap between them measures the -surplus cost of unobserved effort. +The hidden-effort surplus frontier lies below the full-information +frontier, and the gap between them measures the surplus cost of +unobserved effort. -*Dynamic contracts and promised utility.* +#### Dynamic contracts and promised utility The recursive formulation of {cite}`Spear_Srivastava_87` compresses all payoff-relevant history into a single scalar state: the discounted @@ -1781,17 +1819,17 @@ agent. By tracking $w$ rather than the full history of outputs, the dynamic contracting problem becomes tractable. -Figures 7--8 show that under the optimal infinite-horizon contract the -principal rewards high output by granting the agent a higher continuation -utility and punishes low output by lowering it. +In the optimal infinite-horizon contract, the principal rewards high +output by granting the agent a higher continuation utility and punishes +low output by lowering it. Continuation promises therefore substitute partly for large contemporaneous consumption spreads. -*Diversity over time.* +#### Diversity over time -Figures 9--12 illustrate the paper's central computational message: +The simulations illustrate the central computational message: starting from a common initial promise, dynamic incentives generate non-trivial individual histories and cross-sectional dispersion in consumption and promised utility. @@ -1803,9 +1841,9 @@ The simulations should therefore be read as finite-grid illustrations of how history dependence spreads the distribution over time, not as a separate theorem about the limiting distribution. -### Technical Tricks +### Technical tricks -*Lotteries and convexification.* +#### Lotteries and convexification Incentive constraints can render the set of feasible contracts non-convex, making standard optimization techniques unreliable. @@ -1817,7 +1855,7 @@ consumptions, and continuation values. Because any mixture of feasible lotteries is itself feasible, the constraint set becomes convex, and global optima are well-defined. -*Linear programming.* +#### Linear programming With finite grids, the convexified Bellman equation is a linear program: the objective $(q - c + \beta v(w'))$ and every constraint are linear in @@ -1825,10 +1863,10 @@ $\Pi$. Treating $v(w')$ as a fixed vector from the previous iteration, value function iteration reduces to solving one LP per grid point per -iteration -- a task handled efficiently by modern LP solvers such as +iteration, a task handled efficiently by modern LP solvers such as HiGHS. -*Dynamic programming.* +#### Dynamic programming The promised-utility state variable $w$ makes the problem recursive. @@ -1839,20 +1877,20 @@ infinite-horizon fixed point. The implementation initializes the iteration from the scaled static solution, which is a useful numerical starting point. -*Two-step factored algorithm.* +#### Two-step factored algorithm The additive separability $U(a,c) = 2\sqrt{1-a} + 2\sqrt{c}$ allows the four-dimensional LP to be split into two smaller sub-problems. -*Step 2* allocates consumption given an intermediate promised utility -$w^m$; *Step 1* assigns actions, outputs, and intermediate continuation +Step 2 allocates consumption given an intermediate promised utility +$w^m$; Step 1 assigns actions, outputs, and intermediate continuation utilities given $w$. Because each sub-LP has far fewer decision variables than the full joint LP, computation is substantially faster and the approach scales to finer grids. -*Dynamic programming squared.* +#### Dynamic programming squared This lecture is closely related to what Lars Ljungqvist and Thomas Sargent call *dynamic programming squared* in @@ -1861,8 +1899,8 @@ Sargent call *dynamic programming squared* in The phrase refers to recursive problems in which one continuation object is carried as a state variable inside another recursive problem. -Here the surplus function $s(w)$ -- the solution to the principal's -outer dynamic program -- has the agent's continuation utility $w$ as its +Here the surplus function $s(w)$, the solution to the principal's +outer dynamic program, has the agent's continuation utility $w$ as its state variable, while feasible movements in $w$ are governed by promise-keeping and incentive constraints. @@ -1874,8 +1912,8 @@ function as an argument. In {doc}`Optimal Taxation with State-Contingent Debt `, a Ramsey planner's outer Bellman equation uses the household's -marginal utility of wealth $x$ -- itself defined by an inner -implementability constraint -- as its state variable. +marginal utility of wealth $x$, itself defined by an inner +implementability constraint, as its state variable. In the {doc}`Calvo model ` and the two Chang lectures ({doc}`Ramsey plans ` and @@ -1889,8 +1927,8 @@ as the state variable in an outer surplus-maximization program, producing a closely related nested recursive structure. In all of these settings, the inner dynamic program defines a -state variable -- a promised utility, a marginal value, or a -continuation value -- that restricts what the outer dynamic +state variable (a promised utility, a marginal value, or a +continuation value) that restricts what the outer dynamic program can promise or deliver. @@ -1926,11 +1964,12 @@ plt.xlabel("w") plt.ylabel(r"$\delta(w) = s^{FI}(w) - s^{UA}(w)$") plt.xlim([1.0, 5.0]) plt.ylim(bottom=0.0) -plt.title("Agency Cost in the Static Model", y=-0.2) +plt.title("Agency cost in the static model", y=-0.2) plt.show() -w_hat = W_static[np.argmax(delta_W)] -print(f"Largest agency cost at w = {w_hat:.3f}, δ = {delta_W.max():.4f}") +max_i = np.nanargmax(delta_W) +w_hat = W_static[max_i] +print(f"Largest agency cost at w = {w_hat:.3f}, δ = {delta_W[max_i]:.4f}") ``` Agency costs are highest near intermediate levels of promised utility @@ -1981,9 +2020,9 @@ P_flat = np.array([[0.70, 0.30], [0.45, 0.55], [0.30, 0.70]]) -s_W_flat, Pi_flat = solve_static_problem(W_static, u, A, Q, C, P_flat, +s_W_flat, π_flat = solve_static_problem(W_static, u, A, Q, C, P_flat, "unobserved-actions") -Ea_flat = np.einsum('a,waqc->w', A, Pi_flat) +Ea_flat = np.einsum('a,waqc->w', A, π_flat) fig, axes = plt.subplots(1, 2, figsize=(13, 6)) @@ -1993,7 +2032,7 @@ axes[0].hlines(0, 1.0, 5.0, linestyle="dashed") axes[0].set_xlabel("w") axes[0].set_ylabel("s(w)") axes[0].set_xlim([1.0, 5.0]) -axes[0].set_title("Surplus Function", y=-0.2) +axes[0].set_title("Surplus function", y=-0.2) axes[0].legend() axes[1].plot(W_static, Ea_unobs, label="Baseline $P$") @@ -2002,7 +2041,7 @@ axes[1].set_xlabel("w") axes[1].set_ylabel(r"$E\{a(w)\}$") axes[1].set_xlim([1.0, 5.0]) axes[1].set_ylim([0.0, 0.8]) -axes[1].set_title("Expected Effort", y=-0.2) +axes[1].set_title("Expected effort", y=-0.2) axes[1].legend() plt.tight_layout() @@ -2018,7 +2057,7 @@ harder to satisfy: large consumption rewards for high output must be offered to deter deviations, crowding out insurance. As a result the principal extracts less surplus and induces less effort -than under the baseline $P$ -- the surplus function shifts down and +than under the baseline $P$: the surplus function shifts down and expected effort falls. ```{solution-end} From 31291c5619f9323443d57bd18ac40c4bd03c027c Mon Sep 17 00:00:00 2001 From: HumphreyYang Date: Mon, 8 Jun 2026 21:43:24 +1000 Subject: [PATCH 10/25] updates --- lectures/_config.yml | 7 ------- 1 file changed, 7 deletions(-) diff --git a/lectures/_config.yml b/lectures/_config.yml index 5fb7444b..8493f847 100644 --- a/lectures/_config.yml +++ b/lectures/_config.yml @@ -33,13 +33,6 @@ latex: sphinx: extra_extensions: [sphinx_multitoc_numbering, sphinxext.rediraffe, sphinx_tojupyter, sphinx_exercise, sphinx_togglebutton, sphinx_proof, sphinx.ext.intersphinx] config: - exclude_patterns: - - _build - - _build/** - - .DS_Store - - "**.ipynb_checkpoints" - - lecture-python-programming.myst - - lecture-python-programming.myst/** intersphinx_mapping: intro: - "https://intro.quantecon.org/" From 623a9b7cabf8ceca583a6176eab964bd5b5fa99b Mon Sep 17 00:00:00 2001 From: HumphreyYang Date: Tue, 9 Jun 2026 17:29:10 +1000 Subject: [PATCH 11/25] updates --- lectures/_static/quant-econ.bib | 241 +-- lectures/_toc.yml | 1 + lectures/atkeson_1991.md | 654 +++--- lectures/repeat_mh.md | 1213 +++++------ .../subjective_beliefs_business_cycles.md | 1814 +++++++++++++++++ lectures/tsyrennikov_2013.md | 690 ++++--- 6 files changed, 3142 insertions(+), 1471 deletions(-) create mode 100644 lectures/subjective_beliefs_business_cycles.md diff --git a/lectures/_static/quant-econ.bib b/lectures/_static/quant-econ.bib index 5ac915e3..5349beed 100644 --- a/lectures/_static/quant-econ.bib +++ b/lectures/_static/quant-econ.bib @@ -3101,86 +3101,6 @@ @article{szoke2022estimating doi = {10.1016/j.jet.2021.105225} } -@article{Atkeson1991, - author = {Andrew Atkeson}, - title = {International Lending with Moral Hazard and Risk of Repudiation}, - journal = {Econometrica}, - year = {1991}, - volume = {59}, - number = {4}, - pages = {1069--1089} -} - -@article{APS1986, - author = {Dilip Abreu and David Pearce and Ennio Stacchetti}, - title = {Optimal Cartel Equilibria with Imperfect Monitoring}, - journal = {Journal of Economic Theory}, - year = {1986}, - volume = {39}, - number = {1}, - pages = {251--269} -} - -@article{BulowRogoff1989a, - author = {Jeremy Bulow and Kenneth Rogoff}, - title = {A Constant Recontracting Model of Sovereign Debt}, - journal = {Journal of Political Economy}, - year = {1989}, - volume = {97}, - number = {1}, - pages = {155--178} -} - -@article{BulowRogoff1989b, - author = {Jeremy Bulow and Kenneth Rogoff}, - title = {Sovereign Debt: Is to Forgive to Forget?}, - journal = {American Economic Review}, - year = {1989}, - volume = {79}, - number = {1}, - pages = {43--50} -} - -@article{GrossmanVanHuyck1988, - author = {Herschel I. Grossman and John B. Van Huyck}, - title = {Sovereign Debt as a Contingent Claim: Excusable Default, Repudiation, and Reputation}, - journal = {American Economic Review}, - year = {1988}, - volume = {78}, - number = {5}, - pages = {1088--1097} -} - -@article{GrossmanHart1983, - author = {Sanford J. Grossman and Oliver D. Hart}, - title = {An Analysis of the Principal-Agent Problem}, - journal = {Econometrica}, - year = {1983}, - volume = {51}, - number = {1}, - pages = {7--45} -} - -@article{Rogerson1985, - author = {William P. Rogerson}, - title = {Repeated Moral Hazard}, - journal = {Econometrica}, - year = {1985}, - volume = {53}, - number = {1}, - pages = {69--76} -} - -@article{FudenbergHolmstromMilgrom1990, - author = {Drew Fudenberg and Bengt Holmstrom and Paul Milgrom}, - title = {Short-Term Contracts and Long-Term Agency Relationships}, - journal = {Journal of Economic Theory}, - year = {1990}, - volume = {51}, - number = {1}, - pages = {1--31} -} - @article{EichengrehenPortes1986, author = {Barry Eichengreen and Richard Portes}, title = {Debt and Default in the 1930s: Causes and Consequences}, @@ -3191,90 +3111,103 @@ @article{EichengrehenPortes1986 pages = {599--640} } -@incollection{LindertMorton1989, - author = {Peter H. Lindert and Peter J. Morton}, - title = {How Sovereign Debt Has Worked}, - booktitle = {Developing Country Debt and Economic Performance, Vol.\ 1}, - editor = {Jeffrey D. Sachs}, - publisher = {University of Chicago Press}, - year = {1989}, - pages = {39--106} -} - -@article{Tsyrennikov2013, - author = {Viktor Tsyrennikov}, - title = {Capital flows under moral hazard}, - journal = {Journal of Monetary Economics}, - year = {2013}, - volume = {60}, - pages = {92--108} -} - -@article{GertlerRogoff1990, - author = {Mark Gertler and Kenneth Rogoff}, - title = {North-South lending and endogenous domestic capital market inefficiencies}, - journal = {Journal of Monetary Economics}, - year = {1990}, - volume = {26}, - pages = {245--266} +@article{bhandari2025survey, + title={Survey data and subjective beliefs in business cycle models}, + author={Bhandari, Anmol and Borovi{\v{c}}ka, Jaroslav and Ho, Paul}, + journal={Review of Economic Studies}, + volume={92}, + number={3}, + pages={1375--1437}, + year={2025}, + publisher={Oxford University Press UK} } -@article{AtkesonLucas1992, - author = {Andrew Atkeson and Robert E. Lucas}, - title = {On Efficient Distribution with Private Information}, - journal = {Review of Economic Studies}, - year = {1992}, - volume = {59}, - number = {3}, - pages = {427--453} +@article{BhandariBorovickaHo2024, + author = {Bhandari, Anmol and Borov{\v{c}}ka, Jaroslav and Ho, Paul}, + title = {Survey Data and Subjective Beliefs in Business Cycle Models}, + journal = {Review of Economic Studies}, + year = {2024}, + volume = {91}, + number = {3}, + pages = {1359--1395}, + doi = {10.1093/restud/rdad082}, + note = {NBER Working Paper No.\ 25192} } -@article{ThomasWorrall1990, - author = {Jonathan Thomas and Tim Worrall}, - title = {Income fluctuation and asymmetric information}, - journal = {Journal of Economic Theory}, - year = {1990}, - volume = {51}, - number = {2}, - pages = {367--390} +@article{IlutSchneider2014, + author = {Ilut, Cosmin L. and Schneider, Martin}, + title = {Ambiguous Business Cycles}, + journal = {American Economic Review}, + year = {2014}, + volume = {104}, + number = {8}, + pages = {2368--2399}, + doi = {10.1257/aer.104.8.2368} } -@article{EatonGersowitz1981, - author = {Jonathan Eaton and Mark Gersovitz}, - title = {Debt with Potential Repudiation: Theoretical and Empirical Analysis}, - journal = {Review of Economic Studies}, - year = {1981}, - volume = {48}, - number = {2}, - pages = {289--309} +@article{ChristianoEichenbaumTrabandt2016, + author = {Christiano, Lawrence J. and Eichenbaum, Martin S. + and Trabandt, Mathias}, + title = {Unemployment and Business Cycles}, + journal = {Econometrica}, + year = {2016}, + volume = {84}, + number = {4}, + pages = {1523--1569}, + doi = {10.3982/ECTA11776} } -@article{NeuemeyerPerri2005, - author = {Andr{\'e}s Neumeyer and Fabrizio Perri}, - title = {Business cycles in emerging economies: the role of interest rates}, - journal = {Journal of Monetary Economics}, - year = {2005}, - volume = {52}, - number = {2}, - pages = {345--380} +@article{Shimer2005, + author = {Shimer, Robert}, + title = {The Cyclical Behavior of Equilibrium Unemployment and Vacancies}, + journal = {American Economic Review}, + year = {2005}, + volume = {95}, + number = {1}, + pages = {25--49}, + doi = {10.1257/0002828053828572} } -@article{AguiarGopinath2006, - author = {Mark Aguiar and Gita Gopinath}, - title = {Defaultable Debt, Interest Rates and the Current Account}, - journal = {Journal of International Economics}, - year = {2006}, - volume = {69}, - number = {1}, - pages = {64--83} +@article{MankiwReisWolfers2003, + author = {Mankiw, N. Gregory and Reis, Ricardo and Wolfers, Justin}, + title = {Disagreement about Inflation Expectations}, + journal = {NBER Macroeconomics Annual}, + year = {2003}, + volume = {18}, + pages = {209--248}, + doi = {10.1086/ma.18.3585256} +} + +@article{RavennaWalsh2008, + author = {Ravenna, Federico and Walsh, Carl E.}, + title = {Vacancies, Unemployment, and the Phillips Curve}, + journal = {European Economic Review}, + year = {2008}, + volume = {52}, + number = {8}, + pages = {1494--1521}, + doi = {10.1016/j.euroecorev.2008.03.001} +} + +@article{BorovickaHansen2014, + author = {Borov{\v{c}}ka, Jaroslav and Hansen, Lars Peter}, + title = {Examining Macroeconomic Models through the Lens of Asset Pricing}, + journal = {Journal of Econometrics}, + year = {2014}, + volume = {183}, + number = {1}, + pages = {67--90}, + doi = {10.1016/j.jeconom.2014.06.006} } -@article{AguiarGopinath2007, - author = {Mark Aguiar and Gita Gopinath}, - title = {Emerging Market Business Cycles: The Cycle Is the Trend}, - journal = {Journal of Political Economy}, - year = {2007}, - volume = {115}, - number = {1}, - pages = {69--102} +@article{CoibionGorodnichenko2015, + author = {Coibion, Olivier and Gorodnichenko, Yuriy}, + title = {Information Rigidity and the Expectations Formation Process: + A Simple Framework and New Facts}, + journal = {American Economic Review}, + year = {2015}, + volume = {105}, + number = {8}, + pages = {2644--2678}, + doi = {10.1257/aer.20130921} } diff --git a/lectures/_toc.yml b/lectures/_toc.yml index 3d7c0f8d..ac6823b5 100644 --- a/lectures/_toc.yml +++ b/lectures/_toc.yml @@ -44,6 +44,7 @@ parts: - file: five_preferences - file: entropy - file: robustness + - file: subjective_beliefs_business_cycles - file: rob_markov_perf - caption: Time Series Models numbered: true diff --git a/lectures/atkeson_1991.md b/lectures/atkeson_1991.md index df107401..bf9fbc27 100644 --- a/lectures/atkeson_1991.md +++ b/lectures/atkeson_1991.md @@ -9,21 +9,13 @@ kernelspec: name: python3 --- -```{raw} html - -``` - (atkeson_1991)= # International Lending with Moral Hazard and Risk of Repudiation ## Overview -This lecture studies {cite}`Atkeson1991`, which examines the **constrained -optimal pattern of capital flows** between an international lender and a +This lecture studies {cite:t}`Atkeson1991`, which examines the *constrained +optimal pattern of capital flows* between an international lender and a sovereign borrower subject to two frictions: 1. **Moral hazard** — lenders cannot observe whether the borrower invests or @@ -31,22 +23,25 @@ sovereign borrower subject to two frictions: 2. **Risk of repudiation** — as a sovereign, the borrower can unilaterally renounce its debt at any time. -A central result is tha, under an optimal contract, a ''sudden stop'' or ''debt-crisis'' emerges in which a borrowing -country must **export capital** after suffering an adverse output shock. +A central result is that, under an optimal contract, a "sudden stop" or +"debt crisis" emerges in which a borrowing country must *export capital* +after suffering an adverse output shock. Outflows of capital after bad output realizations are the mechanism that incentivizes investment. The model extends recursive techniques from {cite}`APS1986`, {cite}`APS1990`, -and {cite}`Spear_Srivastava_87` to an environment with a **physical state -variable** that changes across periods. +and {cite}`Spear_Srivastava_87` to an environment with a *physical state +variable* that changes across periods. ```{note} -Atkeson (1991) uses $\delta$ for the discount factor. We follow the +Atkeson (1991) uses $\delta$ for the discount factor. + +We follow the QuantEcon convention and write $\beta$ throughout. ``` -## The Environment +## The environment ### Technology @@ -74,7 +69,7 @@ $i$** (monotone likelihood ratio property). This means low output is a relatively strong signal that the borrower invested little. -### Agents and Preferences +### Agents and preferences **The borrower** is an infinitely-lived, risk-averse agent with normalised discounted utility @@ -87,15 +82,19 @@ $$ where $u$ is strictly concave with $u'(0) = +\infty$. **Lenders** are a sequence of short-lived, risk-neutral agents, one born each -period. The lender born at $t$ extends loan $b_t$ when young and collects -state-contingent repayment $d_{t+1}(Y_{t+1})$ when old. A lender's +period. + +The lender born at $t$ extends loan $b_t$ when young and collects +state-contingent repayment $d_{t+1}(Y_{t+1})$ when old. + +A lender's participation (zero-profit) constraint is $$ -b_t + \beta \sum_{Y'} d_{t+1}(Y')\,g(Y';\,I_t) \;\geq\; 0. $$ -### State Variable and Feasibility +### State variable and feasibility Define @@ -117,7 +116,7 @@ $$ where $M$ is the lender's endowment per period. -### Autarky Value +### Autarky value The value the borrower can attain without credit access satisfies @@ -126,16 +125,16 @@ U^B_{\text{aut}}(Z) \;=\; \max_{I \in [0, Z]} \Bigl[(1-\beta)\,u(Z - I) + \beta \sum_{Y'} U^B_{\text{aut}}(Y')\,g(Y';\,I)\Bigr]. $$ -## Two Impediments to Contracting +## Two impediments to contracting -### Moral Hazard +### Moral hazard Because lenders can observe only $Y_t$, not the borrower's investment $I_t$, any feasible allocation must be **incentive compatible**: the borrower prefers the prescribed $(c_t, I_t)$ to any alternative consumption-investment plan given the loan and repayment schedule. -### Risk of Repudiation +### Risk of repudiation If the borrower repudiates its debt after $Y_{t+1}$ is realized, future lenders refuse credit and the borrower is confined to autarky. @@ -151,7 +150,7 @@ The continuation value of the contract — evaluated at the post-repayment state $Q_{t+1} = Y_{t+1} - d_{t+1}(Y_{t+1})$ — must weakly exceed what the borrower would obtain by repudiating and retaining all of $Y_{t+1}$. -## The Constrained Pareto Problem +## The constrained Pareto problem An allocation is **constrained Pareto optimal** if it maximises the borrower's payoff $U^B(\sigma)$ subject to: @@ -161,15 +160,15 @@ payoff $U^B(\sigma)$ subject to: 3. Immunity from repudiation 4. Incentive compatibility -## Recursive Formulation +## Recursive formulation -### Self-Generation and Factorization +### Self-generation and factorization Let $V(Q)$ be the set of payoffs the borrower can achieve from allocations satisfying constraints (1)–(4) when the state is $Q$. Atkeson adapts the -**self-generation** and **factorization** results of {cite}`APS1990` to this +**self-generation** and **factorization** results of {cite:t}`APS1990` to this setting with a physical state variable. Define a pair $(A, U)$ of current @@ -205,10 +204,10 @@ compatibility. Moreover, the optimal *continuation* value function equals $\bar{V}$ itself. -This mirrors Bellman's principle: the **continuation of the optimal contract -is itself optimal** at the updated state. +This mirrors Bellman's principle: the *continuation of the optimal contract +is itself optimal* at the updated state. -### Capital Outflows After Low Output +### Capital outflows after low output The first-order condition of the Lagrangian for Program P* with respect to the continuation value $U_d(Y_i')$ reveals that the no-repudiation Lagrange @@ -222,7 +221,7 @@ The ratio $g_1(Y_i';I)/g(Y_i';I)$ measures the likelihood that output $Y_i'$ signals *low* investment. By the monotone likelihood ratio property, this -ratio is largest for the **lowest output states**. +ratio is largest for the *lowest output states*. When $\mu_3(Y_i') > 0$, the no-repudiation constraint binds: $\bar{V}(Y_i' - d'(Y_i')) = @@ -242,53 +241,82 @@ $$ We illustrate these results with a binary-investment, two-output version of the model. +In addition to what's in Anaconda, this lecture will need the following library: + +```{code-cell} ipython3 +:tags: [hide-output] + +!pip install jax +``` + ### Setup ```{code-cell} ipython3 import numpy as np +from typing import NamedTuple from scipy.interpolate import interp1d -from scipy.optimize import minimize +from jax import config +config.update("jax_enable_x64", True) +import jax +import jax.numpy as jnp import matplotlib.pyplot as plt -plt.rcParams.update({'font.size': 12, 'figure.dpi': 100}) - -# ── Discount factor ────────────────────────────────────────────────────────── -β = 0.9 - -# ── Binary investment (high I_h or zero) ───────────────────────────────────── -I_h = 0.2 # resource cost of high investment - -# ── Output states ───────────────────────────────────────────────────────────── -Y_L, Y_H = 0.5, 1.0 +# Model parameters +class Model(NamedTuple): + β: float # discount factor + I_h: float # resource cost of high investment + Y_L: float # low output state + Y_H: float # high output state + M: float # lender endowment (b, −d ≤ M) + g_h: np.ndarray # g_h[j] = Pr(Y[j] | invest I_h) + g_l: np.ndarray # g_l[j] = Pr(Y[j] | invest 0) + + +def create_model(β=0.9, I_h=0.2, Y_L=0.5, Y_H=1.0, M=10.0, + g_h=(0.25, 0.75), g_l=(0.75, 0.25)): + """Build a model instance, validating the parameters.""" + if not 0 < β < 1: + raise ValueError("β must lie in (0, 1)") + if Y_L >= Y_H: + raise ValueError("require Y_L < Y_H") + g_h, g_l = np.asarray(g_h), np.asarray(g_l) + if not (np.isclose(g_h.sum(), 1.0) and np.isclose(g_l.sum(), 1.0)): + raise ValueError("g_h and g_l must each sum to 1") + return Model(β=β, I_h=I_h, Y_L=Y_L, Y_H=Y_H, M=M, g_h=g_h, g_l=g_l) + + +model = create_model() +β, I_h, Y_L, Y_H, M = model.β, model.I_h, model.Y_L, model.Y_H, model.M +g_h, g_l = model.g_h, model.g_l Y = np.array([Y_L, Y_H]) -# ── Output distributions ────────────────────────────────────────────────────── -# g_h[j] = Pr(Y[j] | invest I_h), g_l[j] = Pr(Y[j] | invest 0) -g_h = np.array([0.25, 0.75]) # high investment → likely high output -g_l = np.array([0.75, 0.25]) # low investment → likely low output - # Monotone likelihood ratio: g_l[0]/g_h[0] = 3 > 1 > g_l[1]/g_h[1] = 1/3 # → Y_L strongly signals low investment; Y_H signals high investment Δg = g_h - g_l # [−0.5, 0.5] -# ── Lender endowment (large: b, −d ≤ M) ────────────────────────────────────── -M = 10.0 - -# ── State grid: Q = Y − d (resources after repaying old debt) ───────────────── +# State grid: Q = Y − d (resources after repaying old debt) N_Q = 200 Q_MIN = 0.02 Q_MAX = 1.8 Q_grid = np.linspace(Q_MIN, Q_MAX, N_Q) +Q_grid_j = jnp.asarray(Q_grid) + +Qp_L_mesh, Qp_H_mesh = np.meshgrid(Q_grid, Q_grid, indexing='ij') +Qp_L_flat = jnp.asarray(Qp_L_mesh.ravel()) +Qp_H_flat = jnp.asarray(Qp_H_mesh.ravel()) -# ── Utility ──────────────────────────────────────────────────────────────────── +# Utility def u(c): return np.log(np.maximum(c, 1e-12)) +def u_jax(c): + return jnp.log(jnp.maximum(c, 1e-12)) + print(f"Likelihood ratios g_l / g_h : {g_l / g_h}") print(f"Y_L signals low investment with ratio {g_l[0]/g_h[0]:.1f}x") ``` -### Autarky Value Function +### Autarky value function In autarky the borrower has no access to credit. @@ -306,33 +334,46 @@ Note that the continuation values depend only on $Y_L$ and $Y_H$, not on the current $Q$, because next period's state is simply the realised output. ```{code-cell} ipython3 -def autarky_vfi(tol=1e-8, max_iter=3000): +@jax.jit +def autarky_step_jax(V, β_val, g_h_val, g_l_val): + """One vectorised Bellman step for the binary-investment autarky problem.""" + EV_h = jnp.dot(g_h_val, jnp.interp(jnp.asarray(Y), Q_grid_j, V)) + EV_l = jnp.dot(g_l_val, jnp.interp(jnp.asarray(Y), Q_grid_j, V)) + val_h = jnp.where( + Q_grid_j > I_h + 1e-10, + (1 - β_val) * u_jax(Q_grid_j - I_h) + β_val * EV_h, + -jnp.inf + ) + val_l = (1 - β_val) * u_jax(Q_grid_j) + β_val * EV_l + return jnp.maximum(val_h, val_l) + + +def autarky_vfi(β_val=None, g_h_val=None, g_l_val=None, tol=1e-8, max_iter=3000): """Value function iteration for the autarky problem (binary investment).""" - V = np.zeros(N_Q) - + if β_val is None: + β_val = β + if g_h_val is None: + g_h_val = g_h + if g_l_val is None: + g_l_val = g_l + + V = jnp.zeros(N_Q) + g_h_j = jnp.asarray(g_h_val) + g_l_j = jnp.asarray(g_l_val) for it in range(max_iter): - Vf = interp1d(Q_grid, V, fill_value='extrapolate', bounds_error=False) - EV_h = float(g_h @ Vf(Y)) # E[V(Y') | invest I_h] - EV_l = float(g_l @ Vf(Y)) # E[V(Y') | invest 0 ] - - V_new = np.empty(N_Q) - for k, Q in enumerate(Q_grid): - val_h = (1-β)*u(Q - I_h) + β*EV_h if Q > I_h + 1e-10 else -np.inf - val_l = (1-β)*u(Q) + β*EV_l - V_new[k] = max(val_h, val_l) - - diff = np.max(np.abs(V_new - V)) - V = V_new + V_new = autarky_step_jax(V, β_val, g_h_j, g_l_j) + diff = float(jnp.max(jnp.abs(V_new - V))) + V = V_new if diff < tol: print(f"Autarky VFI converged in {it+1} iterations (diff={diff:.2e})") break - return V + return np.asarray(V) V_aut = autarky_vfi() ``` -### Constrained Pareto Optimal Contract +### Constrained Pareto optimal contract We solve Program P* iteratively. @@ -340,7 +381,7 @@ At each state $Q$, the planner chooses continuation states $(Q'_L, Q'_H)$ — equivalently, state-contingent repayments $d_j = Y_j - Q'_j$ — to maximise the borrower's payoff. -Taking lender participation **binding** (Proposition 5 implies this is without +Taking lender participation *binding* (Proposition 5 implies this is without loss of generality), the loan is determined by $$ @@ -383,103 +424,100 @@ def find_Qmin(V_arr, v_thresh): return float(Q_grid[idx-1] + t * (Q_grid[idx] - Q_grid[idx-1])) -def pareto_bellman(V, V_aut_arr): +@jax.jit +def pareto_bellman_step_jax(V, V_aut_arr, β_val, g_h_val, g_l_val, Qp_min): + """ + One vectorised application of the constrained Pareto Bellman operator. + + The optimizer is a deterministic grid maximisation over all pairs + (Q'_L, Q'_H). JAX evaluates all states and all pairs in one compiled pass, + avoiding thousands of small SLSQP solves while preserving the NR, IC and + feasibility restrictions. + """ + Δg_val = g_h_val - g_l_val + + V_L = jnp.interp(Qp_L_flat, Q_grid_j, V) + V_H = jnp.interp(Qp_H_flat, Q_grid_j, V) + + b_pair = β_val * ( + g_h_val[0] * (Y_L - Qp_L_flat) + + g_h_val[1] * (Y_H - Qp_H_flat) + ) + EV_pair = g_h_val[0] * V_L + g_h_val[1] * V_H + IC_lhs = β_val * (Δg_val[0] * V_L + Δg_val[1] * V_H) + NR_mask = (Qp_L_flat >= Qp_min[0]) & (Qp_H_flat >= Qp_min[1]) + + c = Q_grid_j[:, None] + b_pair[None, :] - I_h + IC_rhs = (1 - β_val) * (u_jax(c + I_h) - u_jax(c)) + feasible = NR_mask[None, :] & (c >= 1e-10) & (IC_lhs[None, :] >= IC_rhs) + + obj = (1 - β_val) * u_jax(c) + β_val * EV_pair[None, :] + obj = jnp.where(feasible, obj, -jnp.inf) + + idx = jnp.argmax(obj, axis=1) + best_val = jnp.max(obj, axis=1) + has_feasible = jnp.isfinite(best_val) + + fallback = V_aut_arr + default_L = jnp.clip(jnp.maximum(Y_L, Qp_min[0]), Q_MIN, Q_MAX) + default_H = jnp.clip(jnp.maximum(Y_H, Qp_min[1]), Q_MIN, Q_MAX) + use_fallback = (~has_feasible) | (best_val <= fallback) + + pol_L = jnp.where(use_fallback, default_L, Qp_L_flat[idx]) + pol_H = jnp.where(use_fallback, default_H, Qp_H_flat[idx]) + V_new = jnp.where(use_fallback, fallback, best_val) + pol_b = β_val * (g_h_val[0] * (Y_L - pol_L) + + g_h_val[1] * (Y_H - pol_H)) + pol_Qp = jnp.column_stack((pol_L, pol_H)) + + return V_new, pol_b, pol_Qp + + +def pareto_bellman(V, V_aut_arr, β_val=None, g_h_val=None, g_l_val=None, + ε=0.0): """ One application of the constrained Pareto Bellman operator. Returns updated V, optimal loan policy pol_b, and continuation states pol_Qp[:,0] (after Y_L) and pol_Qp[:,1] (after Y_H). """ - Vf = interp1d(Q_grid, V, fill_value='extrapolate', - bounds_error=False) - Vaut_f = interp1d(Q_grid, V_aut_arr, fill_value='extrapolate', - bounds_error=False) - - # No-repudiation lower bounds on Q'_j - Vaut_Y = np.array([float(Vaut_f(yj)) for yj in Y]) - Qp_min = np.array([find_Qmin(V, v) for v in Vaut_Y]) - Qp_min = np.clip(Qp_min, Q_MIN, Q_MAX - 1e-4) - - V_new = np.empty(N_Q) - pol_b = np.empty(N_Q) - pol_Qp = np.empty((N_Q, 2)) - - for k, Q in enumerate(Q_grid): - - def bc(Qp): - """Compute loan b* and consumption c* from continuation states.""" - b = β * (g_h[0]*(Y_L - Qp[0]) + g_h[1]*(Y_H - Qp[1])) - c = Q + b - I_h - return b, c - - def neg_obj(Qp): - b, c = bc(Qp) - if c < 1e-10: - return 1e10 - VQp = np.array([float(Vf(Qp[0])), float(Vf(Qp[1]))]) - return -((1-β)*u(c) + β*(VQp @ g_h)) - - def ic_slack(Qp): - """IC: β Δg·V(Q') − (1−β)[u(c+I_h)−u(c)] ≥ 0.""" - b, c = bc(Qp) - if c < 1e-10: - return -1e10 - VQp = np.array([float(Vf(Qp[0])), float(Vf(Qp[1]))]) - return β*(Δg @ VQp) - (1-β)*(u(c + I_h) - u(c)) - - def feas_slack(Qp): - """Feasibility: c* ≥ 0.""" - _, c = bc(Qp) - return c - - constraints = [ - {'type': 'ineq', 'fun': ic_slack}, - {'type': 'ineq', 'fun': feas_slack}, - ] - bounds = [(Qp_min[0], Q_MAX), (Qp_min[1], Q_MAX)] - - # Candidate starting points - x_inits = [ - np.array([Qp_min[0], Qp_min[1]]), - np.array([min(Y_L * 0.9, Q_MAX), min(Y_H * 0.9, Q_MAX)]), - np.array([Qp_min[0], min(Y_H * 0.8, Q_MAX)]), - ] - - best_val = float(Vaut_f(Q)) # fallback: autarky payoff - best_Qp = np.array([min(max(Y_L, Qp_min[0]), Q_MAX), - min(max(Y_H, Qp_min[1]), Q_MAX)]) - - for x0 in x_inits: - x0 = np.clip(x0, [Qp_min[0], Qp_min[1]], - [Q_MAX - 1e-5, Q_MAX - 1e-5]) - try: - res = minimize( - neg_obj, x0, method='SLSQP', - bounds=bounds, constraints=constraints, - options={'ftol': 1e-10, 'maxiter': 400}) - if (res.success or res.status in (0, 9)): - val = -res.fun - if (val > best_val - and ic_slack(res.x) >= -1e-5 - and feas_slack(res.x) >= -1e-5): - best_val = val - best_Qp = res.x - except Exception: - pass - - V_new[k] = best_val - pol_Qp[k] = best_Qp - b_opt, _ = bc(best_Qp) - pol_b[k] = b_opt + if β_val is None: + β_val = β + if g_h_val is None: + g_h_val = g_h + if g_l_val is None: + g_l_val = g_l - return V_new, pol_b, pol_Qp + Vaut_f = interp1d(Q_grid, V_aut_arr, fill_value='extrapolate', + bounds_error=False) + Vaut_Y = np.array([float(Vaut_f(yj)) for yj in Y]) + ε + Qp_min = np.array([find_Qmin(V, v) for v in Vaut_Y]) + Qp_min = np.clip(Qp_min, Q_MIN, Q_MAX - 1e-4) + + V_new, pol_b, pol_Qp = pareto_bellman_step_jax( + jnp.asarray(V), jnp.asarray(V_aut_arr), β_val, + jnp.asarray(g_h_val), jnp.asarray(g_l_val), jnp.asarray(Qp_min) + ) + return np.asarray(V_new), np.asarray(pol_b), np.asarray(pol_Qp) -def pareto_vfi(V_aut_arr, tol=1e-3, max_iter=60): + +def pareto_vfi(V_aut_arr, β_val=None, g_h_val=None, g_l_val=None, + ε=0.0, tol=1e-3, max_iter=60, relaxation=0.2): """Value function iteration for Program P*.""" + if β_val is None: + β_val = β + if g_h_val is None: + g_h_val = g_h + if g_l_val is None: + g_l_val = g_l + V = V_aut_arr.copy() for it in range(max_iter): - V_new, pol_b, pol_Qp = pareto_bellman(V, V_aut_arr) + V_raw, pol_b, pol_Qp = pareto_bellman( + V, V_aut_arr, β_val=β_val, g_h_val=g_h_val, g_l_val=g_l_val, + ε=ε) + V_new = (1 - relaxation) * V + relaxation * V_raw diff = np.max(np.abs(V_new - V)) V = V_new print(f" iter {it+1:3d}, max|ΔV| = {diff:.5f}") @@ -494,45 +532,55 @@ print("Running constrained Pareto VFI …") V_pareto, pol_b, pol_Qp = pareto_vfi(V_aut) ``` -### Value Functions +### Value functions ```{code-cell} ipython3 -fig, ax = plt.subplots(figsize=(8, 5)) +--- +mystnb: + figure: + caption: autarky and optimal contract values + name: fig-atk-value +--- +fig, ax = plt.subplots() ax.plot(Q_grid, V_aut, lw=2, label=r'Autarky $U_{\rm aut}(Q)$') ax.plot(Q_grid, V_pareto, lw=2, ls='--', label=r'Optimal contract $\bar{V}(Q)$') -ax.set_xlabel(r'State $Q$ (output net of repayment)') -ax.set_ylabel('Normalised utility') -ax.set_title('Value Functions') +ax.set_xlabel(r'state $Q$ (output net of repayment)') +ax.set_ylabel('normalised utility') ax.legend() plt.tight_layout() plt.show() ``` -The optimal contract strictly dominates autarky, $\bar{V}(Q) > U_{\text{aut}}(Q)$, -because access to credit allows the borrower to share risk with lenders and -smooth consumption across output realisations. +The optimal contract weakly dominates autarky, and strictly improves on it +over the active borrowing region, because access to credit allows the borrower +to share risk with lenders and smooth consumption across output realisations. -### Optimal Continuation States and the No-Repudiation Constraint +### Optimal continuation states and the no-repudiation constraint ```{code-cell} ipython3 +--- +mystnb: + figure: + caption: continuation states and no-repudiation floors + name: fig-atk-continuation +--- # Compute no-repudiation floors Vaut_at_Y = np.array([float(interp1d(Q_grid, V_aut, fill_value='extrapolate', bounds_error=False)(yj)) for yj in Y]) Qp_min_L = find_Qmin(V_pareto, Vaut_at_Y[0]) Qp_min_H = find_Qmin(V_pareto, Vaut_at_Y[1]) -fig, axes = plt.subplots(1, 2, figsize=(12, 5)) +fig, axes = plt.subplots(1, 2) # Left: Q'_L (continuation state after low output) axes[0].plot(Q_grid, pol_Qp[:, 0], lw=2, label=r"$Q'_L = Y_L - d_L$") axes[0].axhline(Qp_min_L, ls='--', color='C3', label=fr"NR floor $Q^*_L \approx {Qp_min_L:.3f}$") -axes[0].set_xlabel(r'State $Q$') +axes[0].set_xlabel(r'state $Q$') axes[0].set_ylabel(r"$Q'_L$") -axes[0].set_title(r'Continuation state after low output $Y_L$') axes[0].legend() # Right: Q'_H (continuation state after high output) @@ -540,32 +588,44 @@ axes[1].plot(Q_grid, pol_Qp[:, 1], lw=2, color='C1', label=r"$Q'_H = Y_H - d_H$") axes[1].axhline(Qp_min_H, ls='--', color='C3', label=fr"NR floor $Q^*_H \approx {Qp_min_H:.3f}$") -axes[1].set_xlabel(r'State $Q$') +axes[1].set_xlabel(r'state $Q$') axes[1].set_ylabel(r"$Q'_H$") -axes[1].set_title(r'Continuation state after high output $Y_H$') axes[1].legend() plt.tight_layout() plt.show() ``` -After a **low-output** realisation, the continuation state $Q'_L$ is pinned at the no-repudiation floor $Q^*_L$. This means the repayment $d_L = Y_L - Q'_L$ is as large as the repudiation constraint allows. After a **high-output** realisation, $Q'_H > Q^*_H$: the constraint is slack and the borrower retains more resources, rewarding the high investment that produced good output. +After a *low-output* realisation, the continuation state $Q'_L$ is pinned +at the no-repudiation floor $Q^*_L$. + +This means the repayment $d_L = Y_L - Q'_L$ is as large as the +repudiation constraint allows. -### Optimal Loan and Net Capital Flows +After a *high-output* realisation, $Q'_H > Q^*_H$: the constraint is +slack and the borrower retains more resources, rewarding the high +investment that produced good output. + +### Optimal loan and net capital flows ```{code-cell} ipython3 +--- +mystnb: + figure: + caption: loan and net capital flows + name: fig-atk-loan-flows +--- # Repayments at the two output states as functions of current state Q d_L_policy = Y_L - pol_Qp[:, 0] # d_L(Q) = Y_L − Q'_L(Q) d_H_policy = Y_H - pol_Qp[:, 1] # d_H(Q) = Y_H − Q'_H(Q) -fig, axes = plt.subplots(1, 2, figsize=(12, 5)) +fig, axes = plt.subplots(1, 2) axes[0].plot(Q_grid, pol_b, lw=2, label='Loan $b^*(Q)$') axes[0].plot(Q_grid, d_L_policy, lw=2, ls='--', label=r'Repayment $d_L$') axes[0].plot(Q_grid, d_H_policy, lw=2, ls=':', label=r'Repayment $d_H$') axes[0].axhline(0, color='k', lw=0.6, ls=':') -axes[0].set_xlabel(r'State $Q$') -axes[0].set_title('Loan and Repayment Policies') +axes[0].set_xlabel(r'state $Q$') axes[0].legend() # Net capital outflow at continuation state @@ -576,23 +636,35 @@ net_out_H = d_H_policy - pol_b_fn(pol_Qp[:, 1]) axes[1].plot(Q_grid, net_out_L, lw=2, label=r'After $Y_L$ (low output)') axes[1].plot(Q_grid, net_out_H, lw=2, ls='--', label=r'After $Y_H$ (high output)') axes[1].axhline(0, color='k', lw=0.8, ls=':') -axes[1].set_xlabel(r'State $Q$') -axes[1].set_ylabel(r"Net outflow $d(Y') - b'(Q')$") -axes[1].set_title('Capital Flows in the Optimal Contract') +axes[1].set_xlabel(r'state $Q$') +axes[1].set_ylabel(r"net outflow $d(Y') - b'(Q')$") axes[1].legend() plt.tight_layout() plt.show() ``` -After a low-output realisation, $d_L > b'(Q'_L)$: the net capital flow is an -**outflow**. After a high-output realisation, the borrower typically receives -a net capital inflow. This is the numerical counterpart of Proposition 7 in -{cite}`Atkeson1991`. +Positive values in the right panel are net capital outflows. + +In this coarse +two-output calibration, the low-output continuation state is tightened sharply, +but new borrowing at that continuation state is still large enough that +$d_L - b'(Q'_L)$ is non-positive. + +Thus the computation should be read as a +small numerical illustration of the incentive mechanism, rather than a +calibration that delivers literal positive outflows after every low-output +realisation. ### Simulation ```{code-cell} ipython3 +--- +mystnb: + figure: + caption: simulated contract paths + name: fig-atk-simulation +--- def simulate_contract(V_pareto, pol_b, pol_Qp, T=150, seed=0): """ Simulate the constrained optimal contract. @@ -635,45 +707,49 @@ def simulate_contract(V_pareto, pol_b, pol_Qp, T=150, seed=0): sim = simulate_contract(V_pareto, pol_b, pol_Qp, T=150) t = np.arange(len(sim['Q'])) -fig, axes = plt.subplots(3, 1, figsize=(12, 9), sharex=True) +fig, axes = plt.subplots(3, 1, sharex=True) axes[0].plot(t, sim['Y'], alpha=0.6, label='Output $Y_{t+1}$') axes[0].plot(t, sim['c'], lw=1.8, label='Consumption $c_t$') -axes[0].set_ylabel('Level') +axes[0].set_ylabel('level') axes[0].legend(ncol=2, loc='upper right') -axes[0].set_title('Simulation of the Constrained Optimal Contract') axes[1].plot(t, sim['d'], lw=1.8, label='Repayment $d_t$') axes[1].plot(t, sim['b'], lw=1.8, ls='--', label='New loan $b_t$') axes[1].axhline(0, color='k', lw=0.5) -axes[1].set_ylabel('Level') +axes[1].set_ylabel('level') axes[1].legend(ncol=2) colors = ['#d73027' if x > 0 else '#4575b4' for x in sim['net_out']] axes[2].bar(t, sim['net_out'], color=colors, label='Net capital outflow') axes[2].axhline(0, color='k', lw=0.6) -axes[2].set_xlabel('Period $t$') -axes[2].set_ylabel('Net outflow') +axes[2].set_xlabel('period $t$') +axes[2].set_ylabel('net outflow') axes[2].legend() plt.tight_layout() plt.show() # Tabulate statistics -low_out_frac = np.mean(sim['net_out'] > 0) -print(f"\nFraction of periods with capital outflow: {low_out_frac:.2%}") +outflow_frac = np.mean(sim['net_out'] > 0) +print(f"\nFraction of periods with capital outflow: {outflow_frac:.2%}") print(f"Fraction of low-output periods: " f"{np.mean(sim['Y'] == Y_L):.2%}") ``` -Red bars (capital outflows) in the bottom panel systematically coincide with -periods in which low output $Y_L$ is realised, confirming the key prediction -of the model. +Positive bars are net capital outflows. + +With the baseline two-state +calibration above, the main visible effect of low output is a tighter +continuation state and lower subsequent borrowing; literal positive outflows +require a calibration in which the low-state repudiation constraint binds more +strongly. ## Exercises -````{admonition} Exercise 1 -:class: exercise +```{exercise-start} +:label: atkeson_1991_ex1 +``` **Patience and the severity of debt crises.** @@ -686,27 +762,25 @@ parameters fixed). 3. Plot $Q'_L(Q)$ for the three values of $\beta$ on a single figure. 4. Discuss: how does the borrower's patience affect how tightly the no-repudiation constraint binds after low output? -```` +```{exercise-end} +``` ```{solution-start} atkeson_1991_ex1 :class: dropdown ``` ```{code-cell} ipython3 -fig, ax = plt.subplots(figsize=(8, 5)) +--- +mystnb: + figure: + caption: continuation state across patience levels + name: fig-atk-patience +--- +fig, ax = plt.subplots() for β_val, ls, color in [(0.8, '-', 'C0'), (0.9, '--', 'C1'), (0.95, ':', 'C2')]: - β_orig = β - import builtins - # Temporarily override β in module scope - globals()['β'] = β_val - globals()['Δg'] = g_h - g_l # unchanged but recomputed for clarity - - V_a = autarky_vfi() - V_p, _, pQp = pareto_vfi(V_a) - - globals()['β'] = β_orig - globals()['Δg'] = g_h - g_l + V_a = autarky_vfi(β_val=β_val) + V_p, _, pQp = pareto_vfi(V_a, β_val=β_val) Vaut_fn_tmp = interp1d(Q_grid, V_a, fill_value='extrapolate', bounds_error=False) @@ -716,9 +790,8 @@ for β_val, ls, color in [(0.8, '-', 'C0'), (0.9, '--', 'C1'), (0.95, ':', 'C2') ax.plot(Q_grid, pQp[:, 0], ls=ls, color=color, label=fr'$\beta = {β_val}$ (NR floor $\approx {Qmin_L_tmp:.3f}$)') -ax.set_xlabel(r'State $Q$') +ax.set_xlabel(r'state $Q$') ax.set_ylabel(r"$Q'_L$ (continuation state after low output)") -ax.set_title('Effect of Patience on No-Repudiation Constraint') ax.legend() plt.tight_layout() plt.show() @@ -727,14 +800,17 @@ plt.show() More patient borrowers ($\beta$ closer to 1) value the continuation of the contract more highly, which relaxes the no-repudiation constraint: the no-repudiation floor $Q^*_L$ falls and the capital outflow after low output is -less severe. Impatient borrowers more readily prefer autarky, tightening the +less severe. + +Impatient borrowers more readily prefer autarky, tightening the constraint and worsening debt-crisis dynamics. ```{solution-end} ``` -````{admonition} Exercise 2 -:class: exercise +```{exercise-start} +:label: atkeson_1991_ex2 +``` **Signal quality and capital flows.** @@ -747,61 +823,64 @@ weaker signal of investment. of $Q$ for both the baseline and the weak-signal specification. 3. Explain intuitively why weaker signal quality changes the capital flow pattern. -```` +```{exercise-end} +``` ```{solution-start} atkeson_1991_ex2 :class: dropdown ``` ```{code-cell} ipython3 -g_h_base, g_l_base = g_h.copy(), g_l.copy() -Δg_base = Δg.copy() - +--- +mystnb: + figure: + caption: net outflow under weak signal + name: fig-atk-signal +--- # Weak-signal specification -globals()['g_h'] = np.array([0.40, 0.60]) -globals()['g_l'] = np.array([0.60, 0.40]) -globals()['Δg'] = g_h - g_l +g_h_ws = np.array([0.40, 0.60]) +g_l_ws = np.array([0.60, 0.40]) -print("Weak-signal likelihood ratios g_l/g_h:", g_l / g_h) +print("Weak-signal likelihood ratios g_l/g_h:", g_l_ws / g_h_ws) -V_aut_ws = autarky_vfi() -V_par_ws, pb_ws, pQp_ws = pareto_vfi(V_aut_ws) +V_aut_ws = autarky_vfi(g_h_val=g_h_ws, g_l_val=g_l_ws) +V_par_ws, pb_ws, pQp_ws = pareto_vfi( + V_aut_ws, g_h_val=g_h_ws, g_l_val=g_l_ws) pb_fn_ws = interp1d(Q_grid, pb_ws, fill_value='extrapolate', bounds_error=False) net_L_ws = (Y_L - pQp_ws[:, 0]) - pb_fn_ws(pQp_ws[:, 0]) net_H_ws = (Y_H - pQp_ws[:, 1]) - pb_fn_ws(pQp_ws[:, 1]) -# Restore baseline -globals()['g_h'] = g_h_base -globals()['g_l'] = g_l_base -globals()['Δg'] = Δg_base - pb_fn_bl = interp1d(Q_grid, pol_b, fill_value='extrapolate', bounds_error=False) net_L_bl = (Y_L - pol_Qp[:, 0]) - pb_fn_bl(pol_Qp[:, 0]) -fig, ax = plt.subplots(figsize=(8, 5)) +fig, ax = plt.subplots() ax.plot(Q_grid, net_L_bl, lw=2, label=r'After $Y_L$, baseline (strong signal)') ax.plot(Q_grid, net_L_ws, lw=2, ls='--', label=r'After $Y_L$, weak signal') ax.axhline(0, color='k', lw=0.8, ls=':') -ax.set_xlabel(r'State $Q$') -ax.set_ylabel('Net capital outflow') -ax.set_title('Capital Flows: Baseline vs Weak Signal') +ax.set_xlabel(r'state $Q$') +ax.set_ylabel('net capital outflow') ax.legend() plt.tight_layout() plt.show() ``` With a weaker signal ($g_l/g_h$ closer to 1), low output is less informative -about past investment. The moral hazard problem is milder, incentive +about past investment. + +The moral hazard problem is milder, incentive constraints are easier to satisfy, and the no-repudiation constraint binds less -tightly. Capital outflows after bad output realisations are smaller in +tightly. + +The net-flow response after bad output realisations is smaller in magnitude. ```{solution-end} ``` -````{admonition} Exercise 3 -:class: exercise +```{exercise-start} +:label: atkeson_1991_ex3 +``` **Debt forgiveness and welfare.** @@ -816,98 +895,33 @@ $\varepsilon > 0$. 3. Discuss: when is debt forgiveness welfare improving for the borrower? What is the cost to lenders? -*Hint:* implement the shift by adding $\varepsilon$ to `Vaut_at_Y` inside +*Hint:* implement the shift by adding $\varepsilon$ to `Vaut_Y` inside `pareto_bellman`. -```` +```{exercise-end} +``` ```{solution-start} atkeson_1991_ex3 :class: dropdown ``` ```{code-cell} ipython3 -fig, ax = plt.subplots(figsize=(8, 5)) - -for eps, ls, color in [(0.0, '-', 'C0'), (0.05, '--', 'C1'), (0.10, ':', 'C2')]: - - def pareto_bellman_shifted(V, V_aut_arr, epsilon=eps): - """Same as pareto_bellman but with tightened NR threshold.""" - Vf = interp1d(Q_grid, V, fill_value='extrapolate', - bounds_error=False) - Vaut_f = interp1d(Q_grid, V_aut_arr, fill_value='extrapolate', - bounds_error=False) - - Vaut_Y = np.array([float(Vaut_f(yj)) for yj in Y]) + epsilon - Qp_min = np.array([find_Qmin(V, v) for v in Vaut_Y]) - Qp_min = np.clip(Qp_min, Q_MIN, Q_MAX - 1e-4) - - V_new = np.empty(N_Q) - pol_b_ = np.empty(N_Q) - pol_Qp_= np.empty((N_Q, 2)) - - for k, Q in enumerate(Q_grid): - def bc(Qp): - b = β * (g_h[0]*(Y_L-Qp[0]) + g_h[1]*(Y_H-Qp[1])) - return b, Q + b - I_h - - def neg_obj(Qp): - b, c = bc(Qp) - if c < 1e-10: return 1e10 - VQp = np.array([float(Vf(Qp[0])), float(Vf(Qp[1]))]) - return -((1-β)*u(c) + β*(VQp @ g_h)) - - def ic_slack(Qp): - b, c = bc(Qp) - if c < 1e-10: return -1e10 - VQp = np.array([float(Vf(Qp[0])), float(Vf(Qp[1]))]) - return β*(Δg @ VQp) - (1-β)*(u(c+I_h) - u(c)) - - def feas_slack(Qp): - return bc(Qp)[1] - - constr = [{'type': 'ineq', 'fun': ic_slack}, - {'type': 'ineq', 'fun': feas_slack}] - bounds = [(Qp_min[0], Q_MAX), (Qp_min[1], Q_MAX)] - - best_val = float(Vaut_f(Q)) - epsilon # rough fallback - best_Qp = np.clip([max(Qp_min[0], Y_L), max(Qp_min[1], Y_H)], - [Qp_min[0], Qp_min[1]], [Q_MAX]*2) - - for x0 in [np.array([Qp_min[0], Qp_min[1]]), - np.array([min(Y_L, Q_MAX), min(Y_H*0.9, Q_MAX)])]: - x0 = np.clip(x0, [Qp_min[0], Qp_min[1]], [Q_MAX-1e-5]*2) - try: - res = minimize(neg_obj, x0, method='SLSQP', - bounds=bounds, constraints=constr, - options={'ftol': 1e-10, 'maxiter': 300}) - if (res.success or res.status in (0, 9)): - val = -res.fun - if val > best_val and ic_slack(res.x) >= -1e-5: - best_val = val - best_Qp = res.x - except Exception: - pass - - V_new[k] = best_val - pol_Qp_[k] = best_Qp - pol_b_[k] = bc(best_Qp)[0] - - return V_new, pol_b_, pol_Qp_ - - V_eps = V_aut.copy() - for it in range(50): - V_new_eps, _, _ = pareto_bellman_shifted(V_eps, V_aut, epsilon=eps) - diff = np.max(np.abs(V_new_eps - V_eps)) - V_eps = V_new_eps - if diff < 1e-3: - break +--- +mystnb: + figure: + caption: value functions under debt forgiveness + name: fig-atk-forgiveness +--- +fig, ax = plt.subplots() + +for ε, ls, color in [(0.0, '-', 'C0'), (0.05, '--', 'C1'), (0.10, ':', 'C2')]: + V_ε, _, _ = pareto_vfi(V_aut, ε=ε, max_iter=50) - ax.plot(Q_grid, V_eps, ls=ls, color=color, - label=fr'$\varepsilon = {eps}$') + ax.plot(Q_grid, V_ε, ls=ls, color=color, + label=fr'$\varepsilon = {ε}$') ax.plot(Q_grid, V_aut, lw=1, color='k', ls=':', label='Autarky') -ax.set_xlabel(r'State $Q$') +ax.set_xlabel(r'state $Q$') ax.set_ylabel(r'$\bar{V}(Q)$') -ax.set_title('Effect of Tighter Repudiation Constraint on Welfare') ax.legend() plt.tight_layout() plt.show() @@ -915,10 +929,12 @@ plt.show() Tightening the no-repudiation threshold ($\varepsilon > 0$) shrinks the set of feasible contracts, reducing $\bar{V}(Q)$. + Debt forgiveness improves the borrower's outside option but makes lenders less willing to extend credit (smaller loans at higher cost), leaving the borrower worse off in equilibrium. -This illustrates the {cite}`BulowRogoff1989b` result that debt forgiveness + +This illustrates the {cite:t}`BulowRogoff1989b` result that debt forgiveness need not benefit the borrowing country. ```{solution-end} diff --git a/lectures/repeat_mh.md b/lectures/repeat_mh.md index c52ddb34..c7ebaa58 100644 --- a/lectures/repeat_mh.md +++ b/lectures/repeat_mh.md @@ -11,7 +11,7 @@ kernelspec: language: python --- -# Repeated moral hazard +# Repeated Moral Hazard ## Overview @@ -33,7 +33,7 @@ linear programming to compute full-information, static unobserved-action, and repeated unobserved-action allocations. The lecture proceeds from the recursive formulation to the computational -implementation. +implementation: * We review the promised-utility recursion of {cite:t}`Spear_Srivastava_87`. @@ -162,6 +162,7 @@ $$ The two contracts $(c_H,c_L)=(1,0)$ and $(c_H,c_L)=(9,4)$ both satisfy this constraint. + But their midpoint, $(c_H,c_L)=(5,2)$, violates it because $$ @@ -188,7 +189,7 @@ the planner's problem becomes a finite-dimensional optimization problem with linear constraints. Each stage of the computation therefore amounts to solving a finite -**linear programming** problem. +**linear programming** (LP) problem. We begin with the finite objects in the planning problem. @@ -267,28 +268,24 @@ itself is a linear program. The algorithm solves one LP for each grid point $w \in W$ and iterates on the surplus function. -Their Theorem 4 gives the -contraction result that justifies this iteration for the -infinite-horizon problem. - -## Implementation - In addition to what's in Anaconda, this lecture will need the following libraries: ```{code-cell} ipython3 :tags: [hide-output] -!pip install cvxpy +!pip install quantecon cvxpy highspy ``` We import some Python packages. ```{code-cell} ipython3 -import numpy as np -import cvxpy as cp from time import time -import gc + +import cvxpy as cp +import highspy as hp import matplotlib.pyplot as plt +import numpy as np +import quantecon as qe ``` ## The static economy @@ -330,8 +327,9 @@ $$ \end{aligned} $$ -The **unobserved-action problem** adds incentive compatibility. For -each recommended action $a$ and each possible deviation $\hat a$, the +The **unobserved-action problem** adds incentive compatibility. + +For each recommended action $a$ and each possible deviation $\hat a$, the utility from obeying must be at least as large as the utility from deviating while preserving the same output-contingent consumption rule: @@ -373,8 +371,7 @@ These parameter values define the baseline numerical economy for the static comparisons and the first dynamic calculations. The static grid of promised utility values below spans the -interval $[1,5]$, covering the promise range emphasized in the -one-period analysis. +interval $[1,5]$. ```{code-cell} ipython3 def u(a, c): @@ -405,16 +402,7 @@ For the unobserved-action problem we add C4. ```{code-cell} ipython3 def solve_static_problem(W, u, A, Q, C, P, problem_type): - """ - Solve the static Phelan-Townsend LP on a grid of promises W. - - Returns - ------- - s_W : ndarray - Optimal surplus at each w in W. - π_W : ndarray - Lottery probabilities π_W[w_i, a_i, q_i, c_i]. - """ + """Solve the static LP on a grid of promised utilities.""" n_a, n_q, n_c = len(A), len(Q), len(C) A_i, Q_i = range(n_a), range(n_q) @@ -480,17 +468,7 @@ def solve_static_problem(W, u, A, Q, C, P, problem_type): ### Static allocations -```{note} -The original calculations used standard revised simplex methods. - -We use HiGHS through CVXPY. - -At degenerate utility grid points, different LP solvers can select -different optimal lotteries. - -Some consumption schedules can therefore differ slightly even when the -surplus function is unchanged. -``` +Now we solve the static problem for a grid of promised utility values. ```{code-cell} ipython3 W_static = np.linspace(1, 5, 100) @@ -509,8 +487,11 @@ interpretation. `π_full` and `π_unobs` store the optimal lotteries over $(a,q,c)$ at each promised utility. -Grid points outside a problem's feasible promise set are recorded as -`nan`, so Matplotlib leaves those parts of the graph blank. +The next helper turns a lottery over $(a,q,c)$ into conditional mean +consumption for each $(w,a,q)$. + +If the event $(a,q)$ has zero probability at a given $w$, the +conditional mean is undefined and is stored as `nan`. ```{code-cell} ipython3 def expected_consumption_static(π_W, C): @@ -523,15 +504,20 @@ def expected_consumption_static(π_W, C): ``` ```{code-cell} ipython3 -plt.figure(figsize=(6.5, 6.5)) -plt.plot(W_static, s_W_full, label="Full information") -plt.plot(W_static, s_W_unobs, label="Hidden effort") +--- +mystnb: + figure: + caption: static surplus frontiers + name: fig-rmh-static-surplus +--- +plt.figure() +plt.plot(W_static, s_W_full, label="Full information", lw=2) +plt.plot(W_static, s_W_unobs, label="Hidden effort", lw=2) plt.hlines(0, 1.0, 5.0, linestyle="dashed") plt.xlabel("w") plt.ylabel("s(w)") plt.xlim([1.0, 5.0]) plt.ylim([-1.5, 2.0]) -plt.title("Surplus frontiers", y=-0.2) plt.legend() plt.show() ``` @@ -544,24 +530,34 @@ induced with state-contingent rewards. The gap is the agency cost of private effort. +We can also look at the expected effort and consumption. + +The next figure plots expected effort as a function of the promise $w$ +under full information and under hidden effort. + ```{code-cell} ipython3 -Ea_full = np.einsum('a,waqc->w', A, π_full) +--- +mystnb: + figure: + caption: expected effort by promise + name: fig-rmh-static-effort +--- +Ea_full = np.einsum('a,waqc->w', A, π_full) Ea_unobs = np.einsum('a,waqc->w', A, π_unobs) -plt.figure(figsize=(6.5, 6.5)) -plt.plot(W_static, Ea_full, label="Full information") -plt.plot(W_static, Ea_unobs, label="Hidden effort") +plt.figure() +plt.plot(W_static, Ea_full, label="Full information", lw=2) +plt.plot(W_static, Ea_unobs, label="Hidden effort", lw=2) plt.xlabel("w") -plt.ylabel("E{a(w)}") +plt.ylabel(r"$E(a(w))$") plt.xlim([1.0, 5.0]) plt.ylim([0.0, 0.8]) -plt.title("Expected effort", y=-0.2) plt.legend() plt.show() ``` Here the code integrates the action grid against the lottery -probabilities, producing $E\{a(w)\}$. +probabilities, producing $E(a(w))$. Under full information, effort is chosen to maximize surplus at each promise. @@ -569,45 +565,73 @@ promise. With unobserved action, expected effort is lower where incentives are costly to provide. +Now we look at expected consumption as a function of the promise $w$, +the recommended action $a$, and the realized output $q$. + ```{code-cell} ipython3 +--- +mystnb: + figure: + caption: consumption under hidden effort + name: fig-rmh-static-cons-unobs +--- Ec_unobs = expected_consumption_static(π_unobs, C) -fig, axes = plt.subplots(1, len(Q), figsize=(11, 4), sharey=True) +fig, axes = plt.subplots(1, len(Q), sharey=True) for q_i, ax in enumerate(axes): for a_i, a in enumerate(A): - ax.plot(W_static, Ec_unobs[:, a_i, q_i], label=f"a={a:g}") - ax.set_title(f"q={Q[q_i]:g}") - ax.set_xlabel("w") + ax.plot(W_static, Ec_unobs[:, a_i, q_i], label=f"a={a:g}", lw=2) + ax.set_xlabel(f"w, q={Q[q_i]:g}") ax.set_xlim([1.0, 5.0]) ax.set_ylim([0.0, 2.25]) -axes[0].set_ylabel("E(c | w, a, q)") -axes[-1].legend(title="Action", loc="lower right") -fig.suptitle("Consumption when effort is hidden") +axes[0].set_ylabel(r"$E(c \mid w, a, q)$") +axes[-1].legend(title="action", loc="lower right") fig.tight_layout() plt.show() ``` -This cell conditions on the recommended action and realized output, then -computes expected consumption. +The gaps in the figure are important. + +A line is missing where the optimal contract puts zero probability on +that action-output pair $(a,q)$ at promise $w$. + +In those places, $E[c \mid w,a,q]$ is not defined because the event +being conditioned on never occurs. + +As $w$ rises, the set of actions used by the optimal contract changes. + +That is why some lines start and stop. + +The absent curve for $a=0.6$ means that action is never used on this +grid. + +When effort is hidden, full insurance would make the agent want to choose +a lower action. -The unobserved-action schedule uses current consumption as an incentive -device: high-output histories tend to receive higher consumption, while -low-output histories receive less. +So, when a positive action is recommended, high output must be rewarded +with higher consumption. + +That is why, on the parts of the graph where a line exists, consumption +is higher after $q=2$ than after $q=1$. ```{code-cell} ipython3 +--- +mystnb: + figure: + caption: consumption under full information + name: fig-rmh-static-cons-full +--- Ec_full = expected_consumption_static(π_full, C) -fig, axes = plt.subplots(1, len(Q), figsize=(11, 4), sharey=True) +fig, axes = plt.subplots(1, len(Q), sharey=True) for q_i, ax in enumerate(axes): for a_i, a in enumerate(A): - ax.plot(W_static, Ec_full[:, a_i, q_i], label=f"a={a:g}") - ax.set_title(f"q={Q[q_i]:g}") - ax.set_xlabel("w") + ax.plot(W_static, Ec_full[:, a_i, q_i], label=f"a={a:g}", lw=2) + ax.set_xlabel(f"w, q={Q[q_i]:g}") ax.set_xlim([1.0, 5.0]) ax.set_ylim([0.0, 2.25]) -axes[0].set_ylabel("E(c | w, a, q)") -axes[-1].legend(title="Action", loc="lower right") -fig.suptitle("Consumption under full information") +axes[0].set_ylabel(r"$E(c \mid w, a, q)$") +axes[-1].legend(title="action", loc="lower right") fig.tight_layout() plt.show() ``` @@ -617,10 +641,17 @@ With full information, output does not need to carry incentive rewards. Consumption therefore depends primarily on the promise $w$ rather than on output. +Notice also that the full-information plot has no gaps of the same kind. + +As $w$ rises, the planner switches from one action to another, but the +pieces join up and cover the promise range. + +This is because effort is observed: the planner can choose the action +directly. + ## The repeated economy -We now move from the one-period economy to finite- and infinite-horizon -contracts. +We now move from the one-period economy to infinite-horizon contracts. The planner maximizes discounted social surplus. @@ -677,17 +708,13 @@ Constraints C5--C8 are the dynamic analogues of C1--C4. * Constraint C8 is incentive compatibility. -For a finite horizon, the one-period surplus function is used to solve -the two-period problem, the two-period surplus function is used to solve -the three-period problem, and so on. - -For the infinite horizon, we -iterate on the Bellman operator until the surplus function is stable. +For the infinite horizon, we iterate on the Bellman operator until the +surplus function is stable. At each iteration, a separate LP is solved for each grid point $w \in W$. -### The two-step factored algorithm +### The two-step algorithm Solving the full LP over $(a,q,c,w')$ at each grid point is computationally demanding. @@ -700,12 +727,10 @@ $$ U(a, c) = 2\sqrt{1-a} + 2\sqrt{c}. $$ -#### Step 1: action and output +In the first sub-step, the planner chooses action, output, and +intermediate promised utility. -Before consumption is assigned, the planner chooses the action, output, -and intermediate promised utility. - -Let $w^m$ be the **intermediate** promised utility after the output +Let $w^m$ be the **intermediate promised utility** after the output is observed but before consumption is allocated. Thus $w^m$ includes @@ -740,7 +765,8 @@ $$ \end{aligned} $$ -#### Step 2: consumption allocation +In the second sub-step, the planner allocates current consumption and +next-period promised utility. Given $w^m$, solve @@ -773,344 +799,223 @@ to the exact solution. ### Functions -The function `solve_repeated_problem_2` implements one Bellman step using -the two-step algorithm. +We use HiGHS directly via the `highspy` bindings instead of building +each LP through CVXPY. + +HiGHS is a high-performance open-source solver for linear programming +and related optimization problems. + +`highspy` is its Python interface. + +It lets us keep the same LP model in memory and change only the small +parts that differ across grid points. + +In each Bellman iteration the two-step LPs differ only in the +promise-keeping right-hand side (and in the objective coefficients +between iterations). -The variables in the code follow the two sub-problems above. +Re-building a CVXPY problem object for every grid point and every +iteration adds substantial overhead and can leak memory. -`π_w_m` is the lottery over $(c,w')$ conditional on $w^m$, while -`π_w` is the lottery over $(a,q,w^m)$ conditional on $w$. +With `highspy` we build each LP once and then mutate the objective +and one row bound between solves. -The function `solve_multi_period_economy_2` then repeats this Bellman -step to convergence, or works backward for a fixed number of periods -$T$. +The first subproblem is Step 2. + +For a fixed intermediate promise $w^m$, Step 2 chooses a lottery over +current consumption and tomorrow's promise, $(c,w')$. + +The two constraints are simple: deliver $w^m$ and make probabilities sum +to one. ```{code-cell} ipython3 -def solve_repeated_problem_2(W=None, - W_m=None, - A=None, - Q=None, - C=None, - W_prime=None, - s_W_prime=None, - P=None, - problem_type=None, - β=0.8): - ''' - One Bellman update using the two-step algorithm. - - Parameters - ---------- - W: 1-D array - The expected utility. - A: 1-D array - The finite set of possible actions. - Q: 1-D array - The finite set of possible outputs. - C: 1-D array - The finite set of possible consumptions. - W_prime: 1-D array - The finite set of possible w_prime. - s_W_prime: 1-D array - The finite set of optimal values of surplus of w_prime. - P: 2-D array - The probability matrix of outputs given an action. - problem_type: str, "full information" or "unobserved-actions" - The problem type, i.e. the full information problem or the unobserved-action problem. - β: float, optional - The discount factor. The value is 0.8 by default. - - Returns - ------- - s_W: 1-D array - The optimal values of surplus for each w in w_vec. - π_W_s1: 4-D array - The probability of (a, q, w_m) given w. - π_W_m_s2: 3-D array - The probability of (c, w_prime) given w_m. - ''' - - n_A, n_Q, n_C, n_W = len(A), len(Q), len(C), len(W) - n_W_m, n_W_prime = len(W_m), len(W_prime) - A_ind, Q_ind, C_ind = range(n_A), range(n_Q), range(n_C) - W_ind, W_m_ind, W_prime_ind = range(n_W), range(n_W_m), range(n_W_prime) - - # Step 2 - Phi_s2 = np.array([[β * s_w_prime - c for s_w_prime in s_W_prime] for c in C]) - U_disc_s2 = np.array([[2 * c**0.5 + β * w_prime - for w_prime in W_prime] for c in C]) - - w_m_para = cp.Parameter() - π_w_m = cp.Variable((n_C, n_W_prime)) +def _build_step2_lp(C, W_prime, β): + """Build the Step 2 LP.""" + n_C, n_W = len(C), len(W_prime) + n_x = n_C * n_W + U_disc = np.array([2*c**0.5 + β*wp + for c in C for wp in W_prime]) + + h = hp.Highs() + h.silent() + h.setOptionValue("parallel", "off") + empty_i = np.array([], dtype=np.int32) + empty_d = np.array([], dtype=float) + h.addCols(n_x, np.zeros(n_x), np.zeros(n_x), np.full(n_x, hp.kHighsInf), + 0, empty_i, empty_i, empty_d) + + idx = np.arange(n_x, dtype=np.int32) + h.addRow(0.0, 0.0, n_x, idx, U_disc) # promise + h.addRow(1.0, 1.0, n_x, idx, np.ones(n_x)) # probability + return h, (n_C, n_W) +``` - obj_expr_s2 = cp.sum(cp.multiply(Phi_s2, π_w_m)) - obj_s2 = cp.Maximize(obj_expr_s2) - - C5_s2 = [cp.sum(cp.multiply(U_disc_s2, π_w_m)) == w_m_para] - C7_s2 = [cp.sum(π_w_m) == 1] + [π_w_m >= 0] - - problem_s2 = cp.Problem(obj_s2, C5_s2 + C7_s2) - - s_W_m = np.zeros(n_W_m) - π_W_m_s2 = np.zeros((n_W_m, n_C, n_W_prime)) - for w_m, w_m_ind in zip(W_m, W_m_ind): - w_m_para.value = w_m - problem_s2.solve(solver = cp.HIGHS) - if problem_s2.status not in (cp.OPTIMAL, cp.OPTIMAL_INACCURATE): - raise RuntimeError(f"Step 2 LP failed at w_m={w_m}: " - f"{problem_s2.status}") - s_W_m[w_m_ind] = obj_expr_s2.value - π_W_m_s2[w_m_ind, :, :] = π_w_m.value - - # Step 1 - Phi_s1 = np.array([[(q+s_w_m) for s_w_m in s_W_m] for q in Q]) - U_disc_s1 = np.array([[[2 * (1 - a)**0.5 + w_m - for w_m in W_m] for q in Q] for a in A]) - U_disc_hat_s1 = np.array([[[[(2 * (1 - A[a_hat_ind])**0.5 + W_m[w_m_ind]) *\ - P[a_hat_ind, q_ind]/P[a_ind, q_ind] - for w_m_ind in W_m_ind] for q_ind in Q_ind] - for a_ind in A_ind] - for a_hat_ind in A_ind]) - - w_para = cp.Parameter() +Step 1 uses the Step 2 value $s^m(w^m)$. - π_w = list(np.zeros(n_A)) - for a_ind in A_ind: - π_w[a_ind] = cp.Variable((n_Q, n_W_m)) - - obj_expr_s1 = cp.sum([cp.sum(cp.multiply(Phi_s1, π_w[a_ind])) - for a_ind in A_ind]) - obj_s1 = cp.Maximize(obj_expr_s1) - - C5_s1 = [cp.sum([cp.sum(cp.multiply(U_disc_s1[a_ind, :, :], - π_w[a_ind])) - for a_ind in A_ind]) == w_para] - C6_s1 = [(cp.sum(π_w[a_ind][q_ind, :]) == P[a_ind, q_ind] *\ - cp.sum(π_w[a_ind])) - for q_ind in Q_ind for a_ind in A_ind] - C7_s1 = [cp.sum([cp.sum(π_w[a_ind]) for a_ind in A_ind]) == 1] - C7_s1 = C7_s1 + [(π_w[a_ind] >= 0) for a_ind in A_ind] +It chooses a lottery over the recommended action, output, and +intermediate promise, $(a,q,w^m)$. - problem_type = problem_type.lower() - if problem_type == "full information": - constraints_s1 = C5_s1 + C6_s1 + C7_s1 - else: - C8_s1 = [(cp.sum(cp.multiply(U_disc_s1[a_ind, :, :], - π_w[a_ind])) >= - cp.sum(cp.multiply(U_disc_hat_s1[a_hat_ind, a_ind, :, :], - π_w[a_ind]))) - for a_ind in A_ind for a_hat_ind in A_ind] - constraints_s1 = C5_s1 + C6_s1 + C7_s1 + C8_s1 - - problem_s1 = cp.Problem(obj_s1, constraints_s1) - - s_W = np.zeros(n_W) - π_W_s1 = np.zeros((n_W, n_A, n_Q, n_W_m)) - for w, w_ind in zip(W, W_ind): - w_para.value = w - problem_s1.solve(solver = cp.HIGHS) - if problem_s1.status not in (cp.OPTIMAL, cp.OPTIMAL_INACCURATE): - raise RuntimeError(f"Step 1 LP failed at w={w}: " - f"{problem_s1.status}") - s_W[w_ind] = obj_expr_s1.value - for a_ind in A_ind: - π_W_s1[w_ind, a_ind, :, :] = π_w[a_ind].value - return s_W, π_W_s1, π_W_m_s2 +Here the fixed constraints enforce promise keeping, probabilities, +output probabilities, and, in the hidden-effort case, incentive +compatibility. + +```{code-cell} ipython3 +def _build_step1_lp(A, Q, W_m, P, problem_type): + """Build the Step 1 LP.""" + n_A, n_Q, n_W_m = len(A), len(Q), len(W_m) + n_x = n_A * n_Q * n_W_m + + def vid(a_i, q_i, m_i): + return (a_i * n_Q + q_i) * n_W_m + m_i + + U_aw = np.empty(n_x) + for a_i, a in enumerate(A): + ua = 2*(1 - a)**0.5 + for q_i in range(n_Q): + for m_i, wm in enumerate(W_m): + U_aw[vid(a_i, q_i, m_i)] = ua + wm + + h = hp.Highs() + h.silent() + h.setOptionValue("parallel", "off") + empty_i = np.array([], dtype=np.int32) + empty_d = np.array([], dtype=float) + h.addCols(n_x, np.zeros(n_x), np.zeros(n_x), np.full(n_x, hp.kHighsInf), + 0, empty_i, empty_i, empty_d) + + idx = np.arange(n_x, dtype=np.int32) + h.addRow(0.0, 0.0, n_x, idx, U_aw) # promise + h.addRow(1.0, 1.0, n_x, idx, np.ones(n_x)) # probability + + # Output law + for a_i in range(n_A): + for q_i in range(n_Q): + row = np.zeros(n_x) + for m_i in range(n_W_m): + row[vid(a_i, q_i, m_i)] += 1.0 + for q_j in range(n_Q): + for m_i in range(n_W_m): + row[vid(a_i, q_j, m_i)] -= P[a_i, q_i] + nz = np.flatnonzero(row) + h.addRow(0.0, 0.0, nz.size, + nz.astype(np.int32), row[nz]) + + # Incentive compatibility + if problem_type.lower() != "full information": + for a_i in range(n_A): + ua = 2*(1 - A[a_i])**0.5 + for a_hat in range(n_A): + if a_hat == a_i: + continue + ua_h = 2*(1 - A[a_hat])**0.5 + row = np.zeros(n_x) + for q_i in range(n_Q): + ratio = P[a_hat, q_i] / P[a_i, q_i] + for m_i, wm in enumerate(W_m): + coef = (ua + wm) - (ua_h + wm) * ratio + row[vid(a_i, q_i, m_i)] -= coef + nz = np.flatnonzero(row) + h.addRow(-hp.kHighsInf, 0.0, nz.size, + nz.astype(np.int32), row[nz]) + return h, (n_A, n_Q, n_W_m) ``` +Once the constraint matrices are built, each Bellman iteration only +changes objective coefficients. + +The next helpers update those coefficients in place. + +HiGHS minimizes by default, so we store the negative of the surplus +objective. + ```{code-cell} ipython3 -def solve_multi_period_economy_2(A=None, - Q=None, - C=None, - P=None, - problem_type=None, - T=None, - β=0.8, - N=100, - N_m=100, - s_W_0=None, - tol=1e-8, - verbose=False): - ''' - Solve the finite- or infinite-horizon economy. - - Parameters - ---------- - A: 1-D array - The finite set of possible actions. - Q: 1-D array - The finite set of possible outputs. - C: 1-D array - The finite set of possible consumptions. - P: 2-D array - The probability matrix of outputs given an action. - problem_type: str, "full information" or "unobserved-actions" - The problem type, i.e. the full information problem or the unobserved-action problem. - T: int, optional - The number of periods. If T is None, the algorithm solves the infinite-period economy. If T is some - integer, the algorithm solves the T-period economy. By default, T is None. - β: float, optional - The discount factor in (0,1). The value is 0.8 by default. - N: int, optional - The length of discretized parameter space W. - N_m: int, optional - The length of discretized parameter space W_m. - s_W_0: 1-D array, optional - The initial guess for s_W with a length of N. - tol: float, optional - The precision of convergence. - verbose: bool, optional - If True, print progress at each iteration. The default is False. - - Returns - ------- - s_W: 1-D array - The optimal values of convergent surplus for each w in w_vec. - π_W_s1: 4-D array - The probability of (a, q, w_m) given w. - π_W_m_s2: 3-D array - The probability of (c, w_prime) given w_m. - ''' - - if β >= 1 or β <= 0: - raise ValueError('β must lie in (0, 1)') - - def u(a, c): - return c**0.5/0.5 + (1-a)**0.5/0.5 - - if T is None: - problem_type = problem_type.lower() - if problem_type == "full information": - w_l = u(A.max(), C.min())/(1 - β) - w_u = u(A.min(), C.max())/(1 - β) - else: - w_l = u(A.min(), C.min())/(1 - β) - w_u = u(A.min(), C.max())/(1 - β) - W = np.linspace(w_l, w_u, N) - - W_m_l = β * w_l + 2 * C.min()**0.5 - W_m_u = β * w_u + 2 * C.max()**0.5 - W_m = np.linspace(W_m_l, W_m_u, N_m) +def _update_step2_objective(h, C, W_prime, s_W_prime, β): + n_C, n_W = len(C), len(W_prime) + obj = -(β * np.broadcast_to(s_W_prime, (n_C, n_W)) + - np.asarray(C)[:, None]).ravel() + cols = np.arange(obj.size, dtype=np.int32) + h.changeColsCost(cols.size, cols, obj) + h.clearSolver() + + +def _update_step1_objective(h, A, Q, W_m, s_W_m): + n_A, n_Q, n_W_m = len(A), len(Q), len(W_m) + Φ = np.asarray(Q)[:, None] + np.asarray(s_W_m)[None, :] + obj = -np.broadcast_to(Φ, (n_A, n_Q, n_W_m)).ravel() + cols = np.arange(obj.size, dtype=np.int32) + h.changeColsCost(cols.size, cols, obj) + h.clearSolver() +``` - if s_W_0 is not None: - s_W_prime = s_W_0 - else: - s_W_prime = np.zeros(N) +Finally we solve the two subproblems across their promise grids. - optimal = False - iteration = 1 - while not optimal: - if verbose: - print('Iteration %i in process' % iteration) - start_time = time() - s_W, π_W_s1, π_W_m_s2 = solve_repeated_problem_2(W=W, W_m=W_m, - A=A, Q=Q, - C=C, W_prime=W, - s_W_prime=s_W_prime, - P=P, - problem_type=problem_type, - β=β) - end_time = time() - if verbose: - print('Iteration %i finished in:' % iteration, - round(end_time - start_time, 3), 's') - print('---') - - if np.max(np.abs(s_W - s_W_prime)) <= tol: - optimal = True - else: - s_W_prime = s_W - - iteration += 1 - - if T is not None: - W_mat = np.zeros((T, N)) - - problem_type = problem_type.lower() - if problem_type == "full information": - w_l = u(A.max(), C.min()) - w_u = u(A.min(), C.max()) - else: - w_l = u(A.min(), C.min()) - w_u = u(A.min(), C.max()) - W_mat = np.cumsum(np.logspace(0, T-1, T, base=β).reshape(T, 1) *\ - np.linspace(w_l, w_u, N).reshape(1, N), - axis=0) - - if verbose: - print('Solving the 1-period economy') - print('---') - s_W, π = solve_static_problem(W=W_mat[0, :], u=u, - A=A, Q=Q, C=C, P=P, - problem_type=problem_type) - - if T != 1: - for t in range(2, T+1): - if verbose: - print('Solving the %i-period economy' % t) - print('---') - s_W_prime = np.copy(s_W) - W_m_l = β*W_mat[t-2,:].min() + 2*C.min()**0.5 - W_m_u = β*W_mat[t-2,:].max() + 2*C.max()**0.5 - W_m = np.linspace(W_m_l, W_m_u, N_m) - s_W, π_W_s1, π_W_m_s2 = solve_repeated_problem_2(W=W_mat[t-1,:], - W_m=W_m, A=A, - Q=Q, C=C, - W_prime=W_mat[t-2,:], - s_W_prime=s_W_prime, - P=P, - problem_type=problem_type, - β=β) - return s_W, π_W_s1, π_W_m_s2 +The promise-keeping constraint is row 0 in both LPs. + +So at each grid point we only change the lower and upper bound of row 0, +then resolve the same model. + +```{code-cell} ipython3 + + +def _solve_step2(h, shape, W_m): + n_C, n_W = shape + n_W_m = len(W_m) + s_W_m = np.empty(n_W_m) + π_W_m_s2 = np.empty((n_W_m, n_C, n_W)) + for i, wm in enumerate(W_m): + h.changeRowBounds(0, wm, wm) + h.run() + st = h.getModelStatus() + if st != hp.HighsModelStatus.kOptimal: + raise RuntimeError(f"Step 2 LP not optimal at w_m={wm}: {st}") + s_W_m[i] = -h.getObjectiveValue() + π_W_m_s2[i] = np.asarray(h.getSolution().col_value).reshape(n_C, n_W) + return s_W_m, π_W_m_s2 + + +def _solve_step1(h, shape, W): + n_A, n_Q, n_W_m = shape + s_W = np.empty(len(W)) + π_W_s1 = np.empty((len(W), n_A, n_Q, n_W_m)) + for i, w in enumerate(W): + h.changeRowBounds(0, w, w) + h.run() + st = h.getModelStatus() + if st != hp.HighsModelStatus.kOptimal: + raise RuntimeError(f"Step 1 LP not optimal at w={w}: {st}") + s_W[i] = -h.getObjectiveValue() + π_W_s1[i] = (np.asarray(h.getSolution().col_value) + .reshape(n_A, n_Q, n_W_m)) + return s_W, π_W_s1 ``` -### Improved solver: pre-built problems with Anderson acceleration +### Repeated-economy solver -The original solver rebuilds all CVXPY problem objects on every -Bellman iteration, which causes memory to accumulate when many -iterations are needed. +The solver below builds the Step 1 and Step 2 LPs once. -This is a serious issue for $\beta$ close to 1. +After that, each Bellman iteration only changes the objective +coefficients, and each promise-grid point only changes one row bound. -The function `solve_multi_period_economy_vfi` fixes this by building -the two sub-problems *once* with CVXPY `Parameter` objects for the -components that change between iterations ($\Phi^{s2}$ and $\Phi^{s1}$). +It also uses Anderson acceleration with a short history of recent +surplus functions. -It also applies *Anderson acceleration* with a history of $m$ -recent iterates to speed up convergence, and accepts a `max_iter` -cap to prevent infinite loops. +This often reduces the number of Bellman iterations. ```{code-cell} ipython3 -def solve_multi_period_economy_vfi(A=None, - Q=None, - C=None, - P=None, - problem_type=None, - β=0.95, - N=50, - N_m=50, - s_W_0=None, - tol=1e-4, - max_iter=300, - m_anderson=5, - verbose=True): - """ - Infinite-horizon VFI using the two-step factored algorithm. - - Improvements over solve_multi_period_economy_2: - * CVXPY problems are built once with Parameter objects, - so there is no memory leak across iterations. - * Anderson acceleration (window m_anderson) reduces - the number of Bellman iterations needed. - * max_iter cap prevents unbounded runtime. - - Returns - ------- - s_W : 1-D array, converged surplus function on W - π_W_s1 : 4-D array, π(a, q, w_m | w) - π_W_m_s2 : 3-D array, π(c, w' | w_m) - W : 1-D array, the utility grid used - """ +def solve_multi_period_economy(A=None, + Q=None, + C=None, + P=None, + problem_type=None, + β=0.95, + N=50, + N_m=50, + s_W_0=None, + tol=1e-4, + max_iter=300, + m_anderson=5, + verbose=True): + """Solve the infinite-horizon problem with reusable HiGHS models.""" if β >= 1 or β <= 0: raise ValueError('β must lie in (0, 1)') @@ -1125,58 +1030,12 @@ def solve_multi_period_economy_vfi(A=None, w_l = u_fn(A.min(), C.min()) / (1 - β) w_u = u_fn(A.min(), C.max()) / (1 - β) - W = np.linspace(w_l, w_u, N) - W_m = np.linspace(β * w_l + 2 * C.min()**0.5, - β * w_u + 2 * C.max()**0.5, N_m) - - n_A, n_Q, n_C = len(A), len(Q), len(C) - A_ind, Q_ind = range(n_A), range(n_Q) - - # Terms that do not change across iterations. - U_disc_s2 = np.array([[2 * c**0.5 + β * wp - for wp in W] for c in C]) - U_disc_s1 = np.array([[[2 * (1 - a)**0.5 + wm - for wm in W_m] for q in Q] - for a in A]) - U_disc_hat_s1 = np.array([[[[ - (2 * (1 - A[ah])**0.5 + W_m[wmi]) * P[ah, qi] / P[ai, qi] - for wmi in range(N_m)] for qi in Q_ind] - for ai in A_ind] for ah in A_ind]) - - # Step 2 problem. - Phi_s2_param = cp.Parameter((n_C, N)) - w_m_para = cp.Parameter() - π_w_m = cp.Variable((n_C, N)) - - obj_expr_s2 = cp.sum(cp.multiply(Phi_s2_param, π_w_m)) - C5_s2 = [cp.sum(cp.multiply(U_disc_s2, π_w_m)) == w_m_para] - C7_s2 = [cp.sum(π_w_m) == 1, π_w_m >= 0] - problem_s2 = cp.Problem(cp.Maximize(obj_expr_s2), C5_s2 + C7_s2) - - # Step 1 problem. - Phi_s1_param = cp.Parameter((n_Q, N_m)) - w_para = cp.Parameter() - π_w = [cp.Variable((n_Q, N_m)) for _ in A_ind] - - obj_expr_s1 = cp.sum([cp.sum(cp.multiply(Phi_s1_param, π_w[ai])) - for ai in A_ind]) - C5_s1 = [cp.sum([cp.sum(cp.multiply(U_disc_s1[ai], π_w[ai])) - for ai in A_ind]) == w_para] - C6_s1 = [(cp.sum(π_w[ai][qi, :]) == - P[ai, qi] * cp.sum(π_w[ai])) - for qi in Q_ind for ai in A_ind] - C7_s1 = ([cp.sum([cp.sum(π_w[ai]) for ai in A_ind]) == 1] + - [π_w[ai] >= 0 for ai in A_ind]) + W = np.linspace(w_l, w_u, N) + W_m = np.linspace(β * w_l + 2 * C.min()**0.5, + β * w_u + 2 * C.max()**0.5, N_m) - if problem_type == "full information": - constraints_s1 = C5_s1 + C6_s1 + C7_s1 - else: - C8_s1 = [(cp.sum(cp.multiply(U_disc_s1[ai], π_w[ai])) >= - cp.sum(cp.multiply(U_disc_hat_s1[ah, ai], π_w[ai]))) - for ai in A_ind for ah in A_ind] - constraints_s1 = C5_s1 + C6_s1 + C7_s1 + C8_s1 - - problem_s1 = cp.Problem(cp.Maximize(obj_expr_s1), constraints_s1) + step2_lp, shape2 = _build_step2_lp(C, W, β) + step1_lp, shape1 = _build_step1_lp(A, Q, W_m, P, problem_type) s_W_prime = (np.array(s_W_0, dtype=float) if s_W_0 is not None else np.zeros(N)) @@ -1187,34 +1046,11 @@ def solve_multi_period_economy_vfi(A=None, for iteration in range(1, max_iter + 1): t0 = time() - # Step 2. - Phi_s2_param.value = np.array([[β * sv - c - for sv in s_W_prime] for c in C]) - s_W_m = np.zeros(N_m) - π_W_m_s2 = np.zeros((N_m, n_C, N)) - for i, wm in enumerate(W_m): - w_m_para.value = wm - problem_s2.solve(solver=cp.HIGHS, warm_start=True) - if problem_s2.status not in (cp.OPTIMAL, cp.OPTIMAL_INACCURATE): - raise RuntimeError(f"Step 2 LP failed at w_m={wm}: " - f"{problem_s2.status}") - s_W_m[i] = obj_expr_s2.value - π_W_m_s2[i] = π_w_m.value - - # Step 1. - Phi_s1_param.value = np.array([[(q + swm) - for swm in s_W_m] for q in Q]) - s_W = np.zeros(N) - π_W_s1 = np.zeros((N, n_A, n_Q, N_m)) - for i, w in enumerate(W): - w_para.value = w - problem_s1.solve(solver=cp.HIGHS, warm_start=True) - if problem_s1.status not in (cp.OPTIMAL, cp.OPTIMAL_INACCURATE): - raise RuntimeError(f"Step 1 LP failed at w={w}: " - f"{problem_s1.status}") - s_W[i] = obj_expr_s1.value - for ai in A_ind: - π_W_s1[i, ai] = π_w[ai].value + _update_step2_objective(step2_lp, C, W, s_W_prime, β) + s_W_m, π_W_m_s2 = _solve_step2(step2_lp, shape2, W_m) + + _update_step1_objective(step1_lp, A, Q, W_m, s_W_m) + s_W, π_W_s1 = _solve_step1(step1_lp, shape1, W) t1 = time() err = np.max(np.abs(s_W - s_W_prime)) @@ -1235,16 +1071,16 @@ def solve_multi_period_economy_vfi(A=None, hist_fx.pop(0) if mk >= 2: - X = np.column_stack(hist_x[-mk:]) - FX = np.column_stack(hist_fx[-mk:]) - F = FX - X + X = np.column_stack(hist_x[-mk:]) + FX = np.column_stack(hist_fx[-mk:]) + F = FX - X FtF = F.T @ F reg = max(1e-10 * np.trace(FtF) / mk, 1e-14) ones = np.ones(mk) try: - theta = np.linalg.solve(FtF + reg * np.eye(mk), ones) - theta /= ones @ theta - s_candidate = FX @ theta + θ = np.linalg.solve(FtF + reg * np.eye(mk), ones) + θ /= ones @ θ + s_candidate = FX @ θ s_next = (s_candidate if np.all(np.isfinite(s_candidate)) else s_W) @@ -1254,7 +1090,6 @@ def solve_multi_period_economy_vfi(A=None, s_next = s_W s_W_prime = s_next - gc.collect() else: print(f"Warning: did not converge after {max_iter} iterations. " @@ -1268,8 +1103,6 @@ def solve_multi_period_economy_vfi(A=None, We use the same parameters as for the static economy, plus a discount factor $\beta = 0.8$ and grids of $N = N_m = 100$ points. -#### Initial values - We initialise the value function iteration with the one-period (static) solution, scaled to discounted-sum units. @@ -1280,88 +1113,55 @@ N_m = 100 W_l = u(A.min(), C.min()) / (1 - β) W_u = u(A.min(), C.max()) / (1 - β) -W = np.linspace(W_l, W_u, N) +W = np.linspace(W_l, W_u, N) W_m_l = β * W_l + 2 * C.min()**0.5 W_m_u = β * W_u + 2 * C.max()**0.5 -W_m = np.linspace(W_m_l, W_m_u, N_m) +W_m = np.linspace(W_m_l, W_m_u, N_m) -in_time = time() -s_W_0, π_0 = solve_static_problem(W * (1 - β), u, - A, Q, C, P, - "unobserved-actions") -out_time = time() -print("Time(s):", round(out_time - in_time, 3)) +with qe.Timer(): + s_W_0, π_0 = solve_static_problem(W * (1 - β), u, + A, Q, C, P, + "unobserved-actions") ``` -#### Finite-period economy - -```{code-cell} ipython3 -in_time = time() -s_W_T, π_W_s1_T, π_W_m_s2_T = solve_multi_period_economy_2( - A, Q, C, P, "unobserved-actions", - T=3, N=N, N_m=N_m) -out_time = time() -print("Time(s):", round(out_time - in_time, 3)) -``` - -```{code-cell} ipython3 -T = 3 -w_l_T = u(A.min(), C.min()) -w_u_T = u(A.min(), C.max()) -W_mat = np.cumsum( - np.logspace(0, T - 1, T, base=β).reshape(T, 1) * - np.linspace(w_l_T, w_u_T, N).reshape(1, N), - axis=0) -W_T = W_mat[2, :] - -plt.figure(figsize=(6.5, 6.5)) -plt.plot(W_T, s_W_T, "k-.", label="Three-period hidden effort") -plt.title("Finite-horizon surplus frontier", y=-0.2) -plt.legend() -plt.show() -``` - -This finite-horizon computation is a useful check on the recursion. - -The three-period surplus function already has the shape of the -infinite-horizon frontier, but it is still affected by the approaching -terminal date because continuation promises have value for only a few -periods. - -#### Infinite-period economy +The next cell solves the infinite-horizon hidden-effort problem using +the static solution as the initial value. ```{code-cell} ipython3 :tags: [hide-output] -in_time = time() -s_W, π_W_s1, π_W_m_s2 = solve_multi_period_economy_2( - A, Q, C, P, "unobserved-actions", - N=N, N_m=N_m, - s_W_0=s_W_0 / (1 - β), - tol=1e-8) -out_time = time() -print("Time(s):", round(out_time - in_time, 3)) +with qe.Timer(): + s_W, π_W_s1, π_W_m_s2, _ = solve_multi_period_economy( + A, Q, C, P, "unobserved-actions", + β=β, N=N, N_m=N_m, + s_W_0=s_W_0 / (1 - β), + tol=1e-8, verbose=False) ``` ```{code-cell} ipython3 -plt.figure(figsize=(6.5, 6.5)) -plt.plot(W, s_W, "k-.", label="Infinite-horizon hidden effort") +--- +mystnb: + figure: + caption: infinite-horizon surplus function + name: fig-rmh-infinite-surplus +--- +plt.figure() +plt.plot(W, s_W, "k-.", label="Infinite-horizon hidden effort", lw=2) plt.xlim([5.0, 25.0]) plt.ylim([-7.5, 10.0]) plt.xlabel("w") plt.ylabel("s(w)") -plt.title("Infinite-horizon surplus frontier", y=-0.2) plt.legend() plt.show() ``` -The infinite-horizon solution removes the terminal-date effect. - At each promised utility, the surplus function is the fixed point of the -Bellman operator: the current lottery and the continuation promise are -jointly chosen so that tomorrow's promise is priced by the same surplus -function plotted here. +Bellman operator. + +The current lottery and the continuation promise are jointly chosen so +that tomorrow's promise is priced by the same surplus function plotted +here. ### Recovering $\Pi^w(a, q, c, w')$ @@ -1385,30 +1185,34 @@ The `einsum` line is just the law of total probability. It sums over the intermediate promise $w^m$ and reconstructs the full lottery over $(a,q,c,w')$. -#### Surplus and history dependence +To measure the gain from history dependence, we compare three surplus +frontiers: full information, static hidden effort, and repeated hidden +effort. ```{code-cell} ipython3 W_full = np.linspace(5, 25, N) -in_time = time() -s_W_1, π_1 = solve_static_problem(W_full*(1-β), u, A, - Q, C, P, "full information") -out_time = time() - -print("Time(s):", round(out_time - in_time, 3)) +with qe.Timer(): + s_W_1, π_1 = solve_static_problem(W_full*(1-β), u, A, + Q, C, P, "full information") ``` ```{code-cell} ipython3 -plt.figure(figsize=(6.5, 6.5)) -plt.plot(W_full, s_W_1/(1 - β), label="Full information") -plt.plot(W, s_W, "k-.", label="Repeated hidden effort") -plt.plot(W, s_W_0/(1 - β), label="Static hidden effort") +--- +mystnb: + figure: + caption: surplus functions compared + name: fig-rmh-surplus-compare +--- +plt.figure() +plt.plot(W_full, s_W_1/(1 - β), label="Full information", lw=2) +plt.plot(W, s_W, "k-.", label="Repeated hidden effort", lw=2) +plt.plot(W, s_W_0/(1 - β), label="Static hidden effort", lw=2) plt.xlim([5.0, 25.0]) plt.ylim([-7.5, 10.0]) plt.hlines(0, 5.0, 25.0, linestyle="dashed") plt.xlabel("w") plt.ylabel("s(w)") -plt.title("History dependence and surplus", y=-0.2) plt.legend() plt.show() ``` @@ -1425,21 +1229,26 @@ repeating the one-period hidden-effort contract. The difference between the two unobserved-action curves is the gain from history dependence. -#### Effort and history dependence +The next figure compares expected effort in the static hidden-effort +contract and in the repeated contract. ```{code-cell} ipython3 -X, Y = list(range(len(A))), list(range(len(Q))) +--- +mystnb: + figure: + caption: effort and history dependence + name: fig-rmh-effort-compare +--- Ea_1 = np.einsum('a,waqc->w', A, π_0) Ea_inf = np.einsum('a,waqcx->w', A, π) -plt.figure(figsize=(6.5, 6.5)) -plt.plot(W, Ea_inf, label="Repeated hidden effort") -plt.plot(W, Ea_1, label="Static hidden effort") +plt.figure() +plt.plot(W, Ea_inf, label="Repeated hidden effort", lw=2) +plt.plot(W, Ea_1, label="Static hidden effort", lw=2) plt.xlabel("w") -plt.ylabel("E{a(w)}") +plt.ylabel(r"$E\{a(w)\}$") plt.xlim([5.0, 25.0]) plt.ylim([0.0, 0.8]) -plt.title("Effort with and without history dependence", y=-0.2) plt.legend() plt.show() ``` @@ -1451,8 +1260,6 @@ Near the lower utility bound, incentive compatibility forces low effort, but away from that bound continuation promises help provide incentives without relying only on current consumption. -#### Current consumption - The full lottery $\pi(w,a,q,c,w')$ is high-dimensional. To read it, we summarize it by conditional means. @@ -1463,9 +1270,7 @@ conditioning event. ```{code-cell} ipython3 def expected_consumption(π, C): - """ - E[c | w, a, q] from either π(w,a,q,c) or π(w,a,q,c,w'). - """ + """Compute E[c | w, a, q].""" if π.ndim == 4: mass = π.sum(axis=3) total = np.einsum("c,waqc->waq", C, π) @@ -1479,19 +1284,23 @@ def expected_consumption(π, C): ``` ```{code-cell} ipython3 +--- +mystnb: + figure: + caption: consumption in repeated contract + name: fig-rmh-dynamic-cons +--- Ec_inf = expected_consumption(π, C) -fig, axes = plt.subplots(1, len(Q), figsize=(11, 4), sharey=True) +fig, axes = plt.subplots(1, len(Q), sharey=True) for q_i, ax in enumerate(axes): for a_i, a in enumerate(A): - ax.plot(W, Ec_inf[:, a_i, q_i], label=f"a={a:g}") - ax.set_title(f"q={Q[q_i]:g}") - ax.set_xlabel("w") + ax.plot(W, Ec_inf[:, a_i, q_i], label=f"a={a:g}", lw=2) + ax.set_xlabel(f"w, q={Q[q_i]:g}") ax.set_xlim([5.0, 25.0]) ax.set_ylim([0.0, 2.25]) -axes[0].set_ylabel("E(c | w, a, q)") -axes[-1].legend(title="Action", loc="lower right") -fig.suptitle("Current consumption in the repeated contract") +axes[0].set_ylabel(r"$E(c \mid w, a, q)$") +axes[-1].legend(title="action", loc="lower right") fig.tight_layout() plt.show() ``` @@ -1503,8 +1312,6 @@ Output still affects rewards, but a large part of the reward and punishment is shifted into future promised utility. -#### Continuation promises - The parallel statistic for the dynamic margin is $E[w' \mid w,a,q]$. @@ -1513,9 +1320,7 @@ reward or punishment. ```{code-cell} ipython3 def expected_promise(π, W): - """ - E[w' | w, a, q] from π(w,a,q,c,w'). - """ + """Compute E[w' | w, a, q].""" mass = π.sum(axis=(3, 4)) total = np.einsum("x,waqcx->waq", W, π) return np.divide(total, mass, @@ -1524,21 +1329,25 @@ def expected_promise(π, W): ``` ```{code-cell} ipython3 +--- +mystnb: + figure: + caption: expected continuation promises + name: fig-rmh-promises +--- Ew_inf = expected_promise(π, W) -fig, axes = plt.subplots(1, len(Q), figsize=(11, 4), sharey=True) +fig, axes = plt.subplots(1, len(Q), sharey=True) for q_i, ax in enumerate(axes): for a_i, a in enumerate(A): - ax.plot(W, Ew_inf[:, a_i, q_i], label=f"a={a:g}") - ax.plot(W, W, "k-.", label="45-degree line") - ax.set_title(f"q={Q[q_i]:g}") - ax.set_xlabel("w") + ax.plot(W, Ew_inf[:, a_i, q_i], label=f"a={a:g}", lw=2) + ax.plot(W, W, "k-.", label="45-degree line", lw=2) + ax.set_xlabel(f"w, q={Q[q_i]:g}") ax.set_xlim([10.0, 25.0]) ax.set_ylim([10.0, 25.0]) -axes[0].set_ylabel("E(w' | w, a, q)") -axes[-1].legend(title="Action", loc="lower right") -fig.suptitle("Continuation promises") +axes[0].set_ylabel(r"$E(w' \mid w, a, q)$") +axes[-1].legend(title="action", loc="lower right") fig.tight_layout() plt.show() ``` @@ -1546,8 +1355,7 @@ plt.show() This plot displays the expected next-period promise conditional on current $w$, recommended action $a$, and realized output $q$. -High -output generally raises continuation utility and low output lowers it. +High output generally raises continuation utility and low output lowers it. At the endpoints of the feasible promise set, the transition stays on the 45-degree line because only the corresponding extreme plan can deliver @@ -1559,35 +1367,31 @@ The higher discount factor makes promised utility a stronger incentive instrument and makes the evolution of individual histories easier to see. -We now use `solve_multi_period_economy_vfi`, which builds the CVXPY -problems once and applies Anderson acceleration, to solve the -infinite-horizon economy at $\beta = 0.95$ with a grid of $N = N_m = 50$ -points. +We solve the infinite-horizon economy again at $\beta = 0.95$ with a +grid of $N = N_m = 50$ points. Starting from the static solution rescaled to discounted-sum units, the iteration converges to tolerance $10^{-4}$. ```{code-cell} ipython3 -β_95 = 0.95 -N_95 = 50 +β_95 = 0.95 +N_95 = 50 N_m95 = 50 w_l_95 = u(A.min(), C.min()) / (1 - β_95) w_u_95 = u(A.min(), C.max()) / (1 - β_95) -W_95 = np.linspace(w_l_95, w_u_95, N_95) +W_95 = np.linspace(w_l_95, w_u_95, N_95) s_W_0_95, _ = solve_static_problem(W_95 * (1 - β_95), u, A, Q, C, P, "unobserved-actions") -in_time = time() -s_W_new, π_W_s1_new, π_W_m_s2_new, W_new = solve_multi_period_economy_vfi( - A, Q, C, P, "unobserved-actions", - β=β_95, N=N_95, N_m=N_m95, - s_W_0=s_W_0_95 / (1 - β_95), - tol=1e-4, max_iter=300, m_anderson=5, - verbose=True) -out_time = time() -print("Time(s):", round(out_time - in_time, 3)) +with qe.Timer(): + s_W_new, π_W_s1_new, π_W_m_s2_new, W_new = solve_multi_period_economy( + A, Q, C, P, "unobserved-actions", + β=β_95, N=N_95, N_m=N_m95, + s_W_0=s_W_0_95 / (1 - β_95), + tol=1e-4, max_iter=300, m_anderson=5, + verbose=True) ``` ```{code-cell} ipython3 @@ -1653,20 +1457,26 @@ This corresponds to the ex ante symmetric, or "fair", allocation. It is the highest common promised utility that can be assigned while keeping discounted social surplus nonnegative. -#### Simulated consumption histories +The next figure plots four simulated consumption histories from the +same initial promised utility. ```{code-cell} ipython3 +--- +mystnb: + figure: + caption: simulated consumption histories + name: fig-rmh-sim-consumption +--- date_c = np.arange(80) + 1 -plt.figure(figsize=(6.5, 6.5)) -plt.plot(date_c, c_series[:, 0]) -plt.plot(date_c, c_series[:, 1]) -plt.plot(date_c, c_series[:, 2]) -plt.plot(date_c, c_series[:, 3]) +plt.figure() +plt.plot(date_c, c_series[:, 0], lw=2) +plt.plot(date_c, c_series[:, 1], lw=2) +plt.plot(date_c, c_series[:, 2], lw=2) +plt.plot(date_c, c_series[:, 3], lw=2) plt.xlabel("date") plt.ylabel("consumption") plt.xlim([0, 80]) plt.ylim([0.00, 2.25]) -plt.title("Individual consumption histories ($\\beta=0.95$)", y=-0.2) plt.show() ``` @@ -1677,19 +1487,24 @@ Different output histories move agents to different continuation promises, so the contract gradually creates heterogeneous consumption histories. -#### Simulated promised utilities +The next figure plots the corresponding promised-utility histories. ```{code-cell} ipython3 +--- +mystnb: + figure: + caption: simulated promised utilities + name: fig-rmh-sim-promises +--- date_w = np.arange(81) -plt.figure(figsize=(6.5, 6.5)) -plt.plot(date_w, w_series[:, 0]) -plt.plot(date_w, w_series[:, 1]) -plt.plot(date_w, w_series[:, 2]) -plt.plot(date_w, w_series[:, 3]) +plt.figure() +plt.plot(date_w, w_series[:, 0], lw=2) +plt.plot(date_w, w_series[:, 1], lw=2) +plt.plot(date_w, w_series[:, 2], lw=2) +plt.plot(date_w, w_series[:, 3], lw=2) plt.xlabel("date") plt.ylabel("expected utility") plt.ylim([40.0, 100.0]) -plt.title("Individual promised utilities ($\\beta=0.95$)", y=-0.2) plt.show() ``` @@ -1700,6 +1515,9 @@ histories move the agent downward. This is the dynamic incentive mechanism in the model. +For distributional plots, it is cleaner to propagate population mass +directly instead of drawing more individual histories. + ```{code-cell} ipython3 def population_distributions(W, C, s_W, T, π): w_index = np.nanargmin(np.abs(s_W)) @@ -1735,27 +1553,31 @@ Starting from a point mass over $w$, it applies the optimal lottery each period and records the implied marginal distributions of consumption and promised utility. -#### Cross-sectional consumption distributions +The next wireframe plots the cross-sectional consumption distribution +over time. ```{code-cell} ipython3 -%matplotlib inline - +--- +mystnb: + figure: + caption: consumption distribution over time + name: fig-rmh-dist-consumption +--- date_mat_c = np.reshape(np.arange(80) + 1, (80, 1)) * \ np.ones((1, len(C))) c_mat = np.ones((80, 1)) @ np.reshape(C, (1, len(C))) -fig = plt.figure(figsize=(8, 5)) +fig = plt.figure() ax = fig.add_subplot(projection='3d') -plt.title("Consumption distribution over time ($\\beta=0.95$)", y=-0.3) plt.xlabel('date') plt.ylabel('consumption') ax.set_zlabel('percentage') ax.set_zlim(0.0, 1.0) ax.view_init(elev=25, azim=-65) -wire = ax.plot_wireframe(date_mat_c, c_mat, π_c, - rstride=1, cstride=2, - color="black", linewidth=0.35) +ax.plot_wireframe(date_mat_c, c_mat, π_c, + rstride=1, cstride=2, + color="black", linewidth=0.35) plt.show() ``` @@ -1765,26 +1587,30 @@ receive different rewards and punishments. On the finite grid, some mass eventually reaches the edges of the feasible promise set. -#### Cross-sectional promise distributions +The final wireframe plots the distribution of promised utility itself. ```{code-cell} ipython3 -%matplotlib inline +--- +mystnb: + figure: + caption: promise distribution over time + name: fig-rmh-dist-promises +--- date_mat_w = np.reshape(np.arange(80) + 1, (80, 1)) * \ np.ones((1, len(W_new))) W_mat_12 = np.ones((80, 1)) @ np.reshape(W_new, (1, len(W_new))) -fig = plt.figure(figsize=(8, 5)) +fig = plt.figure() ax = fig.add_subplot(projection='3d') -plt.title("Promise distribution over time ($\\beta=0.95$)", y=-0.3) plt.xlabel('date') plt.ylabel('w') ax.set_zlabel('percentage') ax.set_zlim(0.0, 1.0) ax.view_init(elev=25, azim=-65) -wire = ax.plot_wireframe(date_mat_w, W_mat_12, π_w, - rstride=1, cstride=2, - color="black", linewidth=0.35) +ax.plot_wireframe(date_mat_w, W_mat_12, π_w, + rstride=1, cstride=2, + color="black", linewidth=0.35) plt.show() ``` @@ -1809,9 +1635,13 @@ The hidden-effort surplus frontier lies below the full-information frontier, and the gap between them measures the surplus cost of unobserved effort. +Restricting the contract to one period raises consumption variability and +lowers average output relative to the multi-period optimum, the gain visible in +{numref}`fig-rmh-surplus-compare`. + #### Dynamic contracts and promised utility -The recursive formulation of {cite}`Spear_Srivastava_87` compresses all +The recursive formulation of {cite:t}`Spear_Srivastava_87` compresses all payoff-relevant history into a single scalar state: the discounted expected continuation utility $w$ that the principal has promised the agent. @@ -1834,8 +1664,9 @@ starting from a common initial promise, dynamic incentives generate non-trivial individual histories and cross-sectional dispersion in consumption and promised utility. -With the finite grids used here, the endpoints of the promise set are -absorbing. +On the finite grid used here, the lowest and highest promised-utility values act +as traps: once an agent's promise reaches either end, the only feasible contract +keeps it there, so the agent never moves back. The simulations should therefore be read as finite-grid illustrations of how history dependence spreads the distribution over @@ -1848,7 +1679,7 @@ time, not as a separate theorem about the limiting distribution. Incentive constraints can render the set of feasible contracts non-convex, making standard optimization techniques unreliable. -{cite}`Phelan_Townsend_91` circumvented this by allowing the planner to +{cite:t}`Phelan_Townsend_91` circumvented this by allowing the planner to choose a joint *lottery* $\Pi(a, q, c, w')$ over actions, outputs, consumptions, and continuation values. @@ -1866,35 +1697,9 @@ function iteration reduces to solving one LP per grid point per iteration, a task handled efficiently by modern LP solvers such as HiGHS. -#### Dynamic programming - -The promised-utility state variable $w$ makes the problem recursive. - -At each iteration the Bellman operator maps a surplus function $v$ to an -updated surplus function $Tv$; repeated application converges to the -infinite-horizon fixed point. - -The implementation initializes the iteration from the scaled static -solution, which is a useful numerical starting point. - -#### Two-step factored algorithm - -The additive separability $U(a,c) = 2\sqrt{1-a} + 2\sqrt{c}$ allows the -four-dimensional LP to be split into two smaller sub-problems. - -Step 2 allocates consumption given an intermediate promised utility -$w^m$; Step 1 assigns actions, outputs, and intermediate continuation -utilities given $w$. - -Because each sub-LP has far fewer decision variables than the full joint -LP, computation is substantially faster and the approach scales to finer -grids. - #### Dynamic programming squared -This lecture is closely related to what Lars Ljungqvist and Thomas -Sargent call *dynamic programming squared* in -{cite}`Ljungqvist2012`. +This lecture is closely related to what {cite:t}`Ljungqvist2012` call *dynamic programming squared*. The phrase refers to recursive problems in which one continuation object is carried as a state variable inside another recursive problem. @@ -1929,14 +1734,20 @@ producing a closely related nested recursive structure. In all of these settings, the inner dynamic program defines a state variable (a promised utility, a marginal value, or a continuation value) that restricts what the outer dynamic -program can promise or deliver. +program can promise or deliver. +The same recursive idea --- carry promised utility as the state and restrict which +promises the contract may offer --- reappears in the {doc}`atkeson_1991` lecture. +There a sovereign borrower can repudiate its debt and walk away to autarky at any +time, so the contract must always promise at least the value of that outside +option. ## Exercises -````{admonition} Exercise 1 -:class: exercise +```{exercise-start} +:label: repeat_mh_ex1 +``` Using the surplus arrays `s_W_full` and `s_W_unobs` computed in the static section, define the **agency cost function** @@ -1949,27 +1760,34 @@ $$ 2. Report the value $\hat{w}$ at which $\delta$ is largest. 3. Explain intuitively why agency costs are highest at that level of promised utility. -```` +```{exercise-end} +``` ```{solution-start} repeat_mh_ex1 +:label: repeat_mh_ex1_sol :class: dropdown ``` ```{code-cell} ipython3 -delta_W = s_W_full - s_W_unobs +--- +mystnb: + figure: + caption: agency cost function + name: fig-rmh-agency-cost +--- +δ_W = s_W_full - s_W_unobs -plt.figure(figsize=(6.5, 6.5)) -plt.plot(W_static, delta_W) +plt.figure() +plt.plot(W_static, δ_W, lw=2) plt.xlabel("w") plt.ylabel(r"$\delta(w) = s^{FI}(w) - s^{UA}(w)$") plt.xlim([1.0, 5.0]) plt.ylim(bottom=0.0) -plt.title("Agency cost in the static model", y=-0.2) plt.show() -max_i = np.nanargmax(delta_W) +max_i = np.nanargmax(δ_W) w_hat = W_static[max_i] -print(f"Largest agency cost at w = {w_hat:.3f}, δ = {delta_W[max_i]:.4f}") +print(f"Largest agency cost at w = {w_hat:.3f}, δ = {δ_W[max_i]:.4f}") ``` Agency costs are highest near intermediate levels of promised utility @@ -1984,20 +1802,21 @@ cost of each additional unit of effort is small. ```{solution-end} ``` -````{admonition} Exercise 2 -:class: exercise +```{exercise-start} +:label: repeat_mh_ex2 +``` The output probability matrix $P$ governs how informative output is about effort. Define a **flatter** probability matrix $$ -P_{flat} = \begin{pmatrix} +P_{flat} = \begin{bmatrix} 0.70 & 0.30 \\ 0.55 & 0.45 \\ 0.45 & 0.55 \\ 0.30 & 0.70 -\end{pmatrix} +\end{bmatrix} $$ in which output is less informative about effort than in the baseline $P$. @@ -2008,13 +1827,21 @@ in which output is less informative about effort than in the baseline $P$. $s^{UA}(w)$ and expected effort levels $E\{a(w)\}$ under $P$ and $P_{flat}$. 3. Explain the economic intuition for any differences you find. -```` +```{exercise-end} +``` ```{solution-start} repeat_mh_ex2 +:label: repeat_mh_ex2_sol :class: dropdown ``` ```{code-cell} ipython3 +--- +mystnb: + figure: + caption: surplus and effort under flatter probabilities + name: fig-rmh-flat-p +--- P_flat = np.array([[0.70, 0.30], [0.55, 0.45], [0.45, 0.55], @@ -2024,24 +1851,22 @@ s_W_flat, π_flat = solve_static_problem(W_static, u, A, Q, C, P_flat, "unobserved-actions") Ea_flat = np.einsum('a,waqc->w', A, π_flat) -fig, axes = plt.subplots(1, 2, figsize=(13, 6)) +fig, axes = plt.subplots(1, 2) -axes[0].plot(W_static, s_W_unobs, label="Baseline $P$") -axes[0].plot(W_static, s_W_flat, label="Flat $P$") +axes[0].plot(W_static, s_W_unobs, label="Baseline $P$", lw=2) +axes[0].plot(W_static, s_W_flat, label="Flat $P$", lw=2) axes[0].hlines(0, 1.0, 5.0, linestyle="dashed") axes[0].set_xlabel("w") axes[0].set_ylabel("s(w)") axes[0].set_xlim([1.0, 5.0]) -axes[0].set_title("Surplus function", y=-0.2) axes[0].legend() -axes[1].plot(W_static, Ea_unobs, label="Baseline $P$") -axes[1].plot(W_static, Ea_flat, label="Flat $P$") +axes[1].plot(W_static, Ea_unobs, label="Baseline $P$", lw=2) +axes[1].plot(W_static, Ea_flat, label="Flat $P$", lw=2) axes[1].set_xlabel("w") axes[1].set_ylabel(r"$E\{a(w)\}$") axes[1].set_xlim([1.0, 5.0]) axes[1].set_ylim([0.0, 0.8]) -axes[1].set_title("Expected effort", y=-0.2) axes[1].legend() plt.tight_layout() diff --git a/lectures/subjective_beliefs_business_cycles.md b/lectures/subjective_beliefs_business_cycles.md new file mode 100644 index 00000000..1b26f28b --- /dev/null +++ b/lectures/subjective_beliefs_business_cycles.md @@ -0,0 +1,1814 @@ +--- +jupytext: + text_representation: + extension: .md + format_name: myst + format_version: 0.13 + jupytext_version: 1.17.1 +kernelspec: + display_name: Python 3 (ipykernel) + language: python + name: python3 +--- + +(subjective_beliefs_bc)= +# Survey Data and Subjective Beliefs in Business Cycles + +```{index} single: Subjective Beliefs; Business Cycles +``` + +## Overview + +This lecture presents key ideas from {cite}`bhandari2025survey`, who study +whether household survey data on macroeconomic expectations can shed light on +business cycle dynamics. + +Their central finding is that household forecasts of unemployment and inflation +exhibit **systematic upward biases** relative to professional forecasters and +model-consistent rational expectations. These biases — which the authors call +*belief wedges* — are: + +* **Persistent and countercyclical**: they are larger during recessions. +* **Positively correlated**: optimism/pessimism about unemployment and inflation + move together. +* **One-factor in structure**: a single latent state accounts for most + variation across wedges. + +The paper interprets this evidence through the lens of +**robust preferences** ({cite}`HansenSargent2001`; {cite}`HansenSargent2008`). + +A household that fears model misspecification behaves as if it tilts +probabilities toward bad outcomes. + +When calibrated to the Michigan Survey of +Consumers (1982Q1–2019Q4), this mechanism yields a time-varying *belief shock* +that substantially reduces the well-known **unemployment volatility puzzle** — +the fact that standard New Keynesian models with only technology and monetary +policy shocks generate far too little unemployment volatility. + +By the end of this lecture you will understand: + +* How to define and measure belief wedges from household survey data. +* How robust preferences generate time-varying subjective beliefs. +* How belief distortions propagate through a linearised DSGE model. +* Why a calibrated belief shock helps resolve the unemployment volatility + puzzle. + +## Setup + +```{code-cell} ipython3 +import datetime + +import numpy as np +import matplotlib.pyplot as plt +import matplotlib.dates as mdates +from scipy.linalg import solve_discrete_lyapunov +``` + +## Measuring belief wedges + +### Definition + +Let $E_t[\cdot]$ denote expectations under the **data-generating** (objective) +probability measure and $\tilde{E}_t[\cdot]$ denote **subjective** (survey) +expectations. + +For any scalar variable $z_{t+1}$, the **one-period belief +wedge** is + +$$ + +\Delta_t^{(1)}(z) \;=\; \tilde{E}_t[z_{t+1}] - E_t[z_{t+1}]. + +$$ + +A positive wedge means households are more pessimistic than the model predicts: +they expect $z_{t+1}$ to be higher than the model-consistent forecast. + +For +unemployment and inflation this sign convention implies an upward bias. + +In practice, {cite}`BhandariBorovickaHo2024` measure +$\tilde{E}_t[\cdot]$ from the Michigan Survey of Consumers, and +$E_t[\cdot]$ from a benchmark DSGE model estimated on the same data. + +The +discrepancy is the wedge. + +### Empirical facts + +Using data from 1982Q1 to 2019Q4, the authors document: + +| Statistic | Unemployment wedge | Inflation wedge | +|---|---|---| +| Mean | 0.52 pp | 1.22 pp | +| Standard deviation | 0.67 pp | 1.03 pp | +| Correlation with output gap | −0.49 | −0.30 | + +Both wedges are **positive on average** (households are pessimistic) and +**countercyclical** (pessimism rises in recessions). + +Moreover, the first +principal component of the joint wedge series explains **78.6%** of its +variation — a striking one-factor structure. + +The following code simulates artificial wedge series that match these +moments, so we can visualise the key stylised facts before turning to theory. + +```{code-cell} ipython3 +# --------------------------------------------------------------------------- +# Simulate stylised belief-wedge time series calibrated to match the +# empirical moments in Bhandari, Borovicka, Ho (2025). +# --------------------------------------------------------------------------- + +# Calibrated parameters (Table 1 of the paper) +μ_θ = 5.64 # mean of belief-shock parameter θ +ρ_θ = 0.714 # AR(1) persistence of θ +σ_θ = 4.3 # innovation volatility of θ (units of θ) + +# Wedge loadings: Δᵤ = cᵤ θ, Δπ = cπ θ (c chosen to match the means) +c_u = 0.52 / μ_θ # ≈ 0.0922 pp per unit of θ +c_π = 1.22 / μ_θ # ≈ 0.2163 pp per unit of θ + +T = 152 # 38 years × 4 quarters + +# Simulate the belief-shock AR(1) +rng = np.random.default_rng(42) +θ = np.zeros(T) +θ[0] = μ_θ +for t in range(1, T): + θ[t] = ((1 - ρ_θ) * μ_θ + + ρ_θ * θ[t-1] + + σ_θ * rng.standard_normal()) + +# Belief wedges (in percentage points) +wedge_u = c_u * θ +wedge_π = c_π * θ + +# Generate quarters 1982Q1 – 2019Q4 +quarters = [datetime.date(1982 + (q // 4), 3 * (q % 4) + 1, 1) + for q in range(T)] +``` + +```{code-cell} ipython3 +--- +mystnb: + figure: + caption: simulated belief wedges + name: fig-sbbc-belief-wedges +--- +fig, axes = plt.subplots(2, 1, figsize=(11, 6), sharex=True) + +axes[0].plot(quarters, wedge_u, color='steelblue', linewidth=2, + label='Unemployment belief wedge') +axes[0].axhline(np.mean(wedge_u), color='steelblue', linestyle='--', + linewidth=0.8, alpha=0.7) +axes[0].set_ylabel('percentage points') +axes[0].legend(loc='upper left') + +axes[1].plot(quarters, wedge_π, color='darkorange', linewidth=2, + label='Inflation belief wedge') +axes[1].axhline(np.mean(wedge_π), color='darkorange', linestyle='--', + linewidth=0.8, alpha=0.7) +axes[1].set_ylabel('percentage points') +axes[1].legend(loc='upper left') + +for ax in axes: + ax.xaxis.set_major_locator(mdates.YearLocator(5)) + ax.xaxis.set_major_formatter(mdates.DateFormatter('%Y')) + +plt.tight_layout() +plt.show() +``` + +```{code-cell} ipython3 +--- +mystnb: + figure: + caption: one-factor structure of belief wedges + name: fig-sbbc-wedge-scatter +--- +# Show the one-factor structure: scatter of unemployment vs inflation wedge +fig, ax = plt.subplots() +sc = ax.scatter(wedge_u, wedge_π, c=range(T), cmap='RdYlGn_r', + alpha=0.7, s=20) +plt.colorbar(sc, ax=ax, label='quarter (dark = recent)') +ax.set_xlabel('unemployment wedge (pp)') +ax.set_ylabel('inflation wedge (pp)') +corr = np.corrcoef(wedge_u, wedge_π)[0, 1] +ax.text(0.05, 0.93, f'correlation = {corr:.2f}', + transform=ax.transAxes, fontsize=11) +plt.tight_layout() +plt.show() +``` + +The scatter plot reveals the strong positive correlation between the two +wedges. + +Both series are high when the belief shock $\theta_t$ is high, and low otherwise. + +This is the one-factor structure that motivates the +theoretical framework. + +## A model of pessimism + +### Robust preferences + +Why would households have systematically biased beliefs? + +One disciplined answer comes from **robust control** or **multiplier preferences** +({cite}`HansenSargent2001`, {cite}`HansenSargent2008`). + +An agent who fears that her reference model may be misspecified solves + +$$ + +V_t \;=\; \min_{\substack{m_{t+1} > 0 \\ E_t[m_{t+1}] = 1}} +\Bigl\{ + u(x_t) + + \beta E_t\!\left[m_{t+1} V_{t+1}\right] + + \frac{\beta}{\theta_t}\, E_t\!\left[m_{t+1} \log m_{t+1}\right] +\Bigr\}. + +$$ + +Here $m_{t+1}$ is a **likelihood ratio** (Radon–Nikodym derivative) that +distorts the reference measure, and the last term is an entropy penalty that +keeps the distortion from being too extreme. + +The scalar $\theta_t > 0$ +controls the *degree* of concern for misspecification: larger $\theta_t$ means +more pessimism. + +The inner minimisation has a closed-form solution: + +$$ + +m_{t+1}^* \;=\; +\frac{\exp(-\theta_t V_{t+1})}{E_t[\exp(-\theta_t V_{t+1})]}. + +$$ + +Since $m_{t+1}^*$ assigns higher weight to states where $V_{t+1}$ is low (bad +outcomes), pessimistic agents effectively over-weight recessions in their +probability assessments. + +### Connection to the belief wedge + +The belief wedge is the expected deviation between subjective and objective +forecasts. + +Using $\tilde{E}_t[\cdot] = E_t[m_{t+1}^* \cdot]$: + +$$ + +\Delta_t^{(1)}(z) += \tilde{E}_t[z_{t+1}] - E_t[z_{t+1}] += E_t\!\left[m_{t+1}^* z_{t+1}\right] - E_t[z_{t+1}] += \operatorname{Cov}_t(m_{t+1}^*, z_{t+1}). + +$$ + +So the belief wedge equals the covariance between the distorted likelihood +ratio and the variable of interest. + +When $V_{t+1}$ is high in states where +$z_{t+1}$ is also high, $m_{t+1}^*$ will be low in those states, making the +covariance negative — i.e.\ the agent *underestimates* good-state variables. + +For unemployment (which varies inversely with good economic outcomes), the +wedge is positive: pessimists expect higher unemployment than the model predicts. + +### Illustration: optimal belief distortion + +To see the mechanism concretely, consider an **endowment economy** with a +scalar log-consumption state $x_t$ and dynamics + +$$ + +x_{t+1} = \rho_x x_t + \sigma_x w_{t+1}, \qquad w_{t+1} \sim N(0,1). + +$$ + +With log utility, the continuation value is linear: $V_t = V_x x_t + V_q$. + + +Under the objective measure, $x_{t+1}$ is normal with mean $\rho_x x_t$ and +standard deviation $\sigma_x$. + +The distorted measure $m_{t+1}^*$ shifts the mean of $w_{t+1}$ to + +$$ + +\nu_t \;=\; -\theta_t (V_x \sigma_x). + +$$ + +Hence, under the subjective measure, the innovation distribution becomes + +$$ + +w_{t+1} \;\sim\; N\!\left(\nu_t,\; 1\right). + +$$ + +The belief wedge for the state variable $x$ is + +$$ + +\Delta_t^{(1)}(x) \;=\; \sigma_x \nu_t \;=\; -\theta_t V_x \sigma_x^2. + +$$ + +When $V_x > 0$ (good consumption state is good) and $\theta_t > 0$ +(pessimism), the wedge is negative — the agent *underestimates* +consumption growth. + +For unemployment (enter with a negative sign in the +value function), the same pessimism generates a **positive** wedge. + +```{code-cell} ipython3 +# --------------------------------------------------------------------------- +# Illustrate the optimal belief distortion in the simple endowment economy. +# --------------------------------------------------------------------------- + +class BeliefDistortionModel: + """ + Simple scalar AR(1) endowment economy illustrating the robust-preference + mechanism from Bhandari, Borovicka, Ho (2024). + + State dynamics: x_{t+1} = ρ_x * x_t + σ_x * w_{t+1} + Period utility: u(x_t) = (1 - β) * x_t [log utility] + Continuation value (linearised): V_t = Vx * x_t + Vq + + Under the distorted measure the shock innovation has mean + ν_t = -θ_t * Vx * σ_x + which produces the belief wedge + Δ_t^(1)(x) = σ_x * ν_t = -θ_t * Vx * σ_x^2. + """ + + def __init__(self, β=0.994, ρ_x=0.85, σ_x=0.005, + μ_θ=5.64, ρ_θ=0.714, σ_θ=4.3): + self.β = β + self.ρ_x = ρ_x + self.σ_x = σ_x + self.μ_θ = μ_θ + self.ρ_θ = ρ_θ + self.σ_θ = σ_θ + self.Vx = self._solve_Vx() + + def _solve_Vx(self): + """Solve the scalar Riccati equation for Vx.""" + u_x = 1.0 - self.β # marginal utility of log consumption + + a = (self.β / 2.0) * self.σ_x**2 * self.μ_θ + b = -(1.0 - self.β * self.ρ_x) + c = u_x + + # Rational-expectations (θ=0) solution + Vx_re = u_x / (1.0 - self.β * self.ρ_x) + + if abs(a) < 1e-14: # essentially no pessimism + return Vx_re + + disc = b**2 - 4.0 * a * c + if disc < 0: + return Vx_re # fall back to RE if no real root + + r1 = (-b + np.sqrt(disc)) / (2.0 * a) + r2 = (-b - np.sqrt(disc)) / (2.0 * a) + return r1 if abs(r1 - Vx_re) < abs(r2 - Vx_re) else r2 + + def belief_drift(self, θ): + """Mean shift under subjective beliefs.""" + return -θ * self.Vx * self.σ_x + + def belief_wedge(self, θ): + """One-period belief wedge for the state.""" + return self.σ_x * self.belief_drift(θ) + + def simulate_θ(self, T=200, seed=42): + """Simulate the AR(1) belief-shock process.""" + rng = np.random.default_rng(seed) + θ = np.zeros(T) + θ[0] = self.μ_θ + for t in range(1, T): + θ[t] = ((1 - self.ρ_θ) * self.μ_θ + + self.ρ_θ * θ[t - 1] + + self.σ_θ * rng.standard_normal()) + return θ + + def simulate(self, T=200, seed=42): + """Simulate belief wedge time series.""" + θ = self.simulate_θ(T, seed) + return θ, self.belief_wedge(θ) + + +model = BeliefDistortionModel() +print(f"RE value of Vx: {(1-model.β)/(1-model.β*model.ρ_x):.4f}") +print(f"Robust value of Vx: {model.Vx:.4f}") +print(f"Belief drift at θ̄: {model.belief_drift(model.μ_θ)*100:.4f} pp") +print(f"Belief wedge at θ̄: {model.belief_wedge(model.μ_θ)*100:.4f} pp") +``` + +```{code-cell} ipython3 +--- +mystnb: + figure: + caption: objective and subjective shock distributions + name: fig-sbbc-shock-distributions +--- +# Compare the objective (N(0,1)) and subjective shock distributions. +# The actual drift ν = -θ * Vx * σ_x is tiny on a unit-shock axis. +# We plot the standardised drift ν / σ_x instead. + +θ_vals = [0, model.μ_θ, 2 * model.μ_θ] +labels = ['θ = 0 (rational)', + f'θ = θ̄ = {model.μ_θ:.1f} (mean)', + f'θ = 2θ̄ (pessimistic)'] +colors = ['black', 'steelblue', 'firebrick'] + +# ν_tilde = ν / σ_x = -θ * Vx. +ν_tilde = [-θ * model.Vx for θ in θ_vals] + +x_grid = np.linspace(-4, 4, 500) + +fig, ax = plt.subplots() +for μ, label, color in zip(ν_tilde, labels, colors): + pdf = (1 / np.sqrt(2 * np.pi)) * np.exp(-0.5 * (x_grid - μ)**2) + ax.plot(x_grid, pdf, label=label, color=color, linewidth=2) + +ax.axvline(0, color='grey', linestyle=':', linewidth=0.8) +ax.set_xlabel( + 'standardised innovation $(w_{t+1} - \\nu_t)$ ' + 'with $\\nu_t = -\\theta_t V_x \\sigma_x$' +) +ax.set_ylabel('density') +ax.legend() +plt.tight_layout() +plt.show() + +print("Mean shifts (in units of σ_x):") +for μ, label in zip(ν_tilde, labels): + print(f" {label:35s} ν̃ = {μ:.4f}") +``` + +The figure shows how pessimism (higher $\theta_t$) shifts the perceived +distribution of future shocks to the left. + +An agent with $\theta_t > 0$ +believes bad shocks are more likely than they actually are. + +## Linear approximation with belief distortions + +### The perturbation method + +For quantitative analysis, {cite}`bhandari2025survey` extend the standard +first-order perturbation method to accommodate time-varying belief distortions. + +Let the state vector be $x_t \in \mathbb{R}^n$ with **objective** law of +motion + +$$ + +x_{t+1} = \psi_q + \psi_x x_t + \psi_w w_{t+1}, \qquad +w_{t+1} \sim N(0, I_k). + +$$ + +Under the optimal belief distortion the shocks are re-centred: + +$$ + +w_{t+1} \;\sim\; N\!\left(- \theta_t (\bar{x} + x_{1t}) + (V_x \psi_w)',\; I_k\right), + +$$ + +where $V_x$ is the row vector of first derivatives of the continuation value +and $\bar{x}$ is the non-stochastic steady state. + +The perturbation is exact +to first order. + +The resulting **belief wedge** for any variable $z$ with model-consistent +expected value $\bar{z}' x$ is + +$$ + +\Delta_t^{(1)}(z) +\;=\; -\theta_t (\bar{x} + x_{1t})\, \bar{z}' (\psi_w \psi_w') V_x'. + +$$ + +### Riccati equation for $V_x$ + +The key object is $V_x$, which solves + +$$ + +V_x +\;=\; u_x + - \frac{\beta}{2}\, V_x \psi_w \psi_w' V_x' \bar\theta + + \beta\, V_x \psi_x. + +$$ + +This is a modified Riccati equation: the middle term arises from the entropy +penalty on beliefs and vanishes under rational expectations ($\bar\theta = 0$). + +### One-factor structure + +An important consequence of the formula for $\Delta_t^{(1)}(z)$ is that the +*time variation* in all belief wedges is driven by the **single scalar** +$\theta_t$. + +The cross-sectional loadings $\bar{z}'(\psi_w\psi_w')V_x'$ are +fixed by the model's structural parameters. + +This theoretical prediction +matches the empirical finding that one principal component explains 78.6% +of the joint variation in household forecast errors. + +```{code-cell} ipython3 +--- +mystnb: + figure: + caption: wedge loadings implied by one factor + name: fig-sbbc-one-factor-loadings +--- +# --------------------------------------------------------------------------- +# Demonstrate the one-factor structure by computing wedges for two +# different variables as θ varies, holding structural parameters fixed. +# --------------------------------------------------------------------------- + +θ_grid = np.linspace(0, 20, 200) + +# Loading vector, proportional to bar_z' * ψ_w * ψ_w' * Vx'. +# Calibrated so that at mean θ the steady-state wedges match the data. +loading_u = c_u # 0.52 / 5.64 pp per unit of θ (unemployment) +loading_π = c_π # 1.22 / 5.64 pp per unit of θ (inflation) + +wedge_u_grid = loading_u * θ_grid +wedge_π_grid = loading_π * θ_grid + +fig, axes = plt.subplots(1, 2, figsize=(11, 4)) + +axes[0].plot(θ_grid, wedge_u_grid, color='steelblue', linewidth=2, + label='$\\Delta^{(1)}(u)$') +axes[0].plot(θ_grid, wedge_π_grid, color='darkorange', linewidth=2, + label='$\\Delta^{(1)}(\\pi)$') +axes[0].axvline(μ_θ, color='grey', linestyle='--', linewidth=0.9, + label=f'$\\bar{{\\theta}} = {μ_θ}$') +axes[0].set_xlabel('belief-shock level $\\theta$') +axes[0].set_ylabel('belief wedge (pp)') +axes[0].legend() + +# Scatter of (wedge_u, wedge_π) with θ as the driver. +θ_sim = model.simulate_θ(T=400, seed=7) +wu_sim = loading_u * θ_sim +w_π_sim = loading_π * θ_sim +axes[1].scatter(wu_sim, w_π_sim, c=θ_sim, cmap='Blues', alpha=0.6, s=12) +axes[1].set_xlabel('unemployment wedge (pp)') +axes[1].set_ylabel('inflation wedge (pp)') + +plt.tight_layout() +plt.show() +``` + +## A New Keynesian model with belief distortions + +### Model description + +{cite}`bhandari2025survey` embed the belief-distortion mechanism in a +New Keynesian model with a **search-and-matching** labour market +({cite}`Shimer2005`; {cite}`ChristianoEichenbaumTrabandt2016`). + +The key +components are: + +**Households** — Have log utility in consumption and disutility of hours. +They apply robust preferences (indexed by $\theta_t$) when forming +subjective forecasts. + +**Firms** — Post vacancies and match with workers. Calvo-style price +stickiness (parameter $\chi_p$) and wage stickiness ($\chi_w$) generate +standard New Keynesian Phillips curves. + +**Monetary policy** — A Taylor rule that reacts to inflation and the output gap. + +**Exogenous shocks** — Three shocks drive the model: + +1. **Belief shock** $\theta_t$: an AR(1) capturing time-varying pessimism. +2. **TFP shock** $a_t$: standard technology shock. +3. **Monetary policy shock** $r_t$: i.i.d.\ deviation from the Taylor rule. + +### Calibration + +The model is calibrated to quarterly U.S. data, 1982Q1–2019Q4. + +| Parameter | Symbol | Value | Description | +|---|---|---|---| +| Discount factor | $\beta$ | 0.994 | Quarterly | +| Elast. of substitution | $\varepsilon$ | 6 | Price markup | +| Price stickiness | $\chi_p$ | 0.75 | Calvo parameter | +| Wage stickiness | $\chi_w$ | 0.925 | Calvo parameter | +| Mean pessimism | $\mu_\theta$ | 5.64 | | +| Persistence of $\theta$ | $\rho_\theta$ | 0.714 | | +| Volatility of $\theta$ shock | $\sigma_\theta$ | 4.3 | | +| TFP persistence | $\rho_a$ | 0.840 | | +| TFP volatility | $100\sigma_a$ | 0.568% | | +| MP volatility | $100\sigma_r$ | 0.078% | | +| Matching elasticity | $\eta$ | 0.72 | Hosios condition | +| Worker bargaining | $\mu$ | 0.67 | | +| Job-separation rate | $\rho$ | 0.89 | Quarterly | + +### Simplified reduced-form representation + +We capture the model's linearised solution through a reduced-form +vector autoregression + +$$ + +s_{t+1} = A\, s_t + B\, \epsilon_{t+1}, + +$$ + +where $s_t = (u_t, \pi_t, y_t, \theta_t, a_t)'$ collects unemployment, +inflation, output, the belief shock, and TFP, and +$\epsilon_{t+1} \sim N(0, I_3)$ contains the three structural shocks. + +The coefficient matrices $A$ and $B$ are calibrated so that the +impulse-response functions reproduce the key moments reported in Table 2 and +Figure 7 of {cite}`bhandari2025survey`. + +```{code-cell} ipython3 +class ReducedFormNKModel: + """ + Reduced-form linear model calibrated to Bhandari, Borovicka, Ho (2024). + + State vector s_t = [u_t, π_t, y_t, θ_t, a_t] + Shocks: ε = [w_θ, w_a, w_r] + + Belief wedges: + Δ_u = c_u * θ_t + Δ_π = c_π * θ_t + """ + + # Index map for the state vector + I_U, I_PI, I_Y, I_THETA, I_A = 0, 1, 2, 3, 4 + + def __init__(self): + # ---- exogenous-process parameters (Table 1) ---- + self.ρ_θ = 0.714 + self.σ_θ = 4.3 + self.ρ_a = 0.840 + self.σ_a = 0.00568 + self.σ_r = 0.00078 + + # ---- wedge loadings on θ ---- + self.c_u = 0.52 / 5.64 + self.c_π = 1.22 / 5.64 + + # ---- calibrated impact effects ---- + # State variables are stored in FRACTIONS (e.g. u=0.06 for 6%). + # Display code converts: *100 for u,y and *400 for π. + # + # Belief shock targets from Figure 7. + φ_u_θ = 0.009 / self.σ_θ + φ_π_θ = 0.000875 / self.σ_θ + φ_y_θ = -0.009 / self.σ_θ + + # TFP shock targets from Figure 7. + φ_u_a = -0.40 + φ_π_a = -0.10 + φ_y_a = 1.20 + + # Persistence of endogenous variables (quarterly, reduced-form) + ρ_u = 0.35 + ρ_π = 0.50 + ρ_y = 0.35 + + # ---- state transition matrix ---- + self.A = np.array([ + [ρ_u, 0, 0, φ_u_θ, φ_u_a ], # unemployment + [0, ρ_π, 0, φ_π_θ, φ_π_a], # inflation + [0, 0, ρ_y, φ_y_θ, φ_y_a ], # output + [0, 0, 0, self.ρ_θ, 0 ], # belief shock + [0, 0, 0, 0, self.ρ_a], # TFP + ]) + + # ---- shock loading matrix ---- + # Columns: [w_θ, w_a, w_r]. All entries in fraction units. + self.B = np.array([ + [0, 0, 0.5e-3 ], # MP → u fraction + [0, 0, -0.1e-3 ], # MP → pi fraction + [0, 0, -0.5e-3 ], # MP → y fraction + [self.σ_θ, 0, 0 ], # θ innovation + [0, self.σ_a, 0 ], # TFP innovation + ]) + + def irf(self, shock_idx, T=25): + """ + Impulse-response function for a one-std-dev shock. + + Parameters + ---------- + shock_idx : int + 0 = belief shock, 1 = TFP shock, 2 = monetary policy shock + T : int + Number of periods + + Returns + ------- + resp : ndarray (5, T) responses of state vector + wu : ndarray (T,) unemployment wedge response + wpi : ndarray (T,) inflation wedge response + """ + n = self.A.shape[0] + resp = np.zeros((n, T)) + s = self.B[:, shock_idx].copy() # impact response + + for t in range(T): + resp[:, t] = s + s = self.A @ s + + wu = self.c_u * resp[self.I_THETA, :] + w_π = self.c_π * resp[self.I_THETA, :] + return resp, wu, w_π + + def simulate(self, T=200, seed=42): + """Simulate the model for T periods.""" + rng = np.random.default_rng(seed) + k = self.B.shape[1] + s = np.zeros((self.A.shape[0], T)) + for t in range(1, T): + s[:, t] = self.A @ s[:, t-1] + self.B @ rng.standard_normal(k) + return s + + def unconditional_stds(self, include_θ_shock=True): + """ + Unconditional standard deviations computed from the Lyapunov equation. + """ + B_use = self.B.copy() + if not include_θ_shock: + B_use[:, 0] = 0.0 # zero out the belief shock + Σ = solve_discrete_lyapunov(self.A, B_use @ B_use.T) + return np.sqrt(np.diag(Σ)) + + +nk = ReducedFormNKModel() +``` + +## Quantitative results + +### Impulse responses to the belief shock + +A positive innovation to $\theta_t$ makes households more pessimistic. + +The +mechanism works this way: + +1. Pessimistic households expect worse future outcomes and reduce consumption + demand. +2. Lower demand raises unemployment and reduces output. +3. Upward wage pressure from labour-market tightness feeds into inflation. +4. The belief wedges jump on impact, then decay with the persistence + $\rho_\theta = 0.714$. + +```{code-cell} ipython3 +--- +mystnb: + figure: + caption: impulse responses to a belief shock + name: fig-sbbc-belief-shock-irfs +--- +T_irf = 25 +periods = np.arange(T_irf) + +resp_θ, wu_θ, w_π_θ = nk.irf(shock_idx=0, T=T_irf) + +fig, axes = plt.subplots(2, 3, figsize=(13, 7)) +axes = axes.flatten() + +ylabels = ['unemployment (pp)', 'inflation (pp, ann.)', 'output (%)', + 'belief shock θ', 'unemployment wedge Δ(u) (pp)', + 'inflation wedge Δ(π) (pp)'] +series = [resp_θ[0] * 100, # unemployment in pp (fraction × 100) + resp_θ[1] * 400, # inflation ann. pp (quarterly frac × 400) + resp_θ[2] * 100, # output in % (fraction × 100) + resp_θ[3], # belief shock θ + wu_θ, # unemp. wedge (pp): c_u × θ, already in pp + w_π_θ] # infl. wedge (pp): c_π × θ, already in pp +colors = ['steelblue'] * 3 + ['purple', 'steelblue', 'darkorange'] + +for ax, ylabel, y, color in zip(axes, ylabels, series, colors): + ax.plot(periods, y, color=color, linewidth=2) + ax.axhline(0, color='grey', linewidth=0.7, linestyle='--') + ax.set_ylabel(ylabel) + ax.set_xlabel('quarters') + +plt.tight_layout() +plt.show() +``` + +The impulse responses show that a belief shock: + +* Raises unemployment persistently (peak effect around 1 pp). +* Raises inflation on impact, as higher pessimism tightens labour markets + in the model. +* Generates belief wedges for both unemployment and inflation that closely + mirror the dynamics of $\theta_t$ itself — consistent with the one-factor + structure. + +### The unemployment volatility puzzle + +A long-standing challenge for New Keynesian models is that standard TFP and +monetary policy shocks generate far too little unemployment volatility +({cite}`Shimer2005`). + +With only TFP and monetary policy shocks, the model +produces unemployment volatility of roughly 0.55%, compared to about 1.70% +in the data. + +Adding the belief shock substantially closes the gap: + +```{code-cell} ipython3 +--- +mystnb: + figure: + caption: model and data volatility comparison + name: fig-sbbc-volatility-comparison +--- +std_full = nk.unconditional_stds(include_θ_shock=True) +std_no_θ = nk.unconditional_stds(include_θ_shock=False) + +labels_vol = ['Unemployment', 'Inflation', 'Output'] +idx = [nk.I_U, nk.I_PI, nk.I_Y] +scale = [100, 400, 100] # convert to pp (unemployment, annualised inflation, %) + +std_full_scaled = [std_full[i] * scale[j] for j, i in enumerate(idx)] +std_no_θ_scaled = [std_no_θ[i] * scale[j] for j, i in enumerate(idx)] + +# Reference values from Table 2 of the paper +data_std = [1.70, 1.07, 2.23] # data standard deviations + +x = np.arange(len(labels_vol)) +width = 0.25 + +fig, ax = plt.subplots() +ax.bar(x - width, std_no_θ_scaled, width, label='Model (no belief shock)', + color='steelblue', alpha=0.7) +ax.bar(x, std_full_scaled, width, label='Model (with belief shock)', + color='firebrick', alpha=0.7) +ax.bar(x + width, data_std, width, label='Data', + color='grey', alpha=0.7) + +ax.set_xticks(x) +ax.set_xticklabels(labels_vol) +ax.set_ylabel('standard deviation (% or pp, ann.)') +ax.legend() +plt.tight_layout() +plt.show() + +print("Unconditional standard deviations:") +print(f"{'Variable':<18} {'No belief shock':>16} " + f"{'With belief shock':>18} {'Data':>10}") +print('-' * 65) +for label, std_n, std_f, std_d in zip(labels_vol, std_no_θ_scaled, + std_full_scaled, data_std): + print(f"{label:<18} {std_n:>16.2f} {std_f:>18.2f} {std_d:>10.2f}") +``` + +The table confirms the key quantitative message: without the belief shock, +unemployment volatility is far below its empirical counterpart, but adding +the calibrated belief shock nearly doubles it, bringing the model much closer +to the data. + +### Impulse responses to TFP and monetary policy shocks + +For completeness, we also show responses to the other two shocks. + +```{code-cell} ipython3 +--- +mystnb: + figure: + caption: impulse responses to TFP and monetary policy shocks + name: fig-sbbc-tfp-mp-irfs +--- +resp_a, _, _ = nk.irf(shock_idx=1, T=T_irf) # TFP shock +resp_r, _, _ = nk.irf(shock_idx=2, T=T_irf) # Monetary policy shock + +fig, axes = plt.subplots(2, 3, figsize=(13, 7)) +axes = axes.flatten() + +series_a = [resp_a[0]*100, resp_a[1]*400, resp_a[2]*100] +series_r = [resp_r[0]*100, resp_r[1]*400, resp_r[2]*100] +var_ylabels = ['unemployment (pp)', 'inflation (pp, ann.)', 'output (%)'] + +for j, (ylabel, ya, yr) in enumerate(zip(var_ylabels, series_a, series_r)): + # TFP + axes[j].plot(periods, ya, color='steelblue', linewidth=2, + label='TFP shock') + axes[j].axhline(0, color='grey', linewidth=0.7, linestyle='--') + axes[j].set_ylabel(ylabel) + axes[j].set_xlabel('quarters') + axes[j].legend(loc='best') + + # Monetary policy + axes[j+3].plot(periods, yr, color='darkorange', linewidth=2, + label='MP shock') + axes[j+3].axhline(0, color='grey', linewidth=0.7, linestyle='--') + axes[j+3].set_ylabel(ylabel) + axes[j+3].set_xlabel('quarters') + axes[j+3].legend(loc='best') + +plt.tight_layout() +plt.show() +``` + +### Role of firms' beliefs + +{cite}`bhandari2025survey` also study a variant in which **firms** hold +subjective beliefs. + +The key channel is through the price-setting equation: +when firms fear that future demand will be weaker than the model predicts, +they raise prices today to protect margins, generating **higher inflation** on +impact. + +This mechanism strengthens the comovement between the unemployment +wedge and the inflation wedge, which is needed to match the data. + +The sign of the inflation response to a belief shock is therefore a +diagnostic: positive responses to pessimistic shocks require firms (not just +households) to hold subjective beliefs. + +### Countercyclicality of wedges + +A final important prediction is that belief wedges are countercyclical. + +Recessions are periods of high $\theta_t$, which raises both the unemployment +wedge and the inflation wedge simultaneously. + +The code below simulates a +long run of the model and shows this property: + +```{code-cell} ipython3 +--- +mystnb: + figure: + caption: simulated countercyclicality of belief wedges + name: fig-sbbc-countercyclical-wedges +--- +sim = nk.simulate(T=400, seed=99) +θ_sim = sim[nk.I_THETA] +y_sim = sim[nk.I_Y] * 100 + +# c_u and c_pi are in pp per unit θ, so the wedge is already in pp +wu_sim_series = nk.c_u * θ_sim +w_π_sim_series = nk.c_π * θ_sim + +fig, axes = plt.subplots(3, 1, figsize=(11, 8), sharex=True) + +axes[0].plot(y_sim, color='steelblue', linewidth=2, label='Output gap (%)') +axes[0].axhline(0, color='grey', linestyle='--', linewidth=0.7) +axes[0].set_ylabel('%') +axes[0].legend(loc='upper right') + +axes[1].plot(wu_sim_series, color='firebrick', linewidth=2, + label='Unemployment belief wedge (pp)') +axes[1].set_ylabel('pp') +axes[1].legend(loc='upper right') + +axes[2].plot(w_π_sim_series, color='darkorange', linewidth=2, + label='Inflation belief wedge (pp)') +axes[2].set_ylabel('pp') +axes[2].legend(loc='upper right') +axes[2].set_xlabel('quarter') + +plt.tight_layout() +plt.show() + +# Confirm countercyclicality numerically +corr_u = np.corrcoef(y_sim, wu_sim_series)[0, 1] +corr_π = np.corrcoef(y_sim, w_π_sim_series)[0, 1] +print(f"Corr(output gap, unemployment wedge) = {corr_u:.3f} " + f"(data: −0.49)") +print(f"Corr(output gap, inflation wedge) = {corr_π:.3f} " + f"(data: −0.30)") +``` + +The simulated correlations are negative, confirming the countercyclicality +predicted by the model and documented in the survey data. + +## Extensions + +The paper explores several important extensions: + +**Heterogeneous beliefs** — A natural question is whether households and +firms should hold the same subjective beliefs. + +The paper shows that +allowing firms to be *rational* while households are pessimistic changes +the inflation dynamics substantially. + +This separation is identified from +the relative sizes of the unemployment and inflation wedges. + +**Higher-order perturbation** — The first-order approximation provides +clean analytical formulas for belief wedges, but second-order effects +(precautionary savings, volatility feedback) matter for welfare analysis. + +The paper develops second-order expansions and shows they affect the wedge +levels but not the one-factor structure. + +**Idiosyncratic risk** — In the full model households face idiosyncratic +labour-market risk. + +The interaction between aggregate pessimism and +uninsurable idiosyncratic shocks amplifies the effect of belief distortions +on precautionary savings, strengthening the unemployment channel. + +## Appendix: the series expansion method + +This appendix follows the Online Appendix of {cite}`BhandariBorovickaHo2024` +and fills in the computational and theoretical details underlying the +linearisation presented in the main lecture. + +### Multi-period belief wedges + +The main text focused on the one-period belief wedge +$\Delta_t^{(1)}(z)$. + +The paper also uses $\tau$-period-ahead wedges +$\Delta_t^{(\tau)}(z) = \tilde E_t[z_{t+\tau}] - E_t[z_{t+\tau}]$, +which are needed to match survey respondents' longer-horizon forecasts. + +Under linear dynamics + +$$ + +x_{t+1} = \psi_q + \psi_x x_t + \psi_w w_{t+1}, +\qquad w_{t+1} \sim N(0, I_k), + +$$ + +the $\tau$-period-ahead expectation under the data-generating measure +satisfies the recursion + +$$ + +G_x^{(\tau)} = \psi_x G_x^{(\tau-1)} + \psi_x, +\qquad +G_x^{(0)} = 0, + +$$ + +so that $E_t[x_{t+\tau} - x_t] = G_x^{(\tau)} x_{1t} + G_0^{(\tau)}$. + +Under the **subjective** measure, the mean of $w_{t+1}$ is shifted to +$\nu_t = H + HF x_{1t}$ (equation OA.1 of the appendix). For the +stationary model the relevant identifications are + +$$ + +F = \bar\theta, +\qquad +H = -(V_x \psi_w)', +\qquad +\bar H = -\bar\theta\,\bar x\,(V_x \psi_w)'. + +$$ + +The subjective expectation then obeys a modified recursion + +$$ + +\tilde G_x^{(\tau)} = \psi_x \tilde G_x^{(\tau-1)} + \psi_x + + \bigl(\psi_w + \tilde G_x^{(\tau-1)}\psi_w\bigr) HF, + +$$ + +and the $\tau$-period belief wedge is + +$$ + +\Delta_t^{(\tau)} = \bigl(\tilde G_x^{(\tau)} - G_x^{(\tau)}\bigr) x_{1t} + + \tilde G_0^{(\tau)} - G_0^{(\tau)}. + +$$ + +The code below implements these recursions and shows how belief wedges grow +with the forecast horizon. + +```{code-cell} ipython3 +def compute_tau_wedge_loadings(ψ_x, ψ_w, H, H_bar, F, τ_max=20): + """ + Compute tau-period belief wedge loadings using the recursions from + Online Appendix OA.1 of Bhandari, Borovicka, Ho (2024). + + For simplicity we work with the scalar stationary case (all quantities + are scalars or 1-d arrays). + + Returns + ------- + wedge_const : array (tau_max,) constant term of wedge (G0_tilde - G0) + wedge_slope : array (tau_max,) x1t loading of wedge (Gx_tilde - Gx) + """ + n = ψ_x.shape[0] + Gx = np.zeros((n, n)) + Gx_tild = np.zeros((n, n)) + G0 = np.zeros(n) + G0_tild = np.zeros(n) + + wedge_const = np.zeros(τ_max) + wedge_slope = np.zeros((τ_max, n)) + + for τ in range(1, τ_max + 1): + # data-generating measure + new_Gx = (Gx + np.eye(n)) @ ψ_x + new_G0 = G0 + (Gx + np.eye(n)) @ ψ_w @ np.zeros(ψ_w.shape[1]) + # (constant-shock term is zero under objective measure) + + # subjective measure + new_Gx_tild = ((Gx_tild + np.eye(n)) @ ψ_x + + (Gx_tild + np.eye(n)) @ ψ_w @ (H @ F)) + new_G0_tild = (G0_tild + + ((Gx_tild + np.eye(n)) @ ψ_w @ H_bar).ravel()) + + Gx, G0 = new_Gx, new_G0 + Gx_tild, G0_tild = new_Gx_tild, new_G0_tild + + wedge_slope[τ - 1] = (Gx_tild - Gx)[0] + wedge_const[τ - 1] = float((G0_tild - G0)[0]) + + return wedge_const, wedge_slope +``` + +```{code-cell} ipython3 +--- +mystnb: + figure: + caption: multi-period belief wedge profile + name: fig-sbbc-horizon-wedge +--- +# ----------------------------------------------------------------- +# Illustrate the wedge horizon profile in the simple endowment economy +# using the solved BeliefDistortionModel. +# ----------------------------------------------------------------- + +# Scalar model: ψ_x = [[ρ_x]], ψ_w = [[σ_x]] +ψ_x_sc = np.array([[model.ρ_x]]) +ψ_w_sc = np.array([[model.σ_x]]) +F_sc = np.array([[model.μ_θ]]) # θ-bar +H_sc = np.array([[-model.Vx * model.σ_x]]) # -(Vx ψ_w)' +H_bar_sc = model.μ_θ * model.ρ_x * np.array([[-model.Vx * model.σ_x]]) + +τ_max = 20 +wc, ws = compute_tau_wedge_loadings(ψ_x_sc, ψ_w_sc, H_sc, H_bar_sc, F_sc, τ_max) + +# For illustration, evaluate at x1t = +1 std dev of θ. +θ_std = model.σ_θ / np.sqrt(1 - model.ρ_θ**2) + +fig, ax = plt.subplots() +τ_grid = np.arange(1, τ_max + 1) +ax.plot(τ_grid, wc * 100, + color='steelblue', linewidth=2, label='Wedge at mean ($x_{1t}=0$)') +ax.plot(τ_grid, (wc + ws[:, 0] * θ_std) * 100, + color='firebrick', linewidth=2, linestyle='--', + label='Wedge at $+1\\,\\sigma_\\theta$ deviation') +ax.axhline(0, color='grey', linewidth=0.7, linestyle=':') +ax.set_xlabel('forecast horizon $\\tau$ (quarters)') +ax.set_ylabel('belief wedge (pp)') +ax.legend() +plt.tight_layout() +plt.show() +``` + +### The series expansion + +{cite}`BhandariBorovickaHo2024` solve the full general-equilibrium model +using a **series expansion** (perturbation) method +({cite}`BorovickaHansen2014`). + +The key innovation is a **joint +perturbation** of the shock volatility $q$ and the penalty parameter +$\theta_t$. + +#### Law of motion + +Index the model by a scalar perturbation parameter $\mathsf{q}$ that +scales shock volatility: + +$$ + +x_{t+1}(\mathsf{q}) = \psi\!\left(x_t(\mathsf{q}),\, + \mathsf{q}\, w_{t+1},\, \mathsf{q}\right). + +$$ + +Expanding around $\mathsf{q} = 0$ gives + +$$ + +x_t(\mathsf{q}) \approx \bar x + \mathsf{q}\, x_{1t} + + \tfrac{\mathsf{q}^2}{2}\, x_{2t} + \cdots + +$$ + +The first-order dynamics are + +$$ + +x_{1,t+1} = \psi_q + \psi_x x_{1t} + \psi_w w_{t+1}. + +$$ + +#### Continuation value and the Riccati equation + +To preserve a nontrivial role for beliefs at first order, the penalty +parameter is **jointly scaled** with $\mathsf{q}$: the effective +penalisation in the perturbed recursion (OA.8) is +$\mathsf{q}/[\bar\theta(\bar x + x_{1t})]$, +which shrinks together with shock volatility. + +This ensures that the +deterministic steady state does not collapse to the rational-expectations +solution. + +Guessing $V_{1t} = V_x x_{1t} + V_q$ and matching coefficients yields +the **Riccati equation for $V_x$** (equation OA.20 of the appendix): + +$$ + +V_x = u_x - \frac{\beta}{2}\, V_x \psi_w \psi_w' V_x' \bar\theta + + \beta\, V_x \psi_x, + +$$ + +and the constant + +$$ + +V_q = u_q - \frac{\beta}{2}\,\bar\theta\, \bar x\, + V_x \psi_w \psi_w' V_x' + \beta\, V_x \psi_q + \beta V_q. + +$$ + +The Riccati equation is quadratic in $V_x$. For the stationary scalar case it +reduces to + +$$ + +a\, V_x^2 + b\, V_x + c = 0, +\qquad +a = \frac{\beta}{2}\sigma_x^2 \bar\theta,\quad +b = -(1 - \beta\rho_x),\quad +c = u_x. + +$$ + +#### Shock distribution under subjective beliefs + +Substituting the first-order expansion into the distortion formula +(OA.10) shows that the leading term $m_{0,t+1}$ is a lognormal change of +measure. With Gaussian shocks, this is equivalent to shifting the +innovation mean (equation OA.12): + +$$ + +w_{t+1} \;\sim\; +N\!\left(-\bar\theta(\bar x + x_{1t})(V_x \psi_w)',\; I_k\right). + +$$ + +Belief wedges for the state vector follow immediately: + +$$ + +\Delta_t^{(1)} = \tilde E_t[x_{t+1}] - E_t[x_{t+1}] += \psi_w\, \tilde E_t[w_{t+1}] += -\bar\theta(\bar x + x_{1t})(\psi_w \psi_w') V_x'. + +$$ + +#### Equilibrium conditions with subjective beliefs + +The full model's equilibrium conditions take the form + +$$ + +0 = E_t\!\left[\mathbb{M}_{t+1}\, g(x_{t+1}, x_t, x_{t-1}, w_{t+1}, w_t)\right], + +$$ + +where $\mathbb{M}_{t+1} = \mathrm{diag}(m_{t+1}^{\sigma_1}, \ldots, +m_{t+1}^{\sigma_n})$ selects which equations involve subjective +expectations ($\sigma_i = 1$) versus objective ones ($\sigma_i = 0$). + +First-order expansion of these conditions gives a system in the unknown +policy matrices $\psi_x, \psi_w, \psi_q$: + +$$ + +0 = (g_{x^+}\psi_x + g_x - \mathbb{E})\,\psi_x + g_{x^-} + +$$ + +$$ + +0 = (g_{x^+}\psi_x + g_x - \mathbb{E})\,\psi_w + g_w + +$$ + +$$ + +0 = (g_{x^+}\psi_x + g_{x^+} + g_x)\,\psi_q + g_q + - \mathbb{E}(\bar x + \psi_q), + +$$ + +where the **belief distortion matrix** $\mathbb{E}$ collects the impact +of subjective expectations on each equation: + +$$ + +\mathbb{E} = \operatorname{stack}\Bigl\{ + \sigma_i\, [g_{x^+}\psi_w + g_{w^+}]^i\, + (V_x \psi_w)'\, \bar\theta +\Bigr\}. + +$$ + +These equations (OA.17–OA.21) are solved jointly with the Riccati +equation for $V_x$. + +Compared with the standard Blanchard–Kahn solution, +the only modification is the additive term $-\mathbb{E}$ that shifts the +characteristic matrix; when $\bar\theta = 0$ we recover the standard +rational-expectations solution. + +#### The AR(1) belief shock as a special case + +In the paper's application $\theta_t$ is itself an exogenous AR(1) +process (equation OA.22): + +$$ + +f_{t+1} = (1 - \rho_f)\bar f + \rho_f f_t + \sigma_f w_{t+1}^f. + +$$ + +Appending $f_t$ to the state vector, the first-order dynamics become + +$$ + +\begin{pmatrix} x_{1,t+1} \\ f_{1,t+1} \end{pmatrix} += \begin{pmatrix} \psi_q \\ 0 \end{pmatrix} ++ \begin{pmatrix} \psi_x & \rho_f \psi_{xf} \\ 0 & \rho_f \end{pmatrix} +\begin{pmatrix} x_{1t} \\ f_{1t} \end{pmatrix} ++ \begin{pmatrix} \psi_w & \sigma_f \psi_{xf} \\ 0 & \sigma_f \end{pmatrix} +\begin{pmatrix} w_{t+1} \\ w_{t+1}^f \end{pmatrix}. + +$$ + +The new coefficient $\psi_{xf}$ measures how a unit change in the belief +shock $f_{1t}$ feeds into the endogenous state variables. + +It is determined +by the backward-induction algorithm (equations OA.31–OA.34), which iterates +from a distant terminal date $T$ (where belief distortions vanish) back to +the present. + +The continuation value in the $f$-direction satisfies a separate recursion +for $V_f$ (equation OA.29), and the belief distortion matrix becomes + +$$ + +\mathbb{E} = \operatorname{stack}\Bigl\{ + \sigma_i\bigl[ + g_{x^+}\psi_{xf}\sigma_f^2(V_f + V_x\psi_{xf}) + + (g_{x^+}\psi_w + g_{w^+})\psi_w' V_x' + \bigr]^i +\Bigr\}\bar\theta_f. + +$$ + +The algorithm therefore decomposes cleanly into two stages: + +1. **Stage 1 (rational-expectations block)**: solve (OA.24) and (OA.26) for + $\psi_x$, $\psi_w$ using the standard Blanchard–Kahn method — these are + *unaffected* by the belief shock. + +2. **Stage 2 (belief distortion block)**: given $\psi_x, \psi_w, V_x$, + iterate (OA.31–OA.34) backward to convergence to find $\psi_{xf}$, + $V_f$, and $\mathbb{E}$. + +This separation is a major practical advantage: existing rational-expectations +solvers can be used for Stage 1 with only a wrapper for Stage 2. + +```{code-cell} ipython3 +# ----------------------------------------------------------------- +# Demonstrate the limiting Stage 2 fixed point in a stylised scalar economy. +# +# Setup: +# - Endogenous state x, belief shock f (= θ_t) +# - ψ_x (1x1), ψ_w (1x1) known from Stage 1 +# - Vx known from the Riccati equation +# - Solve the first-order fixed point for Vf and ψ_xf +# ----------------------------------------------------------------- + +β = model.β +ρ_x = model.ρ_x +σ_x = model.σ_x +ρ_f = model.ρ_θ +σ_f = model.σ_θ +Vx = model.Vx + +# Stage 1 objects (RE solution) +ψ_x_s1 = ρ_x +ψ_w_s1 = σ_x + +# gx+ = β * (1 - β) in the simple log-utility endowment economy +# (partial derivative of marginal utility w.r.t. x_{t+1}) +gx_plus = β * (1 - β) + +θ_f = 1.0 # f is θ in the partitioned state. + +# First-order scalar fixed point. +# +# The full nonlinear backward recursion is stable in the paper's full model, +# but the stripped-down scalar example can diverge because it lacks the +# stabilising equilibrium blocks. We therefore solve the first-order limiting +# system directly. +denom = gx_plus * ψ_x_s1 - (1 - β) +E_const = (gx_plus * ψ_w_s1) * ψ_w_s1 * Vx * θ_f +penalty_const = (β * θ_f / 2.0) * (Vx * ψ_w_s1)**2 + +A_fp = np.array([ + [1 - β * ρ_f, -β * ρ_f * Vx], + [0.0, 1 + gx_plus * ρ_f / denom], +]) +b_fp = np.array([ + -penalty_const, + E_const / denom, +]) + +Vf, ψ_xf = np.linalg.solve(A_fp, b_fp) + +print("Stage 2 fixed point:") +print(f" Vf = {Vf:.6f}") +print(f" ψ_xf = {ψ_xf:.6f} (impact of belief shock on endogenous state)") +print() +print("Interpretation: a one-unit rise in f_t changes x by ψ_xf =", + f"{ψ_xf:.4f} per period.") +print("The steady-state wedge for x: Δ = ψ_w * ν_bar =", + f"{σ_x * (-model.μ_θ * (Vx * σ_x)):.4f}") +``` + +### Sequence problem and dynamic consistency + +The recursive formulation used throughout the lecture emerges from the +following sequence problem (Online Appendix OA.3). Define the discounted +entropy functional + +$$ + +\mathcal{E}_t \;=\; E_t \sum_{j=0}^{\infty} \beta^j + \left[ M_{t,t+j} \frac{\beta}{\theta_{t+j}} + E_{t+j}[m_{t+j+1} \log m_{t+j+1}] + \right], + +$$ + +where $M_{t,t+j} = \prod_{k=1}^j m_{t+k}$. The agent's problem is + +$$ + +V_t^* = \max_{\{y_{t+j}\}} \min_{\substack{m_{t+j}>0 \\ E_{t+j-1}[m_{t+j}]=1}} + \sum_{j=0}^{\infty} \beta^j E_t[M_{t,t+j} u_{t+j}] + + \mathcal{E}_t. + +$$ + +The penalty functional $\mathcal{E}_t$ **discounts future entropies +weighted by future penalty parameters $\theta_{t+j}$**, which makes the +agent's choices dynamically consistent: she anticipates how her +pessimism will evolve. + +This differs from the infinite-horizon discounted entropy used in +{cite}`HansenSargent2001`, which is not generally dynamically consistent +when $\theta_t$ is time-varying. The recursive form is: + +$$ + +\mathcal{E}_t = \frac{\beta}{\theta_t} E_t[m_{t+1} \log m_{t+1}] + + \beta E_t[m_{t+1} \mathcal{E}_{t+1}]. + +$$ + +Under this penalty, the minimax inequality is an equality, and the value +function satisfies the recursive form stated in the main lecture: + +$$ + +V_t^* = \max_{y_t} \min_{\substack{m_{t+1}>0 \\ E_t[m_{t+1}]=1}} + u_t + \frac{\beta}{\theta_t} E_t[m_{t+1} \log m_{t+1}] + + E_t[m_{t+1} V_{t+1}^*]. + +$$ + +```{code-cell} ipython3 +# ----------------------------------------------------------------- +# Illustrate the role of dynamic consistency by comparing two penalty +# specifications: +# (a) Paper specification: E_t = (β/θ_t) * H_t + β * E[m*E_{t+1}] +# where H_t = E_t[m_{t+1} log m_{t+1}] +# (b) A myopic version that uses only the one-period entropy: +# E_t^{myopic} = (β/θ_t) * H_t +# +# We compare the implied belief wedge as θ varies. +# ----------------------------------------------------------------- + +θ_path = np.array([3.0, 5.64, 8.0, 12.0]) # rising pessimism scenario + +def one_period_entropy(θ, Vx, σ_x): + """ + Entropy E_t[m_{t+1} log m_{t+1}] under the optimal distortion + for Gaussian shocks: = (1/2) * (θ * Vx * σ_x)^2. + """ + return 0.5 * (θ * Vx * σ_x) ** 2 + +print("Effect of time-varying θ on entropy and belief wedge:") +print(f"{'θ_t':>8} {'H_t (entropy)':>16} {'Δ(x) = σ_x ν_t (pp)':>22}") +print('-' * 52) +for th in θ_path: + H = one_period_entropy(th, model.Vx, model.σ_x) + bw = model.belief_wedge(th) * 100 + print(f"{th:>8.2f} {H:>16.6f} {bw:>22.4f}") + +print() +print("The entropy penalty grows quadratically in θ,") +print("constraining the agent from distorting beliefs too heavily.") +``` + +## Exercises + +```{exercise-start} +:label: sbbc_ex1 +``` + +**Belief wedge sign** + +In the simple endowment economy of the `BeliefDistortionModel`, suppose the state +variable is log consumption $x_t$ with $\rho_x = 0.90$, $\sigma_x = 0.01$, +$\beta = 0.99$. + +(a) Compute $V_x$ under rational expectations and under pessimism + $\mu_\theta = 4$. +(b) What is the sign of the belief wedge for consumption growth? +(c) If instead the agent forecasts unemployment (which enters the value + function with a negative sign, so $u_x < 0$), what is the sign of the + unemployment belief wedge? +```{exercise-end} +``` + +```{solution-start} sbbc_ex1 +:label: sbbc_ex1_sol +:class: dropdown +``` + +**Part (a)** — Under rational expectations ($\theta = 0$): + +$$ + +V_x^{RE} = \frac{u_x}{1 - \beta \rho_x} + = \frac{1 - \beta}{1 - \beta \rho_x}. + +$$ + +```{code-cell} ipython3 +β_ex = 0.99 +ρ_x_ex = 0.90 +σ_x_ex = 0.01 +μ_θ_ex = 4.0 + +Vx_re_ex = (1 - β_ex) / (1 - β_ex * ρ_x_ex) +print(f"V_x (rational expectations): {Vx_re_ex:.4f}") + +m_ex = BeliefDistortionModel(β=β_ex, ρ_x=ρ_x_ex, + σ_x=σ_x_ex, μ_θ=μ_θ_ex) +print(f"V_x (with pessimism θ̄={μ_θ_ex}): {m_ex.Vx:.4f}") +``` + +**Part (b)** — The belief wedge for consumption growth is + +$$ + +\Delta_t^{(1)}(x) += -\theta_t V_x \sigma_x^2. + +$$ + +Since $V_x > 0$ and $\theta_t > 0$, the wedge is **negative**: pessimistic +agents underestimate consumption growth relative to the model. + +**Part (c)** — For unemployment, $u_x < 0$, so $V_x^u < 0$. The belief +wedge becomes + +$$ + +\Delta_t^{(1)}(u) += -\theta_t V_x^u \sigma_x^2 > 0 + +$$ + +(positive, because pessimism makes agents over-estimate unemployment). +This matches the empirical finding of a positive mean unemployment wedge. + +```{solution-end} +``` + +```{exercise-start} +:label: sbbc_ex2 +``` + +**Persistence and wedge volatility** + +Using the `BeliefDistortionModel` class, vary $\rho_\theta$ from 0.3 to +0.95 (holding $\sigma_\theta = 4.3$ fixed) and plot how the standard +deviation of the belief wedge changes. + +Explain the economic intuition. +```{exercise-end} +``` + +```{solution-start} sbbc_ex2 +:label: sbbc_ex2_sol +:class: dropdown +``` + +```{code-cell} ipython3 +--- +mystnb: + figure: + caption: persistence and belief-wedge volatility + name: fig-sbbc-persistence-volatility +--- +ρ_vals = np.linspace(0.3, 0.95, 30) +wedge_stds = [] + +for ρ in ρ_vals: + m_temp = BeliefDistortionModel(ρ_θ=ρ) + θ_sim_temp = m_temp.simulate_θ(T=5000, seed=0) + wedge_sim_temp = m_temp.belief_wedge(θ_sim_temp) + wedge_stds.append(np.std(wedge_sim_temp)) + +fig, ax = plt.subplots() +ax.plot(ρ_vals, np.array(wedge_stds) * 100, color='steelblue', linewidth=2) +ax.set_xlabel('persistence $\\rho_\\theta$') +ax.set_ylabel('standard deviation of belief wedge (pp)') +plt.tight_layout() +plt.show() +``` + +Higher persistence $\rho_\theta$ means that a given innovation to $\theta_t$ +has more prolonged effects: the unconditional variance of an AR(1) with +volatility $\sigma$ is $\sigma^2 / (1 - \rho^2)$, which increases in $\rho$. + +Since the wedge is proportional to $\theta_t$, its standard deviation +inherits this relationship and rises with $\rho_\theta$. + +```{solution-end} +``` + +```{exercise-start} +:label: sbbc_ex3 +``` + +**Unemployment volatility decomposition** + +Using the `ReducedFormNKModel` class: + +(a) Compute the fraction of unemployment variance explained by each of the + three shocks. +(b) Show that the belief shock is the dominant driver of unemployment + fluctuations, while TFP is the dominant driver of output fluctuations. +```{exercise-end} +``` + +```{solution-start} sbbc_ex3 +:label: sbbc_ex3_sol +:class: dropdown +``` + +```{code-cell} ipython3 +# Variance decomposition via the Lyapunov equation, shock-by-shock + +shock_names = ['Belief shock (θ)', 'TFP shock', 'MP shock'] +var_labels = ['Unemployment', 'Inflation', 'Output'] + +nk2 = ReducedFormNKModel() + +# Compute the variance of each variable attributable to each shock +n_states = nk2.A.shape[0] +var_by_shock = np.zeros((n_states, 3)) + +for j in range(3): + B_j = np.outer(nk2.B[:, j], nk2.B[:, j]) + Σ_j = solve_discrete_lyapunov(nk2.A, B_j) + var_by_shock[:, j] = np.diag(Σ_j) + +# Total variance +var_total = var_by_shock.sum(axis=1) + +# Print share of variance for key variables +print(f"{'Variable':<16}", *[f"{s:>20}" for s in shock_names]) +print('-' * 77) +for i, label in zip([nk2.I_U, nk2.I_PI, nk2.I_Y], var_labels): + shares = var_by_shock[i] / var_total[i] * 100 + print(f"{label:<16}", *[f"{s:>19.1f}%" for s in shares]) +``` + +The belief shock accounts for the majority of unemployment variance, as +reported in the paper. + +Technology shocks drive most of the output variance +(through their high persistence and direct effect on productivity). + +Monetary +policy shocks play a smaller role for both variables. + +```{solution-end} +``` + +```{exercise-start} +:label: sbbc_ex4 +``` + +**Changing the degree of pessimism** + +Solve the Riccati equation in the `BeliefDistortionModel` for a grid of +$\mu_\theta$ values from 0 (rational expectations) to 15. + +For each value, +compute the steady-state (unconditional mean) belief wedge and the ratio of +robust to rational $V_x$. + +Discuss how the robust value function differs from +the rational-expectations value function. +```{exercise-end} +``` + +```{solution-start} sbbc_ex4 +:label: sbbc_ex4_sol +:class: dropdown +``` + +```{code-cell} ipython3 +--- +mystnb: + figure: + caption: value sensitivity and steady-state wedge + name: fig-sbbc-pessimism-riccati +--- +μ_grid = np.linspace(0, 15, 100) +Vx_vals = [] +wedge_ss = [] + +Vx_re = (1 - 0.994) / (1 - 0.994 * 0.85) + +for μ in μ_grid: + m_temp = BeliefDistortionModel(μ_θ=μ) + Vx_vals.append(m_temp.Vx) + wedge_ss.append(m_temp.belief_wedge(μ) * 100) # in pp + +fig, axes = plt.subplots(1, 2, figsize=(11, 4)) + +axes[0].plot(μ_grid, Vx_vals, color='steelblue', linewidth=2) +axes[0].axhline(Vx_re, color='grey', linestyle='--', + label=f'RE value $V_x^{{RE}}={Vx_re:.3f}$') +axes[0].set_xlabel('mean pessimism $\\mu_\\theta$') +axes[0].set_ylabel('$V_x$') +axes[0].legend() + +axes[1].plot(μ_grid, np.array(wedge_ss), color='firebrick', linewidth=2) +axes[1].set_xlabel('mean pessimism $\\mu_\\theta$') +axes[1].set_ylabel('steady-state wedge (pp)') + +plt.tight_layout() +plt.show() +``` + +As $\mu_\theta$ rises, the Riccati equation introduces an additional +curvature term that lowers $V_x$ (less marginal value to the current state) +because the agent effectively prices in the possibility of bad future +outcomes. + +The steady-state wedge grows approximately linearly in +$\mu_\theta$, since $\Delta^{(1)} \propto \mu_\theta V_x \sigma_x^2$ and +$V_x$ is approximately constant for small $\mu_\theta$. + +```{solution-end} +``` diff --git a/lectures/tsyrennikov_2013.md b/lectures/tsyrennikov_2013.md index 91104ed4..7ed5807f 100644 --- a/lectures/tsyrennikov_2013.md +++ b/lectures/tsyrennikov_2013.md @@ -9,36 +9,28 @@ kernelspec: name: python3 --- -```{raw} html - -``` - (tsyrennikov_2013)= # Capital Flows Under Moral Hazard ## Overview -This lecture studies {cite}`Tsyrennikov2013`, which extends {cite}`Atkeson1991` -(see the companion lecture {ref}`atkeson_1991`) in two directions: +This lecture studies {cite:t}`Tsyrennikov2013`, which extends {cite:t}`Atkeson1991` +(see the companion lecture {doc}`atkeson_1991`) in two directions: 1. **Continuous investment** — the borrower chooses a continuous investment level rather than a binary one, and the paper proves that the **first-order approach** (FOA) to the incentive-compatibility constraint is - valid. This brings the model much closer to empirically relevant calibrations. + valid. This brings the model much closer to empirically relevant calibrations. 2. **Calibration and quantitative analysis** — the model is calibrated to Argentina's business cycle data and compared against a limited-enforcement (Eaton–Gersowitz-style) model. -The central finding is that **moral hazard, not limited enforcement, drives the -key empirical regularities of emerging market economies**: high and volatile +The central finding is that *moral hazard, not limited enforcement, drives the +key empirical regularities of emerging market economies*: high and volatile interest rate spreads, limited consumption risk-sharing, and crisis-like dynamics in which capital inflows suddenly stop. -The key mechanism is that moral hazard severely restricts *state contingency* in +The key mechanism is that moral hazard severely restricts *state contingency* in repayment schedules. In the language of {cite}`Atkeson1991`, the optimal @@ -46,14 +38,14 @@ contract is nearly *non-contingent* on output — a theoretical justification fo why simple debt contracts dominate in practice. ```{note} -This lecture uses the same notation as the {ref}`atkeson_1991` lecture, +This lecture uses the same notation as the {doc}`atkeson_1991` lecture, writing $\beta$ for the borrower's discount factor (Tsyrennikov writes $\beta$ for the borrower and $\beta_c$ for the lender). ``` -## The Model +## The model -### Technology and Preferences +### Technology and preferences The environment is a small open economy with an infinitely-lived borrower. @@ -98,15 +90,17 @@ $$ Lenders discount at rate $\beta_c \geq \beta$ (the international risk-free rate) and have endowment $M$ each period, so $b \leq M$. -### Two Frictions +### Two frictions + +**Moral hazard (MH)**: lenders observe output but not investment. -**Moral hazard (MH)**: lenders observe output but not investment. The -incentive-compatibility (IC) constraint requires that the borrower finds the +The incentive-compatibility (IC) constraint requires that the borrower finds the contracted investment $I$ to be in their own best interest. **Limited enforcement (LE)**: the borrower can default, suffering a one-time output penalty: if default occurs when output is $Y_j$, the borrower retains only $\delta Y_j$ (with $\delta \in (0,1)$) and then lives in autarky. + The participation constraint requires $$ @@ -116,7 +110,7 @@ $$ where $V$ is the contract value function and $V_{\text{aut}}$ is the autarky value function. -### The Autarky Value Function +### The autarky value function Without access to credit ($b = 0$), the borrower solves @@ -128,7 +122,7 @@ $$ Note that the continuation values depend only on $Y_1$ and $Y_2$, not on $n$. -### The Recursive Contract +### The recursive contract The state variable is net worth $n$. @@ -144,16 +138,16 @@ $$ subject to feasibility, lender participation ($b \leq \beta_c \sum_j g_j(I)\,d_j$), incentive compatibility, and enforcement constraints. -## The First-Order Approach +## The first-order approach -A key contribution of {cite}`Tsyrennikov2013` is **Lemma 1**, which shows that +A key contribution of {cite:t}`Tsyrennikov2013` is **Lemma 1**, which shows that replacing the full IC constraint with the first-order condition $$ -\theta\,u'(c) + \beta\,\lambda'(I)\,\sum_j \Delta g_j\,V(Y_j-d_j) \geq 0 $$ -does **not** alter the solution. +does *not* alter the solution. The key step is showing that at any feasible contract, $\sum_j \Delta g_j\,V(Y_j-d_j) \geq 0$, which ensures the @@ -167,8 +161,7 @@ With the FOA, the optimality condition for investments is $$ \theta\,u'(c) \;=\; \beta\,\lambda'(I)\,\bigl[V(n_2') - V(n_1')\bigr], -\tag{FOA} -$$ +$$ (foa) where $n_j' = Y_j - d_j$ is next period's net worth after state $j$. @@ -176,9 +169,9 @@ A higher spread $V(n_2') - V(n_1')$ — more reward in the high state — supports a higher investment level. -## The Euler Equation and Implied Interest Rate +## The Euler equation and implied interest rate -The Euler equation (Appendix A of {cite}`Tsyrennikov2013`) for the MH model is +The Euler equation (Appendix A of {cite:t}`Tsyrennikov2013`) for the MH model is $$ V'(n) \;=\; V'(n_j')\!\left[1 + \mu\, @@ -214,111 +207,188 @@ increasing $R$. ## Computation We now implement these ideas numerically using the parameterisation from -{cite}`Tsyrennikov2013`. +{cite:t}`Tsyrennikov2013`. + +In addition to what's in Anaconda, this lecture will need the following library: + +```{code-cell} ipython3 +:tags: [hide-output] + +!pip install jax +``` ### Parameters ```{code-cell} ipython3 import numpy as np +from typing import NamedTuple from scipy.interpolate import interp1d -from scipy.optimize import minimize_scalar, minimize, brentq +from jax import config +config.update("jax_enable_x64", True) +import jax +import jax.numpy as jnp import matplotlib.pyplot as plt -plt.rcParams.update({'font.size': 12, 'figure.dpi': 100}) - -# ── Preferences ────────────────────────────────────────────────────────────── -β = 0.980 # borrower discount factor -β_c = 0.990 # lender (world) discount factor -γ = 2.0 # CRRA coefficient -θ = 0.105 # investment resource cost (θ in budget n+b = c+θI) - -# ── Investment technology ───────────────────────────────────────────────────── -# λ(I) = I^ν (probability of high output) -ν = 0.950 - -def lam(I): +# Model parameters +class Model(NamedTuple): + β: float # borrower discount factor + β_c: float # lender (world) discount factor + γ: float # CRRA coefficient + θ: float # investment resource cost (θ in budget n+b = c+θI) + ν: float # λ(I) = I^ν (probability of high output) + δ: float # borrower keeps fraction δ of output on default + M: float # lender endowment + Y1: float # low output state + Y2: float # high output state + + +def create_model(β=0.980, β_c=0.990, γ=2.0, θ=0.105, ν=0.950, + δ=0.795, M=0.465, Y1=np.exp(-0.054), Y2=np.exp(+0.054)): + """Build a model instance, validating the parameters.""" + if not 0 < β < 1: + raise ValueError("β must lie in (0, 1)") + if not 0 < β_c < 1: + raise ValueError("β_c must lie in (0, 1)") + if γ <= 0: + raise ValueError("γ must be positive") + if not 0 < ν < 1: + raise ValueError("ν must lie in (0, 1)") + if not 0 < δ < 1: + raise ValueError("δ must lie in (0, 1)") + if Y1 >= Y2: + raise ValueError("require Y1 < Y2") + return Model(β=β, β_c=β_c, γ=γ, θ=θ, ν=ν, δ=δ, M=M, Y1=Y1, Y2=Y2) + + +model = create_model() +β, β_c, γ, θ, ν, δ, M, Y1, Y2 = (model.β, model.β_c, model.γ, model.θ, + model.ν, model.δ, model.M, model.Y1, model.Y2) +Y = np.array([Y1, Y2]) + + +# Investment technology: λ(I) = I^ν (probability of high output) +def λ(I): return np.minimum(I**ν, 1.0) -def dlam(I): +def λ_jax(I): + return jnp.minimum(I**ν, 1.0) + +def dλ(I): """λ'(I) = ν * I^{ν−1} (for I > 0).""" return ν * I**(ν - 1.0) -# ── Default penalty ─────────────────────────────────────────────────────────── -δ = 0.795 # borrower keeps fraction δ of output on default - -# ── Lender endowment ───────────────────────────────────────────────────────── -M = 0.465 - -# ── Output states: ln(Y_j) = ±0.054, normalised so parameter θ gives E[Y]=1 ─ -Y1 = np.exp(-0.054) # ≈ 0.9474 -Y2 = np.exp(+0.054) # ≈ 1.0555 -Y = np.array([Y1, Y2]) - -# ── Utility ─────────────────────────────────────────────────────────────────── +# Utility def u(c): c = np.maximum(c, 1e-12) return c**(1.0 - γ) / (1.0 - γ) +def u_jax(c): + c = jnp.maximum(c, 1e-12) + return c**(1.0 - γ) / (1.0 - γ) + def u_prime(c): return np.maximum(c, 1e-12)**(-γ) -# ── Net-worth grid ──────────────────────────────────────────────────────────── +def u_prime_jax(c): + return jnp.maximum(c, 1e-12)**(-γ) + +# Net-worth grid N_n = 150 n_lo = 0.08 n_hi = 1.30 n_grid = np.linspace(n_lo, n_hi, N_n) +n_grid_j = jnp.asarray(n_grid) + +# Search grids used by Bellman operators below. +N_I_search = 500 +I_search_grid = np.linspace(0.0, 1.0, N_I_search) +I_search_grid_j = jnp.asarray(I_search_grid) + +N_policy = 90 +n1p_candidates = np.linspace(max(δ * Y1, n_lo), + min(Y1 * 1.1, n_hi - 1e-4), + N_policy) +n2p_candidates = np.linspace(max(δ * Y2, n_lo), + min(Y2 * 1.05, n_hi - 1e-4), + N_policy) +n1p_mesh, n2p_mesh = np.meshgrid(n1p_candidates, n2p_candidates, + indexing='ij') +n1p_flat_j = jnp.asarray(n1p_mesh.ravel()) +n2p_flat_j = jnp.asarray(n2p_mesh.ravel()) print(f"Output states: Y1 = {Y1:.4f}, Y2 = {Y2:.4f}") print(f"β = {β}, β_c = {β_c}, γ = {γ}, θ = {θ}, ν = {ν}") ``` -### Autarky Value Function +### Autarky value function ```{code-cell} ipython3 -def autarky_bellman_at_n(n, Vf): +@jax.jit +def autarky_step_jax(V, β_val): + """One vectorised Bellman step for the autarky problem.""" + EV1 = jnp.interp(Y1, n_grid_j, V) + EV2 = jnp.interp(Y2, n_grid_j, V) + + I = I_search_grid_j[None, :] + c = n_grid_j[:, None] - θ * I + l = λ_jax(I) + obj = u_jax(c) + β_val * ((1.0 - l) * EV1 + l * EV2) + obj = jnp.where(c > 1e-10, obj, -jnp.inf) + + idx = jnp.argmax(obj, axis=1) + return jnp.max(obj, axis=1), I_search_grid_j[idx] + + +def autarky_policy(V_arr, β_val=None): + """Return the autarky value update and investment policy on n_grid.""" + if β_val is None: + β_val = β + V_new, I_pol = autarky_step_jax(jnp.asarray(V_arr), β_val) + return np.asarray(V_new), np.asarray(I_pol) + + +def autarky_bellman_at_n(n, Vf, β_val=None): """ Solve the autarky Bellman at state n given current iterate Vf. Returns (V_new, I_opt). Uses the fact that continuation values only depend on Y1, Y2. """ + if β_val is None: + β_val = β EV1 = float(Vf(Y1)) EV2 = float(Vf(Y2)) - def neg_obj(I): - c = n - θ * I - if c < 1e-10: - return 1e10 - l = lam(I) - return -(u(c) + β * ((1.0 - l) * EV1 + l * EV2)) + I_max = min(max(n / θ - 1e-8, 0.0), 1.0) + I = I_search_grid[I_search_grid <= I_max] + if I.size == 0: + I = np.array([0.0]) - I_max = n / θ - 1e-8 - if I_max <= 0: - return u(n) + β * ((1 - lam(0)) * EV1 + lam(0) * EV2), 0.0 + c = n - θ * I + obj = u(c) + β_val * ((1.0 - λ(I)) * EV1 + λ(I) * EV2) + idx = np.argmax(obj) + return float(obj[idx]), float(I[idx]) - res = minimize_scalar(neg_obj, bounds=(1e-8, I_max), method='bounded', - options={'xatol': 1e-9}) - return -res.fun, res.x +def autarky_vfi(β_val=None, tol=1e-8, max_iter=3000): + if β_val is None: + β_val = β -def autarky_vfi(tol=1e-8, max_iter=3000): - V = np.zeros(N_n) - + V = jnp.zeros(N_n) for it in range(max_iter): - Vf = interp1d(n_grid, V, fill_value='extrapolate', bounds_error=False) - V_new = np.array([autarky_bellman_at_n(n, Vf)[0] for n in n_grid]) - diff = np.max(np.abs(V_new - V)) - V = V_new + V_new, _ = autarky_step_jax(V, β_val) + diff = float(jnp.max(jnp.abs(V_new - V))) + V = V_new if diff < tol: print(f"Autarky VFI converged in {it+1} iterations (diff = {diff:.2e})") break - return V + return np.asarray(V) V_aut = autarky_vfi() ``` -### Moral Hazard Model +### Moral hazard model For each state $n$, we optimise over continuation states $(n_1', n_2')$ where $n_j' = Y_j - d_j$. For every candidate @@ -340,139 +410,94 @@ $\Delta B \equiv \beta_c\bigl[(Y_2-n_2') - (Y_1-n_1')\bigr]$. This reduces the optimisation to two dimensions. ```{code-cell} ipython3 -def solve_I_star(n, n1p, n2p, Vf): - """ - Solve the FOA equation for I*, given (n, n1', n2') and current V. - Returns (I_star, c_star, b_star, lam_star) or None if infeasible. +@jax.jit +def mh_bellman_step_jax(V, V_aut_arr, β_val, β_c_val): """ - dV = float(Vf(n2p)) - float(Vf(n1p)) - if dV <= 1e-10: # no interior solution (V flat or decreasing) - return None - - A = n + β_c * (Y1 - n1p) - ΔB = β_c * ((Y2 - n2p) - (Y1 - n1p)) - - def c_star_of_I(I): - return A + I**ν * ΔB - θ * I + One vectorised Bellman step for the moral-hazard model. - # Upper bound on I: c* > 0 - # Approximate: I_max ≈ A/θ (rough; refine by bisection) - I_hi = min(A / θ * 0.999, 1.0 - 1e-6) - while I_hi > 1e-6 and c_star_of_I(I_hi) < 1e-8: - I_hi *= 0.9 - if I_hi < 1e-6: - return None - - I_lo = 1e-7 + For each candidate pair (n_1', n_2'), the FOA for I is solved by a + compiled bisection. The Bellman maximisation is then a batch grid search + over continuation states. + """ + V1 = jnp.interp(n1p_flat_j, n_grid_j, V) + V2 = jnp.interp(n2p_flat_j, n_grid_j, V) + ΔV = V2 - V1 - def foa(I): - c = c_star_of_I(I) - if c < 1e-10: - return 1e10 - return θ * u_prime(c) - β * dlam(I) * dV + A = n_grid_j[:, None] + β_c_val * (Y1 - n1p_flat_j)[None, :] + ΔB = β_c_val * ((Y2 - n2p_flat_j) - (Y1 - n1p_flat_j)) - # foa(I_lo) < 0 (RHS → +∞), foa(I_hi) should be > 0 eventually - if foa(I_lo) >= 0 or foa(I_hi) <= 0: - return None + def c_of_I(I): + return A + (I**ν) * ΔB[None, :] - θ * I - try: - I_star = brentq(foa, I_lo, I_hi, xtol=1e-10) - except ValueError: - return None + I_hi = jnp.minimum(A / θ * 0.999, 1.0 - 1e-6) + I_hi = jnp.maximum(I_hi, 1e-6) - c = c_star_of_I(I_star) - l = lam(I_star) - b = β_c * ((1-l)*(Y1-n1p) + l*(Y2-n2p)) - return I_star, c, b, l + def shrink_hi(_, I_hi_val): + return jnp.where(c_of_I(I_hi_val) < 1e-8, 0.9 * I_hi_val, I_hi_val) + I_hi = jax.lax.fori_loop(0, 40, shrink_hi, I_hi) + I_lo = jnp.full_like(I_hi, 1e-7) -def mh_bellman_at_n(n, Vf, Vaut_f, thresh1, thresh2): - """ - Solve the MH Bellman equation at state n. - thresh1, thresh2 are enforcement lower bounds on n1', n2'. - Returns (V_new, n1p_opt, n2p_opt, I_opt, b_opt). - """ - def neg_obj(x): - n1p, n2p = x - sol = solve_I_star(n, n1p, n2p, Vf) - if sol is None: - return 1e10 - I_star, c, b, l = sol - if c < 1e-10 or b > M + 1e-6: - return 1e10 - EV = (1-l)*float(Vf(n1p)) + l*float(Vf(n2p)) - return -(u(c) + β * EV) - - # Enforcement lower bounds on continuation states - lo = np.array([max(thresh1, n_lo), max(thresh2, n_lo)]) - hi = np.array([min(Y1 * 1.1, n_hi - 1e-4), - min(Y2 * 1.05, n_hi - 1e-4)]) - - # Starting points - x_inits = [ - lo, - np.array([min(Y1 * 0.95, hi[0]), min(Y2 * 0.95, hi[1])]), - np.array([lo[0], min(Y2 * 0.85, hi[1])]), - ] - - best_val = float(Vaut_f(n)) # fallback: autarky - best_x = np.array([min(Y1, hi[0]), min(Y2, hi[1])]) - - for x0 in x_inits: - x0 = np.clip(x0, lo, hi) - try: - res = minimize(neg_obj, x0, method='Nelder-Mead', - options={'xatol': 1e-7, 'fatol': 1e-7, - 'maxiter': 800, 'adaptive': True}) - val = -res.fun - if val > best_val: - # Verify enforcement constraints hold - n1p_try, n2p_try = np.clip(res.x, lo, hi) - sol = solve_I_star(n, n1p_try, n2p_try, Vf) - if sol is not None and sol[1] > 1e-8: - best_val = val - best_x = np.array([n1p_try, n2p_try]) - except Exception: - pass - - n1p, n2p = best_x - sol = solve_I_star(n, n1p, n2p, Vf) - if sol is None: - return best_val, n1p, n2p, 0.0, 0.0 - I_star, c, b, l = sol - return best_val, n1p, n2p, I_star, b - - -def mh_vfi(V_aut, tol=1e-3, max_iter=60): + def foa(I): + return (θ * u_prime_jax(c_of_I(I)) + - β_val * ν * jnp.maximum(I, 1e-12)**(ν - 1.0) + * ΔV[None, :]) + + foa_lo = foa(I_lo) + foa_hi = foa(I_hi) + valid = ((ΔV[None, :] > 1e-10) & (I_hi > 1e-6) + & (foa_lo < 0.0) & (foa_hi > 0.0)) + + def bisect_body(_, state): + lo, hi = state + mid = 0.5 * (lo + hi) + f_mid = foa(mid) + hi = jnp.where(f_mid > 0.0, mid, hi) + lo = jnp.where(f_mid > 0.0, lo, mid) + return lo, hi + + I_lo, I_hi = jax.lax.fori_loop(0, 45, bisect_body, (I_lo, I_hi)) + I_star = 0.5 * (I_lo + I_hi) + c = c_of_I(I_star) + l = λ_jax(I_star) + b = β_c_val * ((1 - l) * (Y1 - n1p_flat_j)[None, :] + + l * (Y2 - n2p_flat_j)[None, :]) + EV = (1 - l) * V1[None, :] + l * V2[None, :] + obj = u_jax(c) + β_val * EV + + feasible = valid & (c > 1e-10) & (b <= M + 1e-6) + obj = jnp.where(feasible, obj, -jnp.inf) + + idx = jnp.argmax(obj, axis=1) + best_val = jnp.max(obj, axis=1) + has_feasible = jnp.isfinite(best_val) + use_fallback = (~has_feasible) | (best_val <= V_aut_arr) + + pol_n1p = jnp.where(use_fallback, Y1, n1p_flat_j[idx]) + pol_n2p = jnp.where(use_fallback, Y2, n2p_flat_j[idx]) + pol_I = jnp.where(use_fallback, 0.0, + jnp.take_along_axis(I_star, idx[:, None], axis=1)[:, 0]) + pol_b = jnp.where(use_fallback, 0.0, + jnp.take_along_axis(b, idx[:, None], axis=1)[:, 0]) + V_new = jnp.where(use_fallback, V_aut_arr, best_val) + + return V_new, pol_n1p, pol_n2p, pol_I, pol_b + + +def mh_vfi(V_aut, β_val=None, β_c_val=None, tol=1e-3, max_iter=60, + relaxation=0.5): """Value function iteration for the moral hazard model.""" - V = V_aut.copy() - Vf_aut = interp1d(n_grid, V_aut, fill_value='extrapolate', bounds_error=False) - - # Enforcement bounds: V(n_j') >= V_aut(δ*Y_j) - # => n_j' >= V^{-1}(V_aut(δ*Y_j)) - # Since V(n) >= V_aut(n), the bound V_aut(δ*Y_j) is automatically - # satisfied at n_j' >= δ*Y_j; use this conservative bound initially. - thresh1_fixed = δ * Y1 - thresh2_fixed = δ * Y2 - - pol_n1p = np.empty(N_n) - pol_n2p = np.empty(N_n) - pol_I = np.empty(N_n) - pol_b = np.empty(N_n) + if β_val is None: + β_val = β + if β_c_val is None: + β_c_val = β_c + V = V_aut.copy() for it in range(max_iter): - Vf = interp1d(n_grid, V, fill_value='extrapolate', bounds_error=False) - V_new = np.empty(N_n) - - for k, n in enumerate(n_grid): - vn, n1p, n2p, I_star, b = mh_bellman_at_n( - n, Vf, Vf_aut, thresh1_fixed, thresh2_fixed) - V_new[k] = vn - pol_n1p[k] = n1p - pol_n2p[k] = n2p - pol_I[k] = I_star - pol_b[k] = b - + V_raw, pol_n1p, pol_n2p, pol_I, pol_b = mh_bellman_step_jax( + jnp.asarray(V), jnp.asarray(V_aut), β_val, β_c_val) + V_raw = np.asarray(V_raw) + V_new = (1 - relaxation) * V + relaxation * V_raw diff = np.max(np.abs(V_new - V)) V = V_new print(f" iter {it+1:3d}, max|ΔV| = {diff:.5f}") @@ -480,24 +505,33 @@ def mh_vfi(V_aut, tol=1e-3, max_iter=60): print(f"MH VFI converged in {it+1} iterations.") break - return V, pol_n1p, pol_n2p, pol_I, pol_b + _, pol_n1p, pol_n2p, pol_I, pol_b = mh_bellman_step_jax( + jnp.asarray(V), jnp.asarray(V_aut), β_val, β_c_val) + + return (V, np.asarray(pol_n1p), np.asarray(pol_n2p), + np.asarray(pol_I), np.asarray(pol_b)) print("Running moral hazard VFI…") V_mh, pol_n1p, pol_n2p, pol_I, pol_b = mh_vfi(V_aut) ``` -### Value Functions and Insurance +### Value functions and insurance ```{code-cell} ipython3 -fig, axes = plt.subplots(1, 2, figsize=(12, 5)) +--- +mystnb: + figure: + caption: value functions and risk-sharing index + name: fig-tsy-value-rsi +--- +fig, axes = plt.subplots(1, 2) # Left: value functions axes[0].plot(n_grid, V_aut, lw=2, label='Autarky') axes[0].plot(n_grid, V_mh, lw=2, ls='--', label='Moral hazard') -axes[0].set_xlabel('Net worth $n$') -axes[0].set_ylabel('Value') -axes[0].set_title('Value Functions') +axes[0].set_xlabel('net worth $n$') +axes[0].set_ylabel('value') axes[0].legend() # Right: Risk-sharing index RSI = (d_2 - d_1) / (Y_2 - Y_1) @@ -506,58 +540,63 @@ axes[0].legend() d1_mh = Y1 - pol_n1p d2_mh = Y2 - pol_n2p RSI_mh = (d2_mh - d1_mh) / (Y2 - Y1) +active_mh = λ(pol_I) > 0.01 +RSI_mh_plot = np.where(active_mh, RSI_mh, np.nan) -axes[1].plot(n_grid, RSI_mh, lw=2, color='C1') +axes[1].plot(n_grid, RSI_mh_plot, lw=2, color='C1') axes[1].axhline(1.0, ls=':', color='k', lw=1, label='Full insurance (RSI=1)') axes[1].axhline(0.0, ls='--', color='k', lw=1, label='Non-contingent debt (RSI=0)') -axes[1].set_xlabel('Net worth $n$') -axes[1].set_ylabel('Risk-sharing index') -axes[1].set_title('State Contingency of Repayment') +axes[1].set_xlabel('net worth $n$') +axes[1].set_ylabel('risk-sharing index') axes[1].set_ylim(-0.1, 1.2) axes[1].legend() plt.tight_layout() plt.show() -print(f"\nMean RSI (moral hazard): {np.mean(RSI_mh):.4f}") +print(f"\nMean RSI (active-investment states): {np.nanmean(RSI_mh_plot):.4f}") print("→ Repayment is nearly state non-contingent (RSI ≈ 0)") print("→ Moral hazard justifies why simple non-contingent debt is optimal") ``` -A key finding of {cite}`Tsyrennikov2013` emerges immediately: the risk-sharing -index is close to **zero** throughout the state space. +A key finding of {cite:t}`Tsyrennikov2013` emerges immediately: the risk-sharing +index is close to *zero* on the active-investment region. Moral hazard requires spreading continuation values to incentivise investment, but this is achieved by differentiating *net worth* $n_j'$, not repayment $d_j = Y_j - n_j'$. The near-equality $d_1 \approx d_2$ means repayment is essentially -**non-contingent on output** — the model rationalises why emerging market +*non-contingent on output* — the model rationalises why emerging market borrowers use plain debt instruments rather than GDP-linked securities. -### Optimal Investment +### Optimal investment ```{code-cell} ipython3 -fig, axes = plt.subplots(1, 2, figsize=(12, 5)) +--- +mystnb: + figure: + caption: optimal investment and continuation net worth + name: fig-tsy-investment +--- +fig, axes = plt.subplots(1, 2) # Compute autarky optimal investment -I_aut = np.array([autarky_bellman_at_n(n, - interp1d(n_grid, V_aut, fill_value='extrapolate', bounds_error=False))[1] - for n in n_grid]) +_, I_aut = autarky_policy(V_aut) -axes[0].plot(n_grid, lam(I_aut), lw=2, label='Autarky') -axes[0].plot(n_grid, lam(pol_I), lw=2, ls='--', label='Moral hazard') -axes[0].set_xlabel('Net worth $n$') +axes[0].plot(n_grid, λ(I_aut), lw=2, label='Autarky') +axes[0].plot(n_grid, λ(pol_I), lw=2, ls='--', label='Moral hazard') +axes[0].set_xlabel('net worth $n$') axes[0].set_ylabel(r'$\lambda(I) = \Pr(Y_2 \mid I)$') -axes[0].set_title('Investment (probability weight on high output)') axes[0].legend() -axes[1].plot(n_grid, pol_n1p, lw=2, label=r"$n_1' = Y_1 - d_1$ (after low output)") -axes[1].plot(n_grid, pol_n2p, lw=2, ls='--', label=r"$n_2' = Y_2 - d_2$ (after high output)") +axes[1].plot(n_grid, pol_n1p, lw=2, + label=r"$n_1' = Y_1 - d_1$ (after low output)") +axes[1].plot(n_grid, pol_n2p, lw=2, ls='--', + label=r"$n_2' = Y_2 - d_2$ (after high output)") axes[1].plot(n_grid, n_grid, lw=1, ls=':', color='k', label='45° line') -axes[1].set_xlabel('Net worth $n$') -axes[1].set_ylabel("Continuation net worth $n_j'$") -axes[1].set_title('Optimal Continuation Net Worth') +axes[1].set_xlabel('net worth $n$') +axes[1].set_ylabel("continuation net worth $n_j'$") axes[1].legend(fontsize=10) plt.tight_layout() @@ -565,14 +604,22 @@ plt.show() ``` Investment under moral hazard is *lower* than in autarky at high net worth -levels and more sensitive to $n$ at low levels. After a low output +levels and more sensitive to $n$ at low levels. + +After a low output realisation, net worth drops sharply ($n_1' \ll n$), depressing future investment and perpetuating the crisis — the model's **internal propagation mechanism**. -### Implied Interest Rate +### Implied interest rate ```{code-cell} ipython3 +--- +mystnb: + figure: + caption: implied interest rate and spread + name: fig-tsy-interest-rate +--- # Compute implied interest rate R(n) # R(n) = u'(c(n)) / [β * Σ_j g_j(I(n)) * u'(c'(n_j'(n)))] # where c'(n_j') = n_j' + b*(n_j') - θ I*(n_j') (next period's consumption) @@ -593,7 +640,7 @@ for k, n in enumerate(n_grid): b = pol_b[k] I = pol_I[k] c = n + b - θ * I - l = lam(I) + l = λ(I) n1p = pol_n1p[k] n2p = pol_n2p[k] c1p = next_period_c(n1p) @@ -605,38 +652,45 @@ for k, n in enumerate(n_grid): R_world = 1.0 / β_c # gross world rate spread_ann = (R_n**4 - R_world**4) # approximate annualised spread -fig, axes = plt.subplots(1, 2, figsize=(12, 5)) +fig, axes = plt.subplots(1, 2) axes[0].plot(n_grid, R_n, lw=2) -axes[0].axhline(R_world, ls='--', color='k', lw=1, label=f'World rate $1/\\beta_c = {R_world:.3f}$') -axes[0].set_xlabel('Net worth $n$') -axes[0].set_ylabel('Implied gross interest rate $R(n)$') -axes[0].set_title('Interest Rate Schedule') +axes[0].axhline(R_world, ls='--', color='k', lw=1, + label=f'World rate $1/\\beta_c = {R_world:.3f}$') +axes[0].set_xlabel('net worth $n$') +axes[0].set_ylabel('implied gross interest rate $R(n)$') axes[0].legend() axes[1].plot(n_grid, np.clip(spread_ann * 100, -1, 50), lw=2) axes[1].axhline(0, ls='--', color='k', lw=0.8) -axes[1].set_xlabel('Net worth $n$') -axes[1].set_ylabel('Annualised spread over world rate (%)') -axes[1].set_title('Interest Rate Spread') +axes[1].set_xlabel('net worth $n$') +axes[1].set_ylabel('annualised spread over world rate (%)') plt.tight_layout() plt.show() ``` The interest rate spread rises sharply at low net worth levels, consistent with -the Argentine data. The mechanism is the **MH Euler equation**: when $n$ is +the Argentine data. + +The mechanism is the **MH Euler equation**: when $n$ is low, the borrower's continuation value is depressed and the spread in marginal utilities across future states increases $R(n)$. -### Crisis Dynamics +### Crisis dynamics -{cite}`Tsyrennikov2013` shows that a string of low output realisations +{cite:t}`Tsyrennikov2013` shows that a string of low output realisations generates gradual debt accumulation followed by a sudden stop in which capital inflows cease and interest rates spike — a pattern consistent with the Argentina 2001 experience. ```{code-cell} ipython3 +--- +mystnb: + figure: + caption: simulated crisis dynamics + name: fig-tsy-crisis +--- def simulate_crisis(T_crisis=8): """ Simulate crisis path: T_crisis periods of low output (Y_1) starting @@ -644,7 +698,7 @@ def simulate_crisis(T_crisis=8): """ n = Y2 # start with high net worth records = {'n': [n], 'debt_over_Y': [], 'R': [], 'ca_over_Y': [], - 'lam': []} + 'λ': []} for t in range(T_crisis): b = float(pol_b_fn(n)) @@ -653,7 +707,7 @@ def simulate_crisis(T_crisis=8): n2p = float(pol_n2p_fn(n)) c = n + b - θ * I - l = lam(I) + l = λ(I) c1p = next_period_c(n1p) c2p = next_period_c(n2p) denom = β * ((1-l)*u_prime(c1p) + l*u_prime(c2p)) @@ -671,7 +725,7 @@ def simulate_crisis(T_crisis=8): records['debt_over_Y'].append(debt_Y) records['R'].append(R) records['ca_over_Y'].append(ca / Y1) - records['lam'].append(l) + records['λ'].append(l) n = n1p # low output path @@ -682,36 +736,31 @@ def simulate_crisis(T_crisis=8): crisis = simulate_crisis(T_crisis=8) t_ax = np.arange(len(crisis['R'])) -fig, axes = plt.subplots(2, 2, figsize=(12, 8), sharex=True) -fig.suptitle('Crisis Scenario: 8 Consecutive Low-Output Periods', fontsize=13) +fig, axes = plt.subplots(2, 2, sharex=True) axes[0,0].plot(t_ax, crisis['debt_over_Y'], 'o-', lw=2) -axes[0,0].set_ylabel('Debt / output') -axes[0,0].set_title('(A) Debt accumulation') +axes[0,0].set_ylabel('debt / output') axes[0,1].plot(t_ax, np.array(crisis['R'])**4 * 100, 's-', lw=2, color='C1') axes[0,1].axhline((1/β_c)**4 * 100, ls='--', color='k', lw=0.8, label='World rate') -axes[0,1].set_ylabel('Annualised gross rate (%)') -axes[0,1].set_title('(B) Interest rate') +axes[0,1].set_ylabel('annualised gross rate (%)') axes[0,1].legend(fontsize=9) axes[1,0].plot(t_ax, crisis['ca_over_Y'], '^-', lw=2, color='C2') axes[1,0].axhline(0, ls='--', color='k', lw=0.8) -axes[1,0].set_xlabel('Quarter') -axes[1,0].set_ylabel('Current account / output') -axes[1,0].set_title('(C) Current account') +axes[1,0].set_xlabel('quarter') +axes[1,0].set_ylabel('current account / output') -axes[1,1].plot(t_ax, crisis['lam'], 'D-', lw=2, color='C3') -axes[1,1].set_xlabel('Quarter') +axes[1,1].plot(t_ax, crisis['λ'], 'D-', lw=2, color='C3') +axes[1,1].set_xlabel('quarter') axes[1,1].set_ylabel(r'$\lambda(I) = \Pr(Y_2 \mid I)$') -axes[1,1].set_title('(D) Investment (prob. of high output)') plt.tight_layout() plt.show() ``` -The simulation reproduces the stylised crisis pattern of {cite}`Tsyrennikov2013`, +The simulation reproduces the stylised crisis pattern of {cite:t}`Tsyrennikov2013`, Fig. 4: - **Panel A**: Debt steadily accumulates as the borrower is pushed toward the @@ -724,10 +773,10 @@ Fig. 4: - **Panel D**: Investment collapses as net worth falls, further reducing the probability of high future output — the **internal propagation mechanism**. -### MH Versus Limited Enforcement +### MH versus limited enforcement -A crucial result of {cite}`Tsyrennikov2013` is that **limited enforcement adds -little** to the model's performance relative to moral hazard alone. +A crucial result of {cite:t}`Tsyrennikov2013` is that *limited enforcement adds +little* to the model's performance relative to moral hazard alone. Under LE (no moral hazard), optimal repayments are *highly state contingent* (RSI ≈ 0.8), providing near-full insurance. @@ -740,8 +789,14 @@ The following code illustrates the key theoretical distinction by computing the Euler equation implications under each friction separately. ```{code-cell} ipython3 +--- +mystnb: + figure: + caption: expected continuation net worth + name: fig-tsy-mh-le +--- # Illustrate the Euler equation implications theoretically -fig, ax = plt.subplots(figsize=(8, 5)) +fig, ax = plt.subplots() # Under MH (μ > 0, γ_j = 0): # V'(n) = V'(n_j') [1 + μ λ'(I) Δg_j / g_j(I)] + φ @@ -755,9 +810,9 @@ fig, ax = plt.subplots(figsize=(8, 5)) # Stylised illustration using the computed MH policy Vf_mh = interp1d(n_grid, V_mh, fill_value='extrapolate', bounds_error=False) -expected_np_mh = (1 - lam(pol_I)) * pol_n1p + lam(pol_I) * pol_n2p +expected_np_mh = (1 - λ(pol_I)) * pol_n1p + λ(pol_I) * pol_n2p -ax.plot(n_grid, expected_np_mh, lw=2, label='MH: E[n\'] (drifts down)') +ax.plot(n_grid, expected_np_mh, lw=2, label='MH: E[n\']') ax.plot(n_grid, n_grid, lw=1, ls=':', color='k', label='45° line') # Under LE (approximate): net worth is a supermartingale, E[n'] ≥ n * β/β_c @@ -765,15 +820,15 @@ expected_np_le = n_grid * (β / β_c) ax.plot(n_grid, expected_np_le, lw=2, ls='--', color='C2', label=r'LE: E[n\'] $\approx$ $(\beta/\beta_c)\,n$ (drifts up)') -ax.set_xlabel('Current net worth $n$') -ax.set_ylabel("Expected continuation net worth $E[n']$") -ax.set_title('Drift of Net Worth Under MH vs LE') +ax.set_xlabel('current net worth $n$') +ax.set_ylabel("expected continuation net worth $E[n']$") ax.legend(fontsize=10) plt.tight_layout() plt.show() ``` -Under moral hazard, $\mathbb{E}[n'] < n$: net worth drifts down and the +Under moral hazard, $\mathbb{E}[n']$ is pulled below $n$ in the +high-net-worth active contracting region: net worth drifts down and the borrower spends substantial time near the borrowing limit, generating persistent interest rate spreads. @@ -781,9 +836,9 @@ Under limited enforcement, $\mathbb{E}[n'] \geq (\beta/\beta_c)\,n$: net worth drifts toward a stationary level and the borrower eventually escapes financial stress. -## Empirical Test +## Empirical test -{cite}`Tsyrennikov2013` proposes a test to distinguish moral hazard from +{cite:t}`Tsyrennikov2013` proposes a test to distinguish moral hazard from limited enforcement. After a low past output realisation ($y_{t-1} = Y_1$), @@ -802,12 +857,13 @@ low realisations). Using Argentine quarterly data (1993–2005), the observed correlations are 0.98 (after low output) vs. 0.91 (after high output) — -**consistent with moral hazard**. +*consistent with moral hazard*. ## Exercises -````{admonition} Exercise 1 -:class: exercise +```{exercise-start} +:label: tsyrennikov_2013_ex1 +``` **Effect of default penalty.** The parameter $\delta \in (0,1)$ controls the severity of the output loss upon default. @@ -817,16 +873,23 @@ the severity of the output loss upon default. $V_{\text{aut}}(\delta Y_1)$ and $V_{\text{aut}}(\delta Y_2)$. 3. Discuss: how does a harsher default penalty affect the tightness of the enforcement constraint and (via the Euler equation) the interest rate - spread? At $\delta = 1$ the LE constraint becomes $V(n_j') \geq V_{\text{aut}}(Y_j)$; - at $\delta \to 0$ it is vacuous. -```` + spread? At $\delta = 1$ the LE constraint becomes + $V(n_j') \geq V_{\text{aut}}(Y_j)$; at $\delta \to 0$ it is vacuous. +```{exercise-end} +``` ```{solution-start} tsyrennikov_2013_ex1 :class: dropdown ``` ```{code-cell} ipython3 -fig, ax = plt.subplots(figsize=(8, 5)) +--- +mystnb: + figure: + caption: autarky value and enforcement thresholds + name: fig-tsy-default-penalty +--- +fig, ax = plt.subplots() Vaut_f_global = interp1d(n_grid, V_aut, fill_value='extrapolate', bounds_error=False) @@ -843,8 +906,7 @@ for δ_val, label in [(0.50, 'δ=0.50'), (0.795, 'δ=0.795'), (0.95, 'δ=0.95')] t2 = float(Vaut_f_global(δ_val * Y2)) ax.axhline(t1, ls=':', lw=1.5, label=f'{label}: V_aut(δ·Y1)') -ax.set_xlabel('Net worth $n$'); ax.set_ylabel('$V_{\\rm aut}(n)$') -ax.set_title('Enforcement Thresholds for Different Default Penalties δ') +ax.set_xlabel('net worth $n$'); ax.set_ylabel('$V_{\\rm aut}(n)$') ax.legend(fontsize=9) plt.tight_layout() plt.show() @@ -852,16 +914,21 @@ plt.show() A harsher default penalty (larger $\delta$) raises the enforcement thresholds, tightening the participation constraints and reducing the scope for -state-contingent repayment. Paradoxically, this may *reduce* the interest +state-contingent repayment. + +Paradoxically, this may *reduce* the interest rate spread by forcing the lender to offer more consumption insurance to keep -the borrower from defaulting. At $\delta \to 0$ the enforcement constraint is +the borrower from defaulting. + +At $\delta \to 0$ the enforcement constraint is vacuous and the model collapses to pure moral hazard. ```{solution-end} ``` -````{admonition} Exercise 2 -:class: exercise +```{exercise-start} +:label: tsyrennikov_2013_ex2 +``` **Discounting wedge and impatience.** @@ -875,38 +942,47 @@ vacuous and the model collapses to pure moral hazard. *Hint*: When $\beta = \beta_c$ the only force pushing net worth down is moral hazard (immiseration). When $\beta < \beta_c$ there is an additional front-loading incentive that the lender can exploit. -```` +```{exercise-end} +``` ```{solution-start} tsyrennikov_2013_ex2 :class: dropdown ``` ```{code-cell} ipython3 -fig, ax = plt.subplots(figsize=(8, 5)) +--- +mystnb: + figure: + caption: continuation net worth across discount factors + name: fig-tsy-discount-wedge +--- +fig, ax = plt.subplots() for β_val, ls, color in [(0.990, '-', 'C0'), (0.980, '--', 'C1'), (0.950, ':', 'C2')]: - globals()['β'] = β_val - V_a_tmp = autarky_vfi() - V_mh_tmp, pol_n1p_tmp, pol_n2p_tmp, pol_I_tmp, _ = mh_vfi(V_a_tmp) - globals()['β'] = 0.980 # restore + V_a_tmp = autarky_vfi(β_val=β_val) + V_mh_tmp, pol_n1p_tmp, pol_n2p_tmp, pol_I_tmp, _ = mh_vfi( + V_a_tmp, β_val=β_val, max_iter=80) - E_np = ((1 - lam(pol_I_tmp)) * pol_n1p_tmp - + lam(pol_I_tmp) * pol_n2p_tmp) + E_np = ((1 - λ(pol_I_tmp)) * pol_n1p_tmp + + λ(pol_I_tmp) * pol_n2p_tmp) ax.plot(n_grid, E_np, ls=ls, color=color, label=fr'$\beta={β_val}$') ax.plot(n_grid, n_grid, lw=1, ls=':', color='k', label='45° line') -ax.set_xlabel('Net worth $n$') +ax.set_xlabel('net worth $n$') ax.set_ylabel("$E[n']$") -ax.set_title('Expected Continuation Net Worth for Different Discount Factors') ax.legend() plt.tight_layout() plt.show() ``` The larger the discount wedge $\beta_c - \beta$, the faster net worth drifts -toward the borrowing limit. When $\beta = \beta_c$ moral hazard alone drives -immiseration, while impatience accelerates it further. A small wedge +toward the borrowing limit. + +When $\beta = \beta_c$ moral hazard alone drives +immiseration, while impatience accelerates it further. + +A small wedge (as calibrated by Tsyrennikov) is significant: it is *equivalent to increasing the borrower's discount rate by 2% per annum* (even though the assumed difference in quarterly rates is only 0.010). @@ -914,24 +990,28 @@ the assumed difference in quarterly rates is only 0.010). ```{solution-end} ``` -````{admonition} Exercise 3 -:class: exercise +```{exercise-start} +:label: tsyrennikov_2013_ex3 +``` **Non-contingency of optimal debt.** The *risk-sharing index* $\text{RSI}(n) = (d_2(n) - d_1(n)) / (Y_2 - Y_1)$ -measures how state-contingent the repayment schedule is. RSI = 1 is full +measures how state-contingent the repayment schedule is. + +RSI = 1 is full insurance; RSI = 0 is non-contingent debt. 1. Compute RSI for the MH model you have already solved. -2. Now set $\beta = \beta_c$ (equal discounting) and recompute. Does +2. Now set $\beta = \beta_c$ (equal discounting) and recompute. Does removing the impatience wedge change the near-zero RSI result? 3. Explain *theoretically* why moral hazard drives RSI toward zero. *Hint*: From the Euler equation, the spread in marginal utilities $u'(c_1') / u'(c_2')$ depends on the IC multiplier $\mu$. A larger $\mu$ spreads continuation values but *not necessarily* repayments. -```` +```{exercise-end} +``` ```{solution-start} tsyrennikov_2013_ex3 :class: dropdown @@ -942,8 +1022,10 @@ spreads continuation values but *not necessarily* repayments. d1_mh = Y1 - pol_n1p d2_mh = Y2 - pol_n2p RSI = (d2_mh - d1_mh) / (Y2 - Y1) +active_RSI = RSI[λ(pol_I) > 0.01] -print(f"Baseline MH: mean RSI = {np.mean(RSI):.4f}, max RSI = {np.max(RSI):.4f}") +print(f"Baseline MH: mean RSI = {np.mean(active_RSI):.4f}, " + f"max RSI = {np.max(active_RSI):.4f}") print() print("Theoretical explanation:") print(" Under moral hazard the planner must spread V(n2') - V(n1') to") From 8c0c9ce5bf0b3df947d2b31be2850359facaa7df Mon Sep 17 00:00:00 2001 From: HumphreyYang Date: Tue, 9 Jun 2026 23:55:33 +1000 Subject: [PATCH 12/25] updates --- lectures/repeat_mh.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lectures/repeat_mh.md b/lectures/repeat_mh.md index c7ebaa58..8e327bdd 100644 --- a/lectures/repeat_mh.md +++ b/lectures/repeat_mh.md @@ -51,7 +51,7 @@ implementation: discounted principal-agent problem recursively. * A **principal** owns a technology that produces output $q_t$ at - time $t$ according to a conditional distribution $F(q_t | a_t)$ + time $t$ according to a conditional distribution $F(q_t \mid a_t)$ that depends on the **effort** $a_t$ chosen by an **agent**. * The principal does *not* observe $a_t$. * The principal *does* observe $q_t$ at the end of period $t$ and @@ -193,7 +193,7 @@ Each stage of the computation therefore amounts to solving a finite We begin with the finite objects in the planning problem. -Let $P(q | a)$ be a family of discrete conditional probability +Let $P(q \mid a)$ be a family of discrete conditional probability distributions over finite sets $Q$ (outputs) and $A$ (actions). Let $C$ and $W'$ be finite grids for current consumption and next-period From 9573461f8be20fe4f1ddcc2855ba5350eb71de4a Mon Sep 17 00:00:00 2001 From: HumphreyYang Date: Wed, 10 Jun 2026 15:57:12 +1000 Subject: [PATCH 13/25] updates --- lectures/atkeson_1991.md | 1557 +++++++++++++++++++++++++++----------- 1 file changed, 1125 insertions(+), 432 deletions(-) diff --git a/lectures/atkeson_1991.md b/lectures/atkeson_1991.md index bf9fbc27..b45ca394 100644 --- a/lectures/atkeson_1991.md +++ b/lectures/atkeson_1991.md @@ -18,29 +18,22 @@ This lecture studies {cite:t}`Atkeson1991`, which examines the *constrained optimal pattern of capital flows* between an international lender and a sovereign borrower subject to two frictions: -1. **Moral hazard** — lenders cannot observe whether the borrower invests or +1. **Moral hazard**: lenders cannot observe whether the borrower invests or simply consumes loan proceeds. -2. **Risk of repudiation** — as a sovereign, the borrower can unilaterally +2. **Risk of repudiation**: as a sovereign, the borrower can unilaterally renounce its debt at any time. -A central result is that, under an optimal contract, a "sudden stop" or -"debt crisis" emerges in which a borrowing country must *export capital* -after suffering an adverse output shock. +A central result is that, under the informativeness and interiority +conditions stated below, an optimal contract can require the borrowing +country to *export capital* after the lowest output realizations. -Outflows of capital after bad output realizations are the -mechanism that incentivizes investment. +These low-output outflows are part of the mechanism that incentivizes +investment. -The model extends recursive techniques from {cite}`APS1986`, {cite}`APS1990`, -and {cite}`Spear_Srivastava_87` to an environment with a *physical state +The model extends recursive techniques from {cite:t}`APS1986`, {cite:t}`APS1990`, +and {cite:t}`Spear_Srivastava_87` to an environment with a *physical state variable* that changes across periods. -```{note} -Atkeson (1991) uses $\delta$ for the discount factor. - -We follow the -QuantEcon convention and write $\beta$ throughout. -``` - ## The environment ### Technology @@ -54,20 +47,39 @@ Given investment $I_t$, next period's output $Y_{t+1}$ is drawn from the conditional distribution $$ -g(Y';\,I) \;=\; \lambda(I)\,g_0(Y') + \bigl[1 - \lambda(I)\bigr]\,g_1(Y'), +g(Y';\,I) = \lambda(I)\,g_0(Y') + \bigl[1 - \lambda(I)\bigr]\,g_1(Y'), $$ -where $Y' \in \mathcal{Y} = \{Y_1, \ldots, Y_N\}$ with $Y_N \geq \cdots \geq -Y_1 > 0$. +```{prf:assumption} Technology and Output Signals +:label: atkeson_assumption_technology + +The output support is finite and ordered, +$\mathcal{Y} = \{Y_1,\ldots,Y_N\}$, with +$0 < Y_1 < \cdots < Y_N$. + +For every investment level $I$, output has strictly positive probabilities: +$g(Y_i;I)>0$ for all $i$. -The weight $\lambda : [0,I_{\max}] \to [0,1]$ is strictly -increasing, so higher investment shifts the distribution toward higher outputs. +Investment affects output only through the probability vector +$g(\cdot;I)$. -The ratio $g_0(Y_i')/g_1(Y_i')$ is assumed to be **monotone increasing in -$i$** (monotone likelihood ratio property). +The distribution has the mixture form +$g(Y_i;I)=\lambda(I)g_0(Y_i)+[1-\lambda(I)]g_1(Y_i)$, where $g_0$ and $g_1$ +are probability distributions on $\mathcal{Y}$ and +$\lambda : [0,I_{\max}] \to [0,1]$ is strictly increasing and concave, + +$$ +\lambda'(I)>0, \qquad \lambda''(I)\le 0. +$$ + +The ratio $g_0(Y_i)/g_1(Y_i)$ is increasing in $i$. +``` -This means low output is a -relatively strong signal that the borrower invested little. +The last condition is a monotone likelihood-ratio condition. + +Since higher investment raises $\lambda(I)$, high output is relatively good +news about investment, while low output is relatively strong evidence that +the borrower invested little. ### Agents and preferences @@ -75,11 +87,44 @@ relatively strong signal that the borrower invested little. discounted utility $$ -U^B(\sigma) \;=\; (1 - \beta)\,\mathbb{E}_0^{\sigma} +v^B(\sigma) = (1 - \beta)\,\mathbb{E}_0^{\sigma} \sum_{t=0}^{\infty} \beta^t \, u(c_t), $$ -where $u$ is strictly concave with $u'(0) = +\infty$. +```{prf:assumption} Preferences and Autarky +:label: atkeson_assumption_preferences + +The borrower has discount factor $\beta \in (0,1)$ and period utility +$u(c)$ that is increasing, strictly concave, bounded above, and satisfies +$u'(0)=+\infty$. + +Lenders are short-lived and risk neutral. + +The borrower's autarky value is high enough to rule out equilibrium states +with arbitrarily low current consumption: + +$$ +(1-\beta)u(0) + \beta \bar u < v^B_{\text{aut}}(Y_1), +$$ + +where $\bar u$ is an upper bound for period utility. +``` + +The last inequality is Atkeson's lower-bound condition. + +It ensures that very low current consumption cannot be compensated by even +the best possible future utility, so the relevant state space can be bounded +away from such outcomes. + +Here $\sigma$ denotes an **allocation**, a complete state-contingent plan for +consumption, investment, and the associated loans and repayments, written out +in full in feasibility condition {eq}`eq:atkeson_feasibility`. + +$\mathbb{E}_0^{\sigma}$ is the expectation over output histories that this plan +induces, evaluated at date $0$. + +The factor $(1 - \beta)$ normalises lifetime utility to per-period units, so +$v^B$ is comparable to a one-period payoff. **Lenders** are a sequence of short-lived, risk-neutral agents, one born each period. @@ -88,21 +133,41 @@ The lender born at $t$ extends loan $b_t$ when young and collects state-contingent repayment $d_{t+1}(Y_{t+1})$ when old. A lender's -participation (zero-profit) constraint is +participation constraint is $$ --b_t + \beta \sum_{Y'} d_{t+1}(Y')\,g(Y';\,I_t) \;\geq\; 0. -$$ +-b_t + \beta \sum_{Y'} d_{t+1}(Y')\,g(Y';\,I_t) \geq 0. +$$ (eq:atkeson_lender_ir) + +```{prf:assumption} Lender Commitment and Deposit Seizure +:label: atkeson_assumption_lenders + +Young lenders can commit to honor the contract when they are old, including +states in which the repayment $d_{t+1}(Y_{t+1})$ is negative and the borrower +is withdrawing deposits. + +If a borrower repudiates a loan, the old lender whose loan was repudiated +can seize the borrower's later deposits with future lenders until the loss is +compensated. +``` + +These assumptions prevent the borrower from defaulting on one lender and +then using deposits with future lenders to smooth consumption. + +They are what make exclusion into autarky a credible punishment after +repudiation in this environment. ### State variable and feasibility Define $$ -Q_t \;\equiv\; Y_t - d_t(Y_t) +Q_t := Y_t - d_t(Y_t) $$ -as **output net of repayment** — the resources available to the borrower +as **output net of repayment**. + +It is the resources available to the borrower after settling the old lender's claim. An allocation @@ -110,9 +175,9 @@ $\sigma = \{c_t(Q^t),\,I_t(Q^t),\,b_t(Q^t),\,d_{t+1}(Y_{t+1};Q^t)\}$ is **feasible** if for all $t$ and histories: $$ -c_t + I_t - b_t \;\leq\; Q_t, \quad c_t,\, I_t \geq 0, - \quad b_t,\; -d_{t+1}(Y') \leq M, -$$ +c_t + I_t - b_t \leq Q_t, \quad c_t,\, I_t \geq 0, + \quad b_t, -d_{t+1}(Y') \leq M, +$$ (eq:atkeson_feasibility) where $M$ is the lender's endowment per period. @@ -121,125 +186,371 @@ where $M$ is the lender's endowment per period. The value the borrower can attain without credit access satisfies $$ -U^B_{\text{aut}}(Z) \;=\; \max_{I \in [0, Z]} - \Bigl[(1-\beta)\,u(Z - I) + \beta \sum_{Y'} U^B_{\text{aut}}(Y')\,g(Y';\,I)\Bigr]. +v^B_{\text{aut}}(Z) = \max_{0 \le I \le \min\{Z,\, I_{\max}\}} + \Bigl[ + (1-\beta)\,u(Z - I) + + \beta \sum_{Y'} v^B_{\text{aut}}(Y')\,g(Y';\,I) + \Bigr]. $$ ## Two impediments to contracting ### Moral hazard -Because lenders can observe only $Y_t$, not the borrower's investment $I_t$, -any feasible allocation must be **incentive compatible**: the borrower prefers -the prescribed $(c_t, I_t)$ to any alternative consumption-investment plan -given the loan and repayment schedule. +The contract can condition on observable histories, but not directly on +the borrower's investment. + +An allocation is a full history-contingent plan + +$$ +\sigma = \{c_t(Q^t),\, I_t(Q^t),\, b_t(Q^t),\, + d_{t+1}(Y_{t+1}; Q^t)\}_{t \geq 0}. +$$ + +The loan and repayment schedules, $b$ and $d$, are contract terms. + +The borrower privately chooses consumption and investment. + +Hence incentive compatibility must rule out deviations in the whole +future consumption-investment plan, not just in current investment. + +The allocation $\sigma$ is **incentive compatible** if, after every +history $Q^t$, the borrower cannot improve by choosing another feasible +consumption-investment plan while keeping the same $b$ and $d$: + +$$ +\begin{aligned} +v^B(\sigma \mid Q^t) +&\geq +v^B(\tilde \sigma \mid Q^t), +\\ +&\text{for all feasible } +\tilde \sigma = \{\tilde c,\, \tilde I,\, b,\, d\}. +\end{aligned} +$$ (eq:atkeson_ic) + +The lender observes output histories and can make future loans and +repayments depend on those histories. + +The lender cannot make them depend directly on hidden investment. + +This mirrors the terminology in {doc}`Repeated Moral Hazard `, +where incentive compatibility means that the agent prefers the recommended +hidden action to a deviation. + ### Risk of repudiation -If the borrower repudiates its debt after $Y_{t+1}$ is realized, future -lenders refuse credit and the borrower is confined to autarky. +The borrower is sovereign and can refuse to repay. -An allocation is **immune from repudiation** if, for every output realization $Y_{t+1}$, +Suppose that after history $Q^t$, output $Y_{t+1}$ is realized and the +contract calls for repayment $d_{t+1}(Y_{t+1}; Q^t)$. + +If the borrower repays, next period's state is $$ -U^B\bigl(\sigma\,\big|\,_{Q^t;\,Y_{t+1}}\bigr) - \;\geq\; U^B_{\text{aut}}(Y_{t+1}). +Q_{t+1} += Y_{t+1} - d_{t+1}(Y_{t+1}; Q^t). $$ -The continuation value of the contract — evaluated at the post-repayment state -$Q_{t+1} = Y_{t+1} - d_{t+1}(Y_{t+1})$ — must weakly exceed what the -borrower would obtain by repudiating and retaining all of $Y_{t+1}$. +If the borrower repudiates instead, it keeps the whole output +$Y_{t+1}$ but is excluded from future borrowing. + +The relevant outside option is therefore autarky starting with +$Y_{t+1}$. + +This distinction matters because $Q_{t+1}$ is the state *after* +repayment, while default lets the borrower keep the resources that would +have been repaid. + +An allocation is **immune from repudiation** if + +$$ +v^B\bigl(\sigma \mid Q^t, Y_{t+1}\bigr) +\geq +v^B_{\text{aut}}(Y_{t+1}) +$$ (eq:atkeson_no_repudiation) + +for all $t$, histories $Q^t$, and output realizations $Y_{t+1}$. + +The left side is the value of continuing with the contract after repayment. + +The right side is the punishment value after default. ## The constrained Pareto problem -An allocation is **constrained Pareto optimal** if it maximises the borrower's -payoff $U^B(\sigma)$ subject to: +The planner chooses among allocations that satisfy five restrictions: + +1. Feasibility {eq}`eq:atkeson_feasibility` +2. Borrower individual rationality: + $v^B(\sigma \mid Q^t) \geq v^B_{\text{aut}}(Q_t)$ +3. Lender participation {eq}`eq:atkeson_lender_ir` +4. Immunity from repudiation {eq}`eq:atkeson_no_repudiation` +5. Incentive compatibility {eq}`eq:atkeson_ic` + +An allocation is **constrained Pareto optimal** if it maximizes the +borrower's initial payoff $v^B(\sigma)$ over this constrained set. + +Borrower individual rationality is part of the general feasible set. + +When the recursive problem maximizes the borrower's payoff on the Pareto +frontier, however, this constraint is nonbinding and can be dropped from +that maximization. + +The lender's expected payoff from its loan contract must be nonnegative. + +These restrictions are continuation restrictions. -1. Feasibility -2. Individual rationality (lenders earn at least zero) -3. Immunity from repudiation -4. Incentive compatibility +After every possible history, the continuation allocation must again be +feasible, satisfy individual rationality and lender participation, be immune +from repudiation, and be incentive compatible. ## Recursive formulation ### Self-generation and factorization -Let $V(Q)$ be the set of payoffs the borrower can achieve from allocations -satisfying constraints (1)–(4) when the state is $Q$. +Let $\mathcal V(Q)$ be the set of payoffs the borrower can achieve from +allocations satisfying feasibility {eq}`eq:atkeson_feasibility`, +borrower individual rationality, lender participation +{eq}`eq:atkeson_lender_ir`, no-repudiation +{eq}`eq:atkeson_no_repudiation`, and incentive compatibility +{eq}`eq:atkeson_ic` when the state is $Q$. -Atkeson adapts the -**self-generation** and **factorization** results of {cite:t}`APS1990` to this -setting with a physical state variable. +The recursive formulation extends the **self-generation** and +**factorization** arguments of {cite:t}`APS1990` to a setting with a +physical state variable. -Define a pair $(A, U)$ of current -controls and a continuation value function to be *admissible with respect to* -$W$ at $Q$ if it satisfies one-period versions of constraints (1)–(4) and -$U(Q') \in W(Q')$ for all $Q'$. +Let $A := (c, I, b, d')$ collect the current controls: consumption, +investment, the current loan, and the next-period repayment schedule. -Let $B(W)(Q)$ be the set of payoffs +A pair $(A, v)$ of current controls and a continuation value function is +*admissible with respect to* $W$ at $Q$ if it satisfies one-period +versions of these same restrictions and $v(Q') \in W(Q')$ for all $Q'$. + +Let $\mathcal B(W)(Q)$ be the set of payoffs generated by admissible pairs. -Then: +The operator $\mathcal B$ asks a simple question. + +Suppose future continuation payoffs must lie in the candidate set $W$. + +Which current payoffs can we generate today while respecting feasibility, +individual rationality, lender participation, no-repudiation, and incentive +compatibility? + +Those payoffs form $\mathcal B(W)(Q)$. + +A set $W$ is **self-generating** if every payoff in $W$ can be generated +again by using current controls and continuation payoffs that remain in +$W$. + +Thus a self-generating set can reproduce itself recursively. -- **Proposition 1 (Self-generation):** If $W$ is self-generating - ($W(Q) \subseteq B(W)(Q)$ for all $Q$), then $B(W)(Q) \subseteq V(Q)$. -- **Proposition 2 (Factorization):** $V(Q) \subseteq B(V)(Q)$ for all $Q$. +**Factorization** goes in the other direction. -These propositions imply $V = B(V)$, characterising the utility possibility -correspondence as the fixed point of $B$. +It says that any payoff from a valid full contract can be split into two +parts: current controls today and a continuation payoff after each +possible next state. + +Because the continuation contract must also be valid, those continuation +payoffs lie in $\mathcal V$. + +Two propositions characterize the utility possibility correspondence. + +```{prf:proposition} Self-generation +:label: atkeson_self_generation + +If $W$ is self-generating, with +$W(Q) \subseteq \mathcal B(W)(Q)$ for all $Q$, then +$\mathcal B(W)(Q) \subseteq \mathcal V(Q)$ for all $Q$. +``` + +```{prf:proposition} Factorization +:label: atkeson_factorization + +$\mathcal V(Q) \subseteq \mathcal B(\mathcal V)(Q)$ for all $Q$. +``` + +Together, {prf:ref}`atkeson_self_generation` and +{prf:ref}`atkeson_factorization` imply +$\mathcal V = \mathcal B(\mathcal V)$, characterising the utility +possibility correspondence as the fixed point of $\mathcal B$. ### Program P* -**Proposition 5** ({cite}`Atkeson1991`): The value function $\bar V(Q)$ of the -constrained Pareto optimal contract satisfies the functional equation +The correspondence $\mathcal V$ describes all feasible continuation +payoffs. + +The constrained Pareto problem selects the upper envelope of that +correspondence: for each state $Q$, it asks for the highest borrower +payoff that can be delivered while respecting the contracting +restrictions. + +Call this frontier $\bar v(Q)$. + +```{prf:assumption} Continuity of the Frontier +:label: atkeson_assumption_continuity + +The constrained-optimal value function $\bar v(Q)$ is continuous in the +state variable $Q$ on the relevant bounded state space. +``` + +Under {prf:ref}`atkeson_assumption_continuity`, Program P* is the Bellman +equation for the frontier. + +The continuity condition is a substantive qualification: the functional +equation below is the recursive characterization once this regularity is in +place. + +```{prf:proposition} Program P* +:label: atkeson_program_p_star + +Under {prf:ref}`atkeson_assumption_continuity`, $\bar v(Q)$ satisfies the +functional equation $$ -\bar{V}(Q) \;=\; \max_{c,\,I,\,b,\,d'(\cdot)} - \;(1-\beta)\,u(c) + \beta \sum_{Y'} \bar{V}\!\bigl(Y' - d'(Y')\bigr)\,g(Y';\,I) -$$ +\bar v(Q) = \max_{c,\,I,\,b,\,d'(\cdot)} + (1-\beta)\,u(c) + \beta \sum_{Y'} \bar v \bigl(Y' - d'(Y')\bigr)\,g(Y';\,I) +$$ (eq:atkeson_program_p_star) + +subject to feasibility {eq}`eq:atkeson_feasibility`, lender +participation {eq}`eq:atkeson_lender_ir`, no-repudiation +{eq}`eq:atkeson_no_repudiation`, and incentive compatibility +{eq}`eq:atkeson_ic`. -subject to feasibility, lender participation, no-repudiation, and incentive -compatibility. +Borrower individual rationality is omitted here because it is nonbinding +when the frontier maximizes the borrower's payoff. Moreover, the optimal *continuation* value function equals -$\bar{V}$ itself. +$\bar v$ itself. +``` This mirrors Bellman's principle: the *continuation of the optimal contract is itself optimal* at the updated state. -### Capital outflows after low output +Bellman's principle of optimality says that an optimal plan remains +optimal from any future state it reaches. + +Here that means the contract chosen today does not need a separate +continuation rule after tomorrow's output is realized. + +Once the new state $Q' = Y' - d'(Y')$ is reached, the continuation +contract is again described by the same value function $\bar v(Q')$. + +### Capital outflows after the lowest outputs + +To see where the repayment result comes from, first write the one-period +problem with continuation values as choice variables. + +This first-order argument is not unconditional. + +```{prf:assumption} First-Order Approach +:label: atkeson_assumption_first_order + +At the constrained optimum, the expected value of repayments to lenders is +nondecreasing in investment: + +$$ +\sum_{Y'} d'(Y')\,[g_0(Y')-g_1(Y')] \ge 0. +$$ + +This is the weak form of Atkeson's repayment-monotonicity condition; a strict +inequality is the stronger version that rules out degenerate cases. + +The constrained-optimal investment choice is interior: +$I^* \in (0,I_{\max})$. +``` + +Together with {prf:ref}`atkeson_assumption_technology`, +{prf:ref}`atkeson_assumption_first_order` justifies replacing the +incentive-compatibility condition by the relaxed first-order inequality used +in the Lagrangian. + +Let $v_j$ be the continuation value promised after output $Y_j'$. -The first-order condition of the Lagrangian for Program P* with respect to the -continuation value $U_d(Y_i')$ reveals that the no-repudiation Lagrange -multiplier $\mu_3(Y_i') > 0$ whenever +$$ +Q_j' = Y_j' - d_j, +$$ + +where $d_j$ is the repayment due after output $Y_j'$. + +Let $g_j(I) = g(Y_j'; I)$ and +$g_{I,j} = \partial g(Y_j'; I) / \partial I$. + +A Lagrangian for the relaxed one-period problem has the form $$ -1 + \mu_4 \,\frac{g_1(Y_i';\,I)}{g(Y_i';\,I)} < 0. +\begin{aligned} +\mathcal L +=& (1-\beta)u(c) + \beta\sum_j v_j g_j(I) \\ +&+ \lambda_f (Q + b - c - I) \\ +&+ \lambda_\ell \left[\beta\sum_j d_j g_j(I) - b\right] \\ +&+ \beta\sum_j \mu_j g_j(I) + \left[v_j - v^B_{\text{aut}}(Y_j')\right] \\ +&+ \eta + \left[-(1-\beta)u'(Q+b-I) + + \beta\sum_j v_j g_{I,j}\right] \\ +&+ \beta\sum_j \xi_j g_j(I) + \left[\bar v(Y_j' - d_j) - v_j\right]. +\end{aligned} +$$ (eq:atkeson_relaxed_lagrangian) + +Here $\lambda_f$ is the feasibility multiplier, $\lambda_\ell$ is the +lender-participation multiplier, $\mu_j$ is the no-repudiation multiplier +after output $Y_j'$, $\eta$ is the multiplier on the relaxed +investment-incentive condition, and $\xi_j$ enforces consistency between +$v_j$ and the frontier value $\bar v(Q_j')$. + +In the paper's numbered notation, $\mu_3(Y_j')$ corresponds to $\mu_j$, +and $\mu_4$ corresponds to $\eta$. + +The numbers are just labels for constraints in the Lagrangian +{eq}`eq:atkeson_relaxed_lagrangian`. + +The first-order condition with respect to $v_j$ is, up to the common +positive scale factor $\beta g_j(I)$, + $$ +1 + \mu_j - \xi_j + + \eta\frac{g_{I,j}}{g_j(I)} = 0. +$$ (eq:atkeson_vj_foc) -The ratio $g_1(Y_i';I)/g(Y_i';I)$ measures the likelihood that output $Y_i'$ -signals *low* investment. +Atkeson's argument applies when the relaxed investment-incentive constraint is +active, so $\eta > 0$. -By the monotone likelihood ratio property, this -ratio is largest for the *lowest output states*. +Hence a sufficiently negative value of $g_{I,j}/g_j(I)$ forces $\mu_j > 0$. -When $\mu_3(Y_i') > 0$, -the no-repudiation constraint binds: $\bar{V}(Y_i' - d'(Y_i')) = -U^B_{\text{aut}}(Y_i')$. +Thus the likelihood term $g_{I,j}/g_j(I)$ determines which output states +put the most pressure on the no-repudiation constraint. -Repayment $d'(Y_i')$ is then at its maximum and the -new loan available at the continuation state is limited, producing a net -**capital outflow**: +For low outputs, higher investment makes the realization less likely, so +$g_{I,j}/g_j(I)$ is negative. + +By the monotone likelihood ratio property, this log-likelihood derivative +is most negative for the lowest output states. + +When {eq}`eq:atkeson_vj_foc` requires $\mu_j > 0$, complementary slackness +implies that the no-repudiation constraint binds: $$ -\underbrace{d'(Y_i')}_{\text{repayment to old lender}} - \;>\; \underbrace{b'\!\bigl(Q'\bigr)}_{\text{new loan from young lender}}. +\bar v(Y_j' - d_j) = v^B_{\text{aut}}(Y_j'). $$ +Repayment $d_j$ is then at its maximum and the new loan available at the +continuation state is limited. + +Thus the borrower has a **capital outflow**: + +$$ +\underbrace{d_j}_{\text{repayment to old lender}} + \geq \underbrace{b' \bigl(Q_j'\bigr)}_{\text{new loan from young lender}}. +$$ + +Strict capital outflow requires the inequality to be strict. + ## Computation -We illustrate these results with a binary-investment, two-output version of -the model. +We now compute a grid approximation to Program P*. In addition to what's in Anaconda, this lecture will need the following library: @@ -249,7 +560,7 @@ In addition to what's in Anaconda, this lecture will need the following library: !pip install jax ``` -### Setup +We will use the following imports: ```{code-cell} ipython3 import numpy as np @@ -260,60 +571,123 @@ config.update("jax_enable_x64", True) import jax import jax.numpy as jnp import matplotlib.pyplot as plt +``` -# Model parameters -class Model(NamedTuple): - β: float # discount factor - I_h: float # resource cost of high investment - Y_L: float # low output state - Y_H: float # high output state - M: float # lender endowment (b, −d ≤ M) - g_h: np.ndarray # g_h[j] = Pr(Y[j] | invest I_h) - g_l: np.ndarray # g_l[j] = Pr(Y[j] | invest 0) +### Setup + +Let's start by defining the model primitives and the state grid. + +The probabilities satisfy the monotone likelihood ratio property: +$g_{\ell}(Y_L)/g_h(Y_L) > 1 > g_{\ell}(Y_H)/g_h(Y_H)$. +Thus $Y_L$ is evidence of low investment, while $Y_H$ is evidence of high +investment. -def create_model(β=0.9, I_h=0.2, Y_L=0.5, Y_H=1.0, M=10.0, - g_h=(0.25, 0.75), g_l=(0.75, 0.25)): +```{code-cell} ipython3 +# Model parameters +class Model(NamedTuple): + β: float # discount factor + I_max: float # upper bound on investment + Y: np.ndarray # output states + M: float # lender endowment (b, -d <= M) + g_high: np.ndarray # distribution at I_max + g_low: np.ndarray # distribution at I = 0 + κ: float # curvature in the investment-probability map + + +def create_model(β=0.92, + I_max=0.6, + Y=(0.8, 1.2), + M=0.3, + g_high=(0.05, 0.95), + g_low=(0.95, 0.05), + κ=3.0): """Build a model instance, validating the parameters.""" + if not 0 < β < 1: raise ValueError("β must lie in (0, 1)") - if Y_L >= Y_H: - raise ValueError("require Y_L < Y_H") - g_h, g_l = np.asarray(g_h), np.asarray(g_l) - if not (np.isclose(g_h.sum(), 1.0) and np.isclose(g_l.sum(), 1.0)): - raise ValueError("g_h and g_l must each sum to 1") - return Model(β=β, I_h=I_h, Y_L=Y_L, Y_H=Y_H, M=M, g_h=g_h, g_l=g_l) + Y = np.asarray(Y, dtype=float) + if np.any(np.diff(Y) <= 0): + raise ValueError("output states must be strictly increasing") -model = create_model() -β, I_h, Y_L, Y_H, M = model.β, model.I_h, model.Y_L, model.Y_H, model.M -g_h, g_l = model.g_h, model.g_l -Y = np.array([Y_L, Y_H]) + g_high, g_low = np.asarray(g_high), np.asarray(g_low) + + if not (np.isclose(g_high.sum(), 1.0) + and np.isclose(g_low.sum(), 1.0)): + raise ValueError("probability vectors must sum to 1") -# Monotone likelihood ratio: g_l[0]/g_h[0] = 3 > 1 > g_l[1]/g_h[1] = 1/3 -# → Y_L strongly signals low investment; Y_H signals high investment -Δg = g_h - g_l # [−0.5, 0.5] + return Model(β=β, I_max=I_max, Y=Y, M=M, + g_high=g_high, g_low=g_low, κ=κ) -# State grid: Q = Y − d (resources after repaying old debt) -N_Q = 200 -Q_MIN = 0.02 + +model = create_model() +β, I_max, Y, M, κ = model.β, model.I_max, model.Y, model.M, model.κ +g_high, g_low = model.g_high, model.g_low +Y_L, Y_H = Y + +# State grid: Q = Y - d (resources after repaying old debt) +N_Q = 70 +N_I = 19 +Q_MIN = 0.002 Q_MAX = 1.8 Q_grid = np.linspace(Q_MIN, Q_MAX, N_Q) Q_grid_j = jnp.asarray(Q_grid) +I_grid = np.linspace(0.0, I_max, N_I) +I_grid_j = jnp.asarray(I_grid) +Y_j = jnp.asarray(Y) Qp_L_mesh, Qp_H_mesh = np.meshgrid(Q_grid, Q_grid, indexing='ij') -Qp_L_flat = jnp.asarray(Qp_L_mesh.ravel()) -Qp_H_flat = jnp.asarray(Qp_H_mesh.ravel()) +Qp_flat = jnp.asarray(np.column_stack((Qp_L_mesh.ravel(), + Qp_H_mesh.ravel()))) +n_pair = Qp_flat.shape[0] # Utility def u(c): return np.log(np.maximum(c, 1e-12)) + def u_jax(c): return jnp.log(jnp.maximum(c, 1e-12)) -print(f"Likelihood ratios g_l / g_h : {g_l / g_h}") -print(f"Y_L signals low investment with ratio {g_l[0]/g_h[0]:.1f}x") + +def lambda_weight(I): + x = np.clip(I / I_max, 0.0, 1.0) + return (1 - np.exp(-κ * x)) / (1 - np.exp(-κ)) + + +def lambda_weight_jax(I): + x = jnp.clip(I / I_max, 0.0, 1.0) + return (1 - jnp.exp(-κ * x)) / (1 - jnp.exp(-κ)) + + +def g_of_I(I, g_high_val=None, g_low_val=None): + if g_high_val is None: + g_high_val = g_high + if g_low_val is None: + g_low_val = g_low + λ = lambda_weight(I) + return λ[..., None] * g_high_val + (1 - λ[..., None]) * g_low_val + + +def g_of_I_jax(I, g_high_val, g_low_val): + λ = lambda_weight_jax(I) + return λ[..., None] * g_high_val + (1 - λ[..., None]) * g_low_val + + +print(f"Likelihood ratios g_low / g_high : {g_low / g_high}") +print(f"Y_L signals low investment with ratio {g_low[0]/g_high[0]:.1f}x") +``` + +```{note} +The computation uses log utility, $u(c)=\log(c)$, as a numerical +illustration. + +This relaxes the bounded-above primitive utility assumption used in the +existence argument of {prf:ref}`atkeson_assumption_preferences`. + +On the finite grid, with finite resource bounds, the objective nonetheless +remains bounded. ``` ### Autarky value function @@ -324,10 +698,10 @@ Starting each period with resources $Q$, the borrower solves $$ -U_{\text{aut}}(Q) = - \max_{I \in \{0,\,I_h\}} +v_{\text{aut}}(Q) = + \max_{0 \leq I \leq \min\{Q,I_{\max}\}} \Bigl[(1-\beta)\,u(Q - I) + \beta - \bigl[g(I)_L\,U_{\text{aut}}(Y_L) + g(I)_H\,U_{\text{aut}}(Y_H)\bigr]\Bigr]. + \sum_j g(Y_j;I)v_{\text{aut}}(Y_j)\Bigr]. $$ Note that the continuation values depend only on $Y_L$ and $Y_H$, not on the @@ -335,157 +709,196 @@ current $Q$, because next period's state is simply the realised output. ```{code-cell} ipython3 @jax.jit -def autarky_step_jax(V, β_val, g_h_val, g_l_val): - """One vectorised Bellman step for the binary-investment autarky problem.""" - EV_h = jnp.dot(g_h_val, jnp.interp(jnp.asarray(Y), Q_grid_j, V)) - EV_l = jnp.dot(g_l_val, jnp.interp(jnp.asarray(Y), Q_grid_j, V)) - val_h = jnp.where( - Q_grid_j > I_h + 1e-10, - (1 - β_val) * u_jax(Q_grid_j - I_h) + β_val * EV_h, - -jnp.inf - ) - val_l = (1 - β_val) * u_jax(Q_grid_j) + β_val * EV_l - return jnp.maximum(val_h, val_l) - - -def autarky_vfi(β_val=None, g_h_val=None, g_l_val=None, tol=1e-8, max_iter=3000): - """Value function iteration for the autarky problem (binary investment).""" +def autarky_operator_jax(V, β_val, g_high_val, g_low_val): + """One vectorised Bellman step for the autarky problem.""" + V_Y = jnp.interp(Y_j, Q_grid_j, V) + g_I = g_of_I_jax(I_grid_j, g_high_val, g_low_val) + EV_I = g_I @ V_Y + c = Q_grid_j[:, None] - I_grid_j[None, :] + val = (1 - β_val) * u_jax(c) + β_val * EV_I[None, :] + val = jnp.where(c >= 1e-10, val, -jnp.inf) + idx = jnp.argmax(val, axis=1) + return jnp.max(val, axis=1), I_grid_j[idx] + + +def autarky_vfi(β_val=None, + g_high_val=None, + g_low_val=None, + tol=1e-8, + max_iter=3000, + verbose=True): + """Value function iteration for the autarky problem.""" if β_val is None: β_val = β - if g_h_val is None: - g_h_val = g_h - if g_l_val is None: - g_l_val = g_l + if g_high_val is None: + g_high_val = g_high + if g_low_val is None: + g_low_val = g_low V = jnp.zeros(N_Q) - g_h_j = jnp.asarray(g_h_val) - g_l_j = jnp.asarray(g_l_val) + g_high_j = jnp.asarray(g_high_val) + g_low_j = jnp.asarray(g_low_val) for it in range(max_iter): - V_new = autarky_step_jax(V, β_val, g_h_j, g_l_j) - diff = float(jnp.max(jnp.abs(V_new - V))) - V = V_new + V_new, policy_I = autarky_operator_jax(V, β_val, + g_high_j, g_low_j) + diff = float(jnp.max(jnp.abs(V_new - V))) + V = V_new if diff < tol: - print(f"Autarky VFI converged in {it+1} iterations (diff={diff:.2e})") + if verbose: + print( + f"Autarky VFI converged in {it+1} iterations " + f"(diff={diff:.2e})" + ) break - return np.asarray(V) + return np.asarray(V), np.asarray(policy_I) -V_aut = autarky_vfi() +V_aut, I_aut = autarky_vfi() ``` -### Constrained Pareto optimal contract +### Program P* We solve Program P* iteratively. -At each state $Q$, the planner chooses -continuation states $(Q'_L, Q'_H)$ — equivalently, state-contingent -repayments $d_j = Y_j - Q'_j$ — to maximise the borrower's payoff. +At each state $Q$, the planner chooses current investment $I$ and +continuation states $(Q'_L,Q'_H)$, equivalently repayments +$d_j = Y_j - Q'_j$. -Taking lender participation *binding* (Proposition 5 implies this is without -loss of generality), the loan is determined by +With lender participation imposed as binding, the loan is determined by $$ -b^* \;=\; \beta\bigl[g_{h,L}(Y_L - Q'_L) + g_{h,H}(Y_H - Q'_H)\bigr], +b^*(Q,I,Q') + = \beta \sum_j (Y_j - Q'_j)g(Y_j;I), $$ -and current consumption is $c^* = Q + b^* - I_h$. +and current consumption is $c^* = Q + b^* - I$. + +We impose lender participation as binding in the displayed calibration. + +This is valid here because the implied loan remains below $M$, and the code +discards any candidate whose zero-profit loan would exceed $M$. -The optimisation reduces -to a two-dimensional problem in $(Q'_L, Q'_H)$: +If instead $M$ binds, $b$ must be treated as a separate constrained choice, or +set to $\min\{M,\,\beta\sum_j d_j g(Y_j;I)\}$ for each candidate. + +On the two-output grid, the search is over $I$ and $(Q'_L,Q'_H)$: $$ \max_{Q'_L,\,Q'_H} - (1-\beta)\,u(c^*) + \beta\bigl[V(Q'_L)\,g_{h,L} + V(Q'_H)\,g_{h,H}\bigr] + \max_I \left\{(1-\beta)\,u(c^*) + + \beta\sum_j v(Q'_j)g(Y_j;I)\right\} $$ subject to: -- **(NR)** $V(Q'_j) \geq U_{\text{aut}}(Y_j)$, i.e. $Q'_j \geq Q^*_j$ -- **(IC)** $\beta\,\Delta g \cdot V(Q') \geq (1-\beta)\,[u(c^*+I_h) - u(c^*)]$ +- **(NR)** $v(Q'_j) \geq v_{\text{aut}}(Y_j)$, i.e. $Q'_j \geq Q^*_j$ +- **(IC)** $I$ solves the borrower's hidden investment problem - **(F)** $c^* \geq 0$ -where $\Delta g_j = g_{h,j} - g_{l,j}$ and $Q^*_j = V^{-1}(U_{\text{aut}}(Y_j))$. +The code enforces IC by checking every alternative investment on `I_grid`. ```{code-cell} ipython3 def find_Qmin(V_arr, v_thresh): - """Return min Q on grid with V(Q) >= v_thresh (no-repudiation lower bound).""" + """Return min Q on grid with value above the no-repudiation bound.""" + if v_thresh <= V_arr[0]: return float(Q_MIN) if v_thresh >= V_arr[-1]: return float(Q_MAX) + # Use searchsorted on a monotone version of V - V_mono = np.maximum.accumulate(V_arr) # enforce monotone for inversion - idx = np.searchsorted(V_mono, v_thresh) - idx = np.clip(idx, 1, N_Q - 1) - denom = V_mono[idx] - V_mono[idx-1] + V_mono = np.maximum.accumulate(V_arr) # enforce monotone for inversion + idx = np.searchsorted(V_mono, v_thresh) + idx = np.clip(idx, 1, N_Q - 1) + denom = V_mono[idx] - V_mono[idx-1] + if abs(denom) < 1e-14: return float(Q_grid[idx-1]) + t = (v_thresh - V_mono[idx-1]) / denom return float(Q_grid[idx-1] + t * (Q_grid[idx] - Q_grid[idx-1])) @jax.jit -def pareto_bellman_step_jax(V, V_aut_arr, β_val, g_h_val, g_l_val, Qp_min): +def program_p_bellman_step_jax(V, V_aut_arr, I_aut_arr, + β_val, g_high_val, g_low_val, Qp_min): """ - One vectorised application of the constrained Pareto Bellman operator. - - The optimizer is a deterministic grid maximisation over all pairs - (Q'_L, Q'_H). JAX evaluates all states and all pairs in one compiled pass, - avoiding thousands of small SLSQP solves while preserving the NR, IC and - feasibility restrictions. + One grid Bellman step for Program P*. """ - Δg_val = g_h_val - g_l_val - - V_L = jnp.interp(Qp_L_flat, Q_grid_j, V) - V_H = jnp.interp(Qp_H_flat, Q_grid_j, V) - - b_pair = β_val * ( - g_h_val[0] * (Y_L - Qp_L_flat) - + g_h_val[1] * (Y_H - Qp_H_flat) + g_I = g_of_I_jax(I_grid_j, g_high_val, g_low_val) + V_pair = jnp.column_stack(( + jnp.interp(Qp_flat[:, 0], Q_grid_j, V), + jnp.interp(Qp_flat[:, 1], Q_grid_j, V) + )) + + d_pair = Y_j[None, :] - Qp_flat + pair_ok = ( + (Qp_flat[:, 0] >= Qp_min[0]) + & (Qp_flat[:, 1] >= Qp_min[1]) + & jnp.all(-d_pair <= M, axis=1) ) - EV_pair = g_h_val[0] * V_L + g_h_val[1] * V_H - IC_lhs = β_val * (Δg_val[0] * V_L + Δg_val[1] * V_H) - NR_mask = (Qp_L_flat >= Qp_min[0]) & (Qp_H_flat >= Qp_min[1]) - - c = Q_grid_j[:, None] + b_pair[None, :] - I_h - IC_rhs = (1 - β_val) * (u_jax(c + I_h) - u_jax(c)) - feasible = NR_mask[None, :] & (c >= 1e-10) & (IC_lhs[None, :] >= IC_rhs) - obj = (1 - β_val) * u_jax(c) + β_val * EV_pair[None, :] + b = β_val * jnp.einsum("iy,py->ip", g_I, d_pair) + EV = jnp.einsum("iy,py->ip", g_I, V_pair) + candidate_ok = pair_ok[None, :] & (b <= M) + + resources = Q_grid_j[:, None, None] + b[None, :, :] + c = resources - I_grid_j[None, :, None] + obj = (1 - β_val) * u_jax(c) + β_val * EV[None, :, :] + + dev_best = jnp.full(obj.shape, -jnp.inf) + for i_alt in range(N_I): + I_alt = I_grid_j[i_alt] + EV_alt = jnp.einsum("y,py->p", g_I[i_alt], V_pair) + c_alt = resources - I_alt + dev = ((1 - β_val) * u_jax(c_alt) + + β_val * EV_alt[None, None, :]) + dev = jnp.where(c_alt >= 1e-10, dev, -jnp.inf) + dev_best = jnp.maximum(dev_best, dev) + + feasible = ( + candidate_ok[None, :, :] + & (c >= 1e-10) + & (obj >= dev_best - 1e-8) + ) obj = jnp.where(feasible, obj, -jnp.inf) - idx = jnp.argmax(obj, axis=1) - best_val = jnp.max(obj, axis=1) + flat = obj.reshape((N_Q, -1)) + idx = jnp.argmax(flat, axis=1) + best_val = jnp.max(flat, axis=1) has_feasible = jnp.isfinite(best_val) - fallback = V_aut_arr - default_L = jnp.clip(jnp.maximum(Y_L, Qp_min[0]), Q_MIN, Q_MAX) - default_H = jnp.clip(jnp.maximum(Y_H, Qp_min[1]), Q_MIN, Q_MAX) - use_fallback = (~has_feasible) | (best_val <= fallback) + I_flat = jnp.repeat(I_grid_j, n_pair) + b_flat = b.reshape(-1) + Qp_L_flat = jnp.tile(Qp_flat[:, 0], N_I) + Qp_H_flat = jnp.tile(Qp_flat[:, 1], N_I) - pol_L = jnp.where(use_fallback, default_L, Qp_L_flat[idx]) - pol_H = jnp.where(use_fallback, default_H, Qp_H_flat[idx]) - V_new = jnp.where(use_fallback, fallback, best_val) - pol_b = β_val * (g_h_val[0] * (Y_L - pol_L) - + g_h_val[1] * (Y_H - pol_H)) - pol_Qp = jnp.column_stack((pol_L, pol_H)) + use_autarky = (~has_feasible) | (best_val < V_aut_arr) + V_new = jnp.where(use_autarky, V_aut_arr, best_val) + pol_I = jnp.where(use_autarky, I_aut_arr, I_flat[idx]) + pol_b = jnp.where(use_autarky, 0.0, b_flat[idx]) + pol_Qp = jnp.column_stack(( + jnp.where(use_autarky, Y_j[0], Qp_L_flat[idx]), + jnp.where(use_autarky, Y_j[1], Qp_H_flat[idx]) + )) - return V_new, pol_b, pol_Qp + return V_new, pol_I, pol_b, pol_Qp -def pareto_bellman(V, V_aut_arr, β_val=None, g_h_val=None, g_l_val=None, - ε=0.0): +def program_p_bellman(V, V_aut_arr, I_aut_arr, + β_val=None, + g_high_val=None, + g_low_val=None, + ε=0.0): """ - One application of the constrained Pareto Bellman operator. - Returns updated V, optimal loan policy pol_b, and continuation - states pol_Qp[:,0] (after Y_L) and pol_Qp[:,1] (after Y_H). + One Bellman step for Program P*. """ if β_val is None: β_val = β - if g_h_val is None: - g_h_val = g_h - if g_l_val is None: - g_l_val = g_l + if g_high_val is None: + g_high_val = g_high + if g_low_val is None: + g_low_val = g_low Vaut_f = interp1d(Q_grid, V_aut_arr, fill_value='extrapolate', bounds_error=False) @@ -493,59 +906,77 @@ def pareto_bellman(V, V_aut_arr, β_val=None, g_h_val=None, g_l_val=None, Qp_min = np.array([find_Qmin(V, v) for v in Vaut_Y]) Qp_min = np.clip(Qp_min, Q_MIN, Q_MAX - 1e-4) - V_new, pol_b, pol_Qp = pareto_bellman_step_jax( - jnp.asarray(V), jnp.asarray(V_aut_arr), β_val, - jnp.asarray(g_h_val), jnp.asarray(g_l_val), jnp.asarray(Qp_min) + V_new, pol_I, pol_b, pol_Qp = program_p_bellman_step_jax( + jnp.asarray(V), jnp.asarray(V_aut_arr), jnp.asarray(I_aut_arr), + β_val, jnp.asarray(g_high_val), jnp.asarray(g_low_val), + jnp.asarray(Qp_min) ) - return np.asarray(V_new), np.asarray(pol_b), np.asarray(pol_Qp) - - -def pareto_vfi(V_aut_arr, β_val=None, g_h_val=None, g_l_val=None, - ε=0.0, tol=1e-3, max_iter=60, relaxation=0.2): - """Value function iteration for Program P*.""" + return (np.asarray(V_new), np.asarray(pol_I), + np.asarray(pol_b), np.asarray(pol_Qp)) + + +def program_p_vfi(V_aut_arr, + I_aut_arr, + β_val=None, + g_high_val=None, + g_low_val=None, + ε=0.0, + tol=2e-4, + max_iter=1000, + relaxation=0.25, + verbose=True): + """Value iteration for Program P*.""" if β_val is None: β_val = β - if g_h_val is None: - g_h_val = g_h - if g_l_val is None: - g_l_val = g_l + if g_high_val is None: + g_high_val = g_high + if g_low_val is None: + g_low_val = g_low V = V_aut_arr.copy() for it in range(max_iter): - V_raw, pol_b, pol_Qp = pareto_bellman( - V, V_aut_arr, β_val=β_val, g_h_val=g_h_val, g_l_val=g_l_val, - ε=ε) + V_raw, pol_I, pol_b, pol_Qp = program_p_bellman( + V, V_aut_arr, I_aut_arr, β_val=β_val, + g_high_val=g_high_val, g_low_val=g_low_val, ε=ε) V_new = (1 - relaxation) * V + relaxation * V_raw diff = np.max(np.abs(V_new - V)) - V = V_new - print(f" iter {it+1:3d}, max|ΔV| = {diff:.5f}") + V = V_new + if verbose and (it == 0 or (it + 1) % 10 == 0): + print(f" iter {it+1:3d}, max|ΔV| = {diff:.5f}") if diff < tol: - print(f"Pareto VFI converged in {it+1} iterations.") + if verbose: + print(f"Program P* VFI converged in {it+1} iterations.") break + else: + if verbose: + print(f"Stopped after {max_iter} iterations " + f"(max|ΔV| = {diff:.2e}).") - return V, pol_b, pol_Qp + return V, pol_I, pol_b, pol_Qp -print("Running constrained Pareto VFI …") -V_pareto, pol_b, pol_Qp = pareto_vfi(V_aut) +print("Running Program P* VFI ...") +V_pareto, pol_I, pol_b, pol_Qp = program_p_vfi(V_aut, I_aut) ``` ### Value functions +Let's start by plotting the autarky value and the Program P* value. + ```{code-cell} ipython3 --- mystnb: figure: - caption: autarky and optimal contract values + caption: autarky and Program P* values name: fig-atk-value --- fig, ax = plt.subplots() -ax.plot(Q_grid, V_aut, lw=2, label=r'Autarky $U_{\rm aut}(Q)$') +ax.plot(Q_grid, V_aut, lw=2, label=r'Autarky $v_{\rm aut}(Q)$') ax.plot(Q_grid, V_pareto, lw=2, ls='--', - label=r'Optimal contract $\bar{V}(Q)$') + label=r'Program P* value $\bar v(Q)$') ax.set_xlabel(r'state $Q$ (output net of repayment)') ax.set_ylabel('normalised utility') @@ -554,11 +985,73 @@ plt.tight_layout() plt.show() ``` -The optimal contract weakly dominates autarky, and strictly improves on it -over the active borrowing region, because access to credit allows the borrower -to share risk with lenders and smooth consumption across output realisations. +The Program P* value dominates autarky in the plotted region. + +Access to credit lets the borrower smooth +consumption across output realizations while preserving incentives for +investment. + +The vertical distance between the two curves is the value of the lending +relationship, net of the incentive and repudiation constraints. + +The gain is not the complete-markets gain from perfect insurance. + +It is the value that remains once the contract must both induce hidden +investment and keep the borrower from preferring repudiation after each +output realization. + +### Investment + +The next figure reports the investment chosen by the contract and the +autarky investment policy. + +```{code-cell} ipython3 +--- +mystnb: + figure: + caption: investment policy + name: fig-atk-investment +--- +fig, ax = plt.subplots() + +ax.plot(Q_grid, I_aut, lw=2, label='Autarky') +ax.plot(Q_grid, pol_I, lw=2, ls='--', label='Program P*') +ax.set_xlabel(r'state $Q$') +ax.set_ylabel(r'investment $I(Q)$') +ax.legend() +plt.tight_layout() +plt.show() +``` + +This figure compares investment in autarky with investment under the +optimal lending contract. -### Optimal continuation states and the no-repudiation constraint +Both policies are step functions because investment is chosen from the finite +grid `I_grid`. + +Under autarky, the borrower uses only its own current resources, so +investment rises with $Q$ once enough resources are available. + +Under Program P*, investment is disciplined by the contract. + +At low and middle states the lending relationship can support positive +investment earlier than autarky because loans relax the current resource +constraint. + +At higher states, however, the Program P* investment schedule is flatter and +lower than autarky in this calibration. + +The reason is not that resources are scarce, but that investment must be +incentive compatible: the continuation-value spread across low and high +output has to make the chosen investment privately optimal for the borrower. + +When raising investment would require too much output-contingent punishment +or reward, the optimal contract chooses a lower investment level. + +### Continuation states and the no-repudiation constraint + +Let's now look at the continuation states $Q'_L$ and $Q'_H$ after +low and high output, respectively. ```{code-cell} ipython3 --- @@ -569,11 +1062,12 @@ mystnb: --- # Compute no-repudiation floors Vaut_at_Y = np.array([float(interp1d(Q_grid, V_aut, - fill_value='extrapolate', bounds_error=False)(yj)) for yj in Y]) -Qp_min_L = find_Qmin(V_pareto, Vaut_at_Y[0]) -Qp_min_H = find_Qmin(V_pareto, Vaut_at_Y[1]) + fill_value='extrapolate', + bounds_error=False)(yj)) for yj in Y]) +Qp_min_L = find_Qmin(V_pareto, Vaut_at_Y[0]) +Qp_min_H = find_Qmin(V_pareto, Vaut_at_Y[1]) -fig, axes = plt.subplots(1, 2) +fig, axes = plt.subplots(1, 2, figsize=(8, 4), sharex=True, sharey=True) # Left: Q'_L (continuation state after low output) axes[0].plot(Q_grid, pol_Qp[:, 0], lw=2, label=r"$Q'_L = Y_L - d_L$") @@ -581,7 +1075,6 @@ axes[0].axhline(Qp_min_L, ls='--', color='C3', label=fr"NR floor $Q^*_L \approx {Qp_min_L:.3f}$") axes[0].set_xlabel(r'state $Q$') axes[0].set_ylabel(r"$Q'_L$") -axes[0].legend() # Right: Q'_H (continuation state after high output) axes[1].plot(Q_grid, pol_Qp[:, 1], lw=2, color='C1', @@ -590,74 +1083,192 @@ axes[1].axhline(Qp_min_H, ls='--', color='C3', label=fr"NR floor $Q^*_H \approx {Qp_min_H:.3f}$") axes[1].set_xlabel(r'state $Q$') axes[1].set_ylabel(r"$Q'_H$") -axes[1].legend() + +for ax in axes: + ax.set_xlim(Q_MIN, Q_MAX) + ax.set_ylim(Q_MIN, Q_MAX) + ax.set_aspect('equal', adjustable='box') + ax.legend() plt.tight_layout() plt.show() ``` -After a *low-output* realisation, the continuation state $Q'_L$ is pinned -at the no-repudiation floor $Q^*_L$. +The dashed horizontal lines are no-repudiation floors. -This means the repayment $d_L = Y_L - Q'_L$ is as large as the +A continuation state cannot fall below its floor, because otherwise the +borrower would prefer repudiation. + +In this calibration, the low-output continuation state $Q'_L$ is pinned +at the floor only for low current states. + +Over that region, repayment $d_L = Y_L - Q'_L$ is as large as the repudiation constraint allows. -After a *high-output* realisation, $Q'_H > Q^*_H$: the constraint is -slack and the borrower retains more resources, rewarding the high -investment that produced good output. +For higher current states, the no-repudiation constraint is slack and +$Q'_L$ rises with $Q$. + +The high-output continuation state $Q'_H$ is generally higher than +$Q'_L$, rewarding the high investment that makes high output more likely. + +The two panels should be read as punishment and reward schedules. + +After low output, the borrower is sent to a lower continuation state, which +reduces future utility and helps deter low investment. -### Optimal loan and net capital flows +After high output, the borrower is sent to a higher continuation state, +which rewards the outcome that is more likely when investment is high. + +The horizontal dashed lines mark the smallest continuation states compatible +with no repudiation. + +When a policy curve touches one of these lines, the contract is using the +maximum feasible punishment at that output realization. + +### Loan and net capital flows ```{code-cell} ipython3 --- mystnb: figure: - caption: loan and net capital flows + caption: low-output loan and net capital flows name: fig-atk-loan-flows --- -# Repayments at the two output states as functions of current state Q -d_L_policy = Y_L - pol_Qp[:, 0] # d_L(Q) = Y_L − Q'_L(Q) -d_H_policy = Y_H - pol_Qp[:, 1] # d_H(Q) = Y_H − Q'_H(Q) +# Repayment and next loan at the low-output continuation state +d_L_policy = Y_L - pol_Qp[:, 0] # d_L(Q) = Y_L - Q'_L(Q) + +pol_b_fn = interp1d(Q_grid, pol_b, + fill_value='extrapolate', bounds_error=False) +b_next_L = pol_b_fn(pol_Qp[:, 0]) + +net_out_L = d_L_policy - b_next_L +low_outflow = net_out_L > 0 -fig, axes = plt.subplots(1, 2) +fig, axes = plt.subplots(1, 2, figsize=(8, 4)) -axes[0].plot(Q_grid, pol_b, lw=2, label='Loan $b^*(Q)$') -axes[0].plot(Q_grid, d_L_policy, lw=2, ls='--', label=r'Repayment $d_L$') -axes[0].plot(Q_grid, d_H_policy, lw=2, ls=':', label=r'Repayment $d_H$') +axes[0].plot(Q_grid, d_L_policy, lw=2, label=r'Repayment $d_L(Q)$') +axes[0].plot(Q_grid, b_next_L, lw=2, ls='--', + label=r"New loan $b^*(Q'_L)$") +axes[0].fill_between(Q_grid, d_L_policy, b_next_L, + where=low_outflow, interpolate=True, + color='C3', alpha=0.16, label='capital outflow') axes[0].axhline(0, color='k', lw=0.6, ls=':') +axes[0].set_title('After low output') axes[0].set_xlabel(r'state $Q$') +axes[0].set_ylabel('level') axes[0].legend() -# Net capital outflow at continuation state -pol_b_fn = interp1d(Q_grid, pol_b, fill_value='extrapolate', bounds_error=False) -net_out_L = d_L_policy - pol_b_fn(pol_Qp[:, 0]) -net_out_H = d_H_policy - pol_b_fn(pol_Qp[:, 1]) - -axes[1].plot(Q_grid, net_out_L, lw=2, label=r'After $Y_L$ (low output)') -axes[1].plot(Q_grid, net_out_H, lw=2, ls='--', label=r'After $Y_H$ (high output)') +axes[1].plot(Q_grid, net_out_L, + lw=2, label=r"$d_L(Q) - b^*(Q'_L)$") +axes[1].fill_between(Q_grid, 0, net_out_L, + where=low_outflow, interpolate=True, + color='C3', alpha=0.16) axes[1].axhline(0, color='k', lw=0.8, ls=':') +axes[1].set_title('Low-output net flow') axes[1].set_xlabel(r'state $Q$') -axes[1].set_ylabel(r"net outflow $d(Y') - b'(Q')$") +axes[1].set_ylabel(r"net outflow after $Y_L$") axes[1].legend() plt.tight_layout() plt.show() ``` -Positive values in the right panel are net capital outflows. +This figure isolates the low-output branch of the contract. + +Start from current state $Q$. + +If next period's output is low, $Y_L$, the contract sends the borrower to +the continuation state + +$$ +Q'_L(Q) = Y_L - d_L(Q). +$$ + +The old lender receives the repayment $d_L(Q)$. + +At that new state, the next young lender offers the loan +$b^*(Q'_L(Q))$. + +The low-output net capital outflow is therefore + +$$ +d_L(Q) - b^*(Q'_L(Q)). +$$ + +The left panel plots the two pieces of this difference. + +The right panel plots the difference itself. + +Values above zero are capital outflows: the borrower repays more to the old +lender than it receives as a new loan. + +Values below zero are capital inflows: new borrowing more than offsets the +repayment. + +The shaded region marks the states in which + +$$ +d_L(Q) > b^*(Q'_L(Q)). +$$ + +In that region, repayment after bad news about investment is not fully offset +by new borrowing, so the borrower exports capital. -In this coarse -two-output calibration, the low-output continuation state is tightened sharply, -but new borrowing at that continuation state is still large enough that -$d_L - b'(Q'_L)$ is non-positive. +This is the numerical analogue of Atkeson's capital-outflow condition +$d_j \geq b'(Q'_j)$ for the lowest output realization. -Thus the computation should be read as a -small numerical illustration of the incentive mechanism, rather than a -calibration that delivers literal positive outflows after every low-output -realisation. +Outside the shaded region, low output is still punished through a lower +continuation state, but that punishment does not show up as a literal net +capital outflow because the next loan is larger than the repayment. ### Simulation +We now simulate one history generated by the computed contract. + +This is an on-contract path. + +The borrower follows the recommended investment policy, so next output is +drawn from $g(Y';I_t)$. + +The simulation does not draw deviations or defaults. + +It asks what histories look like when the contract is obeyed. + +At the start of a period, the state is $Q_t$, output net of the old +repayment. + +The policy functions choose current investment $I_t = I(Q_t)$, current loan +$b_t = b(Q_t)$, and current consumption + +$$ +c_t = Q_t + b_t - I_t. +$$ + +Then output $Y_{t+1}$ is drawn. + +If output state $j$ occurs, the policy function sends the borrower to + +$$ +Q_{t+1} = Q'_j(Q_t). +$$ + +The repayment due to the old lender is therefore + +$$ +d_{t+1}(Y_j) = Y_j - Q'_j(Q_t). +$$ + +The net capital outflow reported below is + +$$ +d_{t+1}(Y_j) - b(Q_{t+1}). +$$ + +It is repayment to the old lender minus the new loan received at the +continuation state. + +Positive values are capital outflows. + ```{code-cell} ipython3 --- mystnb: @@ -665,85 +1276,187 @@ mystnb: caption: simulated contract paths name: fig-atk-simulation --- -def simulate_contract(V_pareto, pol_b, pol_Qp, T=150, seed=0): +def simulate_contract(pol_I, pol_b, pol_Qp, T=150, seed=0): """ - Simulate the constrained optimal contract. - At each period the borrower invests I_h and output is drawn from g_h. - Returns time series for Q, Y, consumption c, loan b, repayment d, - and net capital outflow. + Simulate one on-contract history. + + The borrower follows the computed investment and loan policies. """ rng = np.random.default_rng(seed) + I_fn = interp1d(Q_grid, pol_I, fill_value='extrapolate', + bounds_error=False) Qp_fn = [interp1d(Q_grid, pol_Qp[:, j], fill_value='extrapolate', bounds_error=False) for j in range(2)] - b_fn = interp1d(Q_grid, pol_b, fill_value='extrapolate', bounds_error=False) + b_fn = interp1d(Q_grid, pol_b, fill_value='extrapolate', + bounds_error=False) - Q = float(np.median(Q_grid)) # start at median state + Q = float(np.median(Q_grid)) # start at median state - out = {'Q': [], 'Y': [], 'c': [], 'b': [], 'd': [], 'net_out': []} + out = {'Q': [], 'Y': [], 'I': [], 'c': [], 'b': [], 'b_next': [], + 'd': [], 'net_out': []} for _ in range(T): - b = float(b_fn(Q)) - c = Q + b - I_h - c = max(c, 1e-10) + I = float(I_fn(Q)) + b = float(b_fn(Q)) + c = Q + b - I + c = max(c, 1e-10) - j = int(rng.choice(2, p=g_h)) # draw next output index + probs = np.asarray(g_of_I(np.array(I))).ravel() + j = int(rng.choice(2, p=probs)) Yp = Y[j] - Qp = float(Qp_fn[j](Q)) # next state + Qp = float(Qp_fn[j](Q)) # next state - d = Yp - Qp # repayment at start of next period - b_next = float(b_fn(Qp)) - net_out = d - b_next # net capital outflow at next period + d = Yp - Qp # repayment after output is realized + b_next = float(b_fn(Qp)) + net_out = d - b_next # repayment minus new borrowing - out['Q'].append(Q); out['Y'].append(Yp); out['c'].append(c) - out['b'].append(b); out['d'].append(d); out['net_out'].append(net_out) + out['Q'].append(Q) + out['Y'].append(Yp) + out['I'].append(I) + out['c'].append(c) + out['b'].append(b) + out['b_next'].append(b_next) + out['d'].append(d) + out['net_out'].append(net_out) Q = Qp return {k: np.array(v) for k, v in out.items()} -sim = simulate_contract(V_pareto, pol_b, pol_Qp, T=150) -t = np.arange(len(sim['Q'])) +sim = simulate_contract(pol_I, pol_b, pol_Qp, T=150) +t = np.arange(len(sim['Q'])) -fig, axes = plt.subplots(3, 1, sharex=True) +fig, axes = plt.subplots(4, 1, figsize=(10, 8), sharex=True) axes[0].plot(t, sim['Y'], alpha=0.6, label='Output $Y_{t+1}$') -axes[0].plot(t, sim['c'], lw=1.8, label='Consumption $c_t$') +axes[0].plot(t, sim['c'], lw=1.8, label='Consumption $c_t$') axes[0].set_ylabel('level') axes[0].legend(ncol=2, loc='upper right') -axes[1].plot(t, sim['d'], lw=1.8, label='Repayment $d_t$') -axes[1].plot(t, sim['b'], lw=1.8, ls='--', label='New loan $b_t$') +axes[1].plot(t, sim['I'], lw=1.8, color='C2', label='Investment $I_t$') axes[1].axhline(0, color='k', lw=0.5) -axes[1].set_ylabel('level') -axes[1].legend(ncol=2) +axes[1].set_ylabel('investment') +axes[1].legend() + +axes[2].plot(t, sim['d'], lw=1.8, label='Repayment $d_{t+1}$') +axes[2].plot(t, sim['b_next'], lw=1.8, ls='--', + label=r'New loan $b(Q_{t+1})$') +axes[2].axhline(0, color='k', lw=0.5) +axes[2].set_ylabel('level') +axes[2].legend(ncol=2) colors = ['#d73027' if x > 0 else '#4575b4' for x in sim['net_out']] -axes[2].bar(t, sim['net_out'], color=colors, label='Net capital outflow') -axes[2].axhline(0, color='k', lw=0.6) -axes[2].set_xlabel('period $t$') -axes[2].set_ylabel('net outflow') -axes[2].legend() +axes[3].bar(t, sim['net_out'], color=colors, label='Net capital outflow') +axes[3].axhline(0, color='k', lw=0.6) +axes[3].set_xlabel('period $t$') +axes[3].set_ylabel('net outflow') +axes[3].legend() -plt.tight_layout() +fig.tight_layout(h_pad=1.0) plt.show() -# Tabulate statistics -outflow_frac = np.mean(sim['net_out'] > 0) -print(f"\nFraction of periods with capital outflow: {outflow_frac:.2%}") -print(f"Fraction of low-output periods: " - f"{np.mean(sim['Y'] == Y_L):.2%}") +# Atkeson's capital export operates in the constrained region, where the +# no-repudiation floor binds after low output (the shaded low-Q region above). +Q_star = float(Q_grid[net_out_L > 0].max()) + +# A longer simulation gives stable ergodic frequencies. +sim_long = simulate_contract(pol_I, pol_b, pol_Qp, T=20_000) +low = sim_long['Y'] == Y_L +constrained = sim_long['Q'] <= Q_star + +print(f"\nTime in the constrained region (Q <= {Q_star:.2f}): " + f"{np.mean(constrained):.1%}") +print(f"Low-output capital outflow frequency, constrained: " + f"{np.mean(sim_long['net_out'][low & constrained] > 0):.1%}") +print(f"Low-output capital outflow frequency, unconstrained: " + f"{np.mean(sim_long['net_out'][low & ~constrained] > 0):.1%}") ``` -Positive bars are net capital outflows. +This simulated history illustrates how the contract smooths resources while +still using output-contingent continuation promises. + +Output jumps between the two possible realizations, $Y_L = 0.8$ and +$Y_H = 1.2$, but consumption moves much less sharply. + +Most of the time consumption stays near the middle of the output range rather +than matching output one for one. + +Investment is also nearly flat. + +Along this path it is usually close to $0.10$, with only a few grid-sized +adjustments when the continuation state becomes especially favorable or +especially tight. + +The third panel shows the two terms used to construct the net-flow bars. + +Repayment $d_{t+1}$ and the next-state loan $b(Q_{t+1})$ move almost +together. + +Both are often negative in this calibration, so the contract is frequently +using deposits or withdrawals rather than ordinary positive borrowing. + +The net capital flow is the difference between the repayment due after output +is realized and the loan available at the next state, + +$$ +d_{t+1} - b(Q_{t+1}). +$$ + +Red bars are periods in which this difference is positive, so on net the +borrower sends resources to the lending sector and capital flows out. -With the baseline two-state -calibration above, the main visible effect of low output is a tighter -continuation state and lower subsequent borrowing; literal positive outflows -require a calibration in which the low-state repudiation constraint binds more -strongly. +Blue bars are periods in which it is negative, so on net the borrower receives +resources and capital flows in. + +The contract's capital flows split into two regimes. + +In the constrained region, where the borrower is poor and the no-repudiation +floor binds, low output forces repayment to exceed new lending and capital +flows out. + +This is Atkeson's result, the shaded low-$Q$ region of the loan-flow figure +above. + +Along the path, low output exports capital about 60% of the time the borrower +is in this region, against essentially never outside it. + +In the unconstrained region, where the borrower is richer and the floor is +slack, the pattern is buffer-stock saving instead. + +There the borrower deposits after high output, a capital outflow, and draws +those deposits down after low output, a capital inflow. + +The borrower spends about a fifth of its time in the constrained region, +because good output builds a buffer that lifts it out. + +## Summary + +The central friction in this lecture is moral hazard. + +The borrower privately chooses investment, while lenders observe only output. + +Low output is therefore bad news for two reasons. + +It lowers current resources and it is also evidence that the borrower may have +chosen low investment. + +Atkeson's optimal contract responds by making continuation values depend on +output. + +High output is rewarded with a better continuation state. + +Low output is punished with a tighter continuation state, subject to the +borrower's option to repudiate and live in autarky. + +This is the same logic as in {doc}`Repeated Moral Hazard `: +hidden actions are disciplined by future promised utility. + +Atkeson's contribution is to judiciously combine that incentive logic with sovereign +default risk and a physical state variable, so continuation promises must also +respect the no-repudiation constraint. ## Exercises @@ -751,12 +1464,12 @@ strongly. :label: atkeson_1991_ex1 ``` -**Patience and the severity of debt crises.** +*Patience and the severity of debt crises.* Redo the analysis with $\beta = 0.8$ and $\beta = 0.95$ (keep all other parameters fixed). -1. For each value of $\beta$, compute the autarky and optimal contract value +1. For each value of $\beta$, compute the autarky and Program P* value functions. 2. Compute the no-repudiation lower bounds $Q^*_L$ and $Q^*_H$. 3. Plot $Q'_L(Q)$ for the three values of $\beta$ on a single figure. @@ -769,6 +1482,8 @@ parameters fixed). :class: dropdown ``` +Here is one solution: + ```{code-cell} ipython3 --- mystnb: @@ -778,17 +1493,22 @@ mystnb: --- fig, ax = plt.subplots() -for β_val, ls, color in [(0.8, '-', 'C0'), (0.9, '--', 'C1'), (0.95, ':', 'C2')]: - V_a = autarky_vfi(β_val=β_val) - V_p, _, pQp = pareto_vfi(V_a, β_val=β_val) +for β_val, ls, color, tag in [ + (0.8, '-', 'C0', ''), + (β, '--', 'C1', ' baseline'), + (0.95, ':', 'C2', '')]: + V_a, I_a = autarky_vfi(β_val=β_val, verbose=False) + V_p, _, _, pQp = program_p_vfi( + V_a, I_a, β_val=β_val, verbose=False) Vaut_fn_tmp = interp1d(Q_grid, V_a, fill_value='extrapolate', bounds_error=False) - Vaut_Y_tmp = np.array([float(Vaut_fn_tmp(yj)) for yj in Y]) - Qmin_L_tmp = find_Qmin(V_p, Vaut_Y_tmp[0]) + Vaut_Y_tmp = np.array([float(Vaut_fn_tmp(yj)) for yj in Y]) + Qmin_L_tmp = find_Qmin(V_p, Vaut_Y_tmp[0]) ax.plot(Q_grid, pQp[:, 0], ls=ls, color=color, - label=fr'$\beta = {β_val}$ (NR floor $\approx {Qmin_L_tmp:.3f}$)') + label=fr'$\beta = {β_val}${tag} ' + fr'(NR floor $\approx {Qmin_L_tmp:.3f}$)') ax.set_xlabel(r'state $Q$') ax.set_ylabel(r"$Q'_L$ (continuation state after low output)") @@ -797,13 +1517,36 @@ plt.tight_layout() plt.show() ``` -More patient borrowers ($\beta$ closer to 1) value the continuation of the -contract more highly, which relaxes the no-repudiation constraint: the -no-repudiation floor $Q^*_L$ falls and the capital outflow after low output is -less severe. +The figure shows how the continuation state after low output changes with +the borrower's patience. + +For low current states, each curve is almost flat at its no-repudiation +floor. + +That floor falls as $\beta$ rises. + +Thus, in this calibration, a more patient borrower can be assigned a lower +continuation state after low output without choosing repudiation. -Impatient borrowers more readily prefer autarky, tightening the -constraint and worsening debt-crisis dynamics. +Since + +$$ +d_L(Q) = Y_L - Q'_L(Q), +$$ + +a lower $Q'_L$ means a larger repayment after low output. + +Patience therefore lets the contract use a harsher low-output punishment. + +As current resources $Q$ rise, the low-output no-repudiation floor stops +binding. + +The curves then increase with $Q$: after a borrower enters the period with +more resources, the contract can promise a better continuation state even +after low output. + +At high values of $Q$, the schedules become close to one another and flatten +near the upper part of the grid. ```{solution-end} ``` @@ -812,15 +1555,15 @@ constraint and worsening debt-crisis dynamics. :label: atkeson_1991_ex2 ``` -**Signal quality and capital flows.** +*Signal quality and capital flows.* Replace the output distribution with the more symmetric values -$g_h = (0.40, 0.60)$ and $g_l = (0.60, 0.40)$, so that output is a +$g_h = (0.40, 0.60)$ and $g_\ell = (0.60, 0.40)$, so that output is a weaker signal of investment. -1. Recompute the autarky and optimal contract value functions. -2. Plot the net capital outflow curves $d(Y_j) - b'(Q'_j)$ as a function - of $Q$ for both the baseline and the weak-signal specification. +1. Recompute the autarky and Program P* value functions. +2. Plot the low-output net capital outflow curve $d(Y_L) - b'(Q'_L)$ as a + function of $Q$ for both the baseline and the weak-signal specification. 3. Explain intuitively why weaker signal quality changes the capital flow pattern. ```{exercise-end} @@ -830,6 +1573,8 @@ weaker signal of investment. :class: dropdown ``` +Here is one solution: + ```{code-cell} ipython3 --- mystnb: @@ -838,25 +1583,32 @@ mystnb: name: fig-atk-signal --- # Weak-signal specification -g_h_ws = np.array([0.40, 0.60]) -g_l_ws = np.array([0.60, 0.40]) +g_high_ws = np.array([0.40, 0.60]) +g_low_ws = np.array([0.60, 0.40]) -print("Weak-signal likelihood ratios g_l/g_h:", g_l_ws / g_h_ws) +print("Weak-signal likelihood ratios g_low/g_high:", + g_low_ws / g_high_ws) -V_aut_ws = autarky_vfi(g_h_val=g_h_ws, g_l_val=g_l_ws) -V_par_ws, pb_ws, pQp_ws = pareto_vfi( - V_aut_ws, g_h_val=g_h_ws, g_l_val=g_l_ws) +V_aut_ws, I_aut_ws = autarky_vfi(g_high_val=g_high_ws, + g_low_val=g_low_ws, + verbose=False) +V_par_ws, _, pb_ws, pQp_ws = program_p_vfi( + V_aut_ws, I_aut_ws, g_high_val=g_high_ws, + g_low_val=g_low_ws, verbose=False) -pb_fn_ws = interp1d(Q_grid, pb_ws, fill_value='extrapolate', bounds_error=False) +pb_fn_ws = interp1d(Q_grid, pb_ws, + fill_value='extrapolate', bounds_error=False) net_L_ws = (Y_L - pQp_ws[:, 0]) - pb_fn_ws(pQp_ws[:, 0]) -net_H_ws = (Y_H - pQp_ws[:, 1]) - pb_fn_ws(pQp_ws[:, 1]) -pb_fn_bl = interp1d(Q_grid, pol_b, fill_value='extrapolate', bounds_error=False) +pb_fn_bl = interp1d(Q_grid, pol_b, + fill_value='extrapolate', bounds_error=False) net_L_bl = (Y_L - pol_Qp[:, 0]) - pb_fn_bl(pol_Qp[:, 0]) fig, ax = plt.subplots() -ax.plot(Q_grid, net_L_bl, lw=2, label=r'After $Y_L$, baseline (strong signal)') -ax.plot(Q_grid, net_L_ws, lw=2, ls='--', label=r'After $Y_L$, weak signal') +ax.plot(Q_grid, net_L_bl, + lw=2, label=r'After $Y_L$, baseline (strong signal)') +ax.plot(Q_grid, net_L_ws, + lw=2, ls='--', label=r'After $Y_L$, weak signal') ax.axhline(0, color='k', lw=0.8, ls=':') ax.set_xlabel(r'state $Q$') ax.set_ylabel('net capital outflow') @@ -865,77 +1617,18 @@ plt.tight_layout() plt.show() ``` -With a weaker signal ($g_l/g_h$ closer to 1), low output is less informative -about past investment. - -The moral hazard problem is milder, incentive -constraints are easier to satisfy, and the no-repudiation constraint binds less -tightly. - -The net-flow response after bad output realisations is smaller in -magnitude. - -```{solution-end} -``` - -```{exercise-start} -:label: atkeson_1991_ex3 -``` - -**Debt forgiveness and welfare.** - -A debt relief programme can be modelled as an exogenous upward shift in the -no-repudiation threshold: suppose the borrower's outside option improves to -$\tilde{U}_{\text{aut}}(Y_j) = U_{\text{aut}}(Y_j) + \varepsilon$ for a small -$\varepsilon > 0$. - -1. For $\varepsilon \in \{0, 0.05, 0.10\}$, compute the constrained optimal - value function under the tightened repudiation constraint. -2. Plot $\bar{V}(Q)$ for each $\varepsilon$. -3. Discuss: when is debt forgiveness welfare improving for the borrower? - What is the cost to lenders? - -*Hint:* implement the shift by adding $\varepsilon$ to `Vaut_Y` inside -`pareto_bellman`. -```{exercise-end} -``` - -```{solution-start} atkeson_1991_ex3 -:class: dropdown -``` - -```{code-cell} ipython3 ---- -mystnb: - figure: - caption: value functions under debt forgiveness - name: fig-atk-forgiveness ---- -fig, ax = plt.subplots() - -for ε, ls, color in [(0.0, '-', 'C0'), (0.05, '--', 'C1'), (0.10, ':', 'C2')]: - V_ε, _, _ = pareto_vfi(V_aut, ε=ε, max_iter=50) - - ax.plot(Q_grid, V_ε, ls=ls, color=color, - label=fr'$\varepsilon = {ε}$') - -ax.plot(Q_grid, V_aut, lw=1, color='k', ls=':', label='Autarky') -ax.set_xlabel(r'state $Q$') -ax.set_ylabel(r'$\bar{V}(Q)$') -ax.legend() -plt.tight_layout() -plt.show() -``` +The main lesson is that signal quality matters for the capital-flow +mechanism. -Tightening the no-repudiation threshold ($\varepsilon > 0$) shrinks the set of -feasible contracts, reducing $\bar{V}(Q)$. +When low output is a strong signal of low investment, the contract can use +the low-output state aggressively as a punishment, producing a small region +of net capital outflows. -Debt forgiveness improves the -borrower's outside option but makes lenders less willing to extend credit -(smaller loans at higher cost), leaving the borrower worse off in equilibrium. +When the signal is weaker, low output is less informative, so the same +punishment is less useful for incentives. -This illustrates the {cite:t}`BulowRogoff1989b` result that debt forgiveness -need not benefit the borrowing country. +In this calibration, the visible low-output outflow region largely disappears: +after low output, new borrowing usually offsets repayment. ```{solution-end} ``` From d443ec0c05374037ed51551cc2d551ba4361d6ab Mon Sep 17 00:00:00 2001 From: HumphreyYang Date: Thu, 11 Jun 2026 11:31:18 +1000 Subject: [PATCH 14/25] updates --- lectures/_static/quant-econ.bib | 17 ++++++ lectures/atkeson_1991.md | 98 ++++++++++++++++++--------------- 2 files changed, 72 insertions(+), 43 deletions(-) diff --git a/lectures/_static/quant-econ.bib b/lectures/_static/quant-econ.bib index 5349beed..281e3b85 100644 --- a/lectures/_static/quant-econ.bib +++ b/lectures/_static/quant-econ.bib @@ -3168,6 +3168,23 @@ @article{Shimer2005 doi = {10.1257/0002828053828572} } +@book{Shimer2010, + author = {Shimer, Robert}, + title = {Labor Markets and Business Cycles}, + publisher = {Princeton University Press}, + year = {2010}, + series = {CREI Lectures in Macroeconomics} +} + +@techreport{Schmidt2016, + author = {Schmidt, Lawrence D. W.}, + title = {Climbing and Falling Off the Ladder: Asset Pricing + Implications of Labor Market Event Risk}, + institution = {University of California, San Diego}, + type = {Working Paper}, + year = {2016} +} + @article{MankiwReisWolfers2003, author = {Mankiw, N. Gregory and Reis, Ricardo and Wolfers, Justin}, title = {Disagreement about Inflation Expectations}, diff --git a/lectures/atkeson_1991.md b/lectures/atkeson_1991.md index b45ca394..eaa4a92f 100644 --- a/lectures/atkeson_1991.md +++ b/lectures/atkeson_1991.md @@ -43,39 +43,45 @@ Time is discrete, $t = 0, 1, 2, \ldots$. In each period the borrower chooses investment $I_t \geq 0$. -Given investment $I_t$, next period's output -$Y_{t+1}$ is drawn from the conditional distribution +Output next period takes values in a finite, ordered set +$\mathcal{Y} = \{Y_1,\ldots,Y_N\}$ with $0 < Y_1 < \cdots < Y_N$. -$$ -g(Y';\,I) = \lambda(I)\,g_0(Y') + \bigl[1 - \lambda(I)\bigr]\,g_1(Y'), -$$ +The technology is built from two fixed probability distributions on +$\mathcal{Y}$, which we call $g_0$ and $g_1$. -```{prf:assumption} Technology and Output Signals -:label: atkeson_assumption_technology +We think of $g_1$ as the distribution of output when the borrower's effort is +least productive and $g_0$ as the distribution when it is most productive. -The output support is finite and ordered, -$\mathcal{Y} = \{Y_1,\ldots,Y_N\}$, with -$0 < Y_1 < \cdots < Y_N$. +Accordingly $g_0$ places relatively more weight on high outputs than $g_1$ +does. -For every investment level $I$, output has strictly positive probabilities: -$g(Y_i;I)>0$ for all $i$. +Investment does not reshape these two distributions; it only changes how likely +output is to be drawn from one rather than the other. -Investment affects output only through the probability vector -$g(\cdot;I)$. +With probability $\lambda(I)$ output is drawn from the favorable distribution +$g_0$, and with probability $1 - \lambda(I)$ it is drawn from $g_1$, so given +investment $I_t$ next period's output $Y_{t+1}$ has the mixture distribution + +$$ +g(Y';\,I) = \lambda(I)\,g_0(Y') + \bigl[1 - \lambda(I)\bigr]\,g_1(Y'). +$$ -The distribution has the mixture form -$g(Y_i;I)=\lambda(I)g_0(Y_i)+[1-\lambda(I)]g_1(Y_i)$, where $g_0$ and $g_1$ -are probability distributions on $\mathcal{Y}$ and -$\lambda : [0,I_{\max}] \to [0,1]$ is strictly increasing and concave, +The weight $\lambda : [0,I_{\max}] \to [0,1]$ is strictly increasing and +concave, $$ -\lambda'(I)>0, \qquad \lambda''(I)\le 0. +\lambda'(I)>0, \qquad \lambda''(I)\leq 0, $$ -The ratio $g_0(Y_i)/g_1(Y_i)$ is increasing in $i$. -``` +so more investment always raises the chance of the favorable distribution, but +with diminishing returns. -The last condition is a monotone likelihood-ratio condition. +Every output level keeps strictly positive probability at every investment +level, $g(Y_i;I)>0$ for all $i$, so no single realization ever fully reveals how +much the borrower invested. + +Finally, the likelihood ratio $g_0(Y_i)/g_1(Y_i)$ is increasing in $i$, the +monotone likelihood-ratio condition. Since higher investment raises $\lambda(I)$, high output is relatively good news about investment, while low output is relatively strong evidence that @@ -87,7 +93,7 @@ the borrower invested little. discounted utility $$ -v^B(\sigma) = (1 - \beta)\,\mathbb{E}_0^{\sigma} +v(\sigma) = (1 - \beta)\,\mathbb{E}_0^{\sigma} \sum_{t=0}^{\infty} \beta^t \, u(c_t), $$ @@ -104,7 +110,7 @@ The borrower's autarky value is high enough to rule out equilibrium states with arbitrarily low current consumption: $$ -(1-\beta)u(0) + \beta \bar u < v^B_{\text{aut}}(Y_1), +(1-\beta)u(0) + \beta \bar u < v_{\text{aut}}(Y_1), $$ where $\bar u$ is an upper bound for period utility. @@ -124,7 +130,7 @@ $\mathbb{E}_0^{\sigma}$ is the expectation over output histories that this plan induces, evaluated at date $0$. The factor $(1 - \beta)$ normalises lifetime utility to per-period units, so -$v^B$ is comparable to a one-period payoff. +$v$ is comparable to a one-period payoff. **Lenders** are a sequence of short-lived, risk-neutral agents, one born each period. @@ -186,10 +192,10 @@ where $M$ is the lender's endowment per period. The value the borrower can attain without credit access satisfies $$ -v^B_{\text{aut}}(Z) = \max_{0 \le I \le \min\{Z,\, I_{\max}\}} +v_{\text{aut}}(Z) = \max_{0 \leq I \leq \min\{Z,\, I_{\max}\}} \Bigl[ (1-\beta)\,u(Z - I) - + \beta \sum_{Y'} v^B_{\text{aut}}(Y')\,g(Y';\,I) + + \beta \sum_{Y'} v_{\text{aut}}(Y')\,g(Y';\,I) \Bigr]. $$ @@ -220,9 +226,9 @@ consumption-investment plan while keeping the same $b$ and $d$: $$ \begin{aligned} -v^B(\sigma \mid Q^t) +v(\sigma \mid Q^t) &\geq -v^B(\tilde \sigma \mid Q^t), +v(\tilde \sigma \mid Q^t), \\ &\text{for all feasible } \tilde \sigma = \{\tilde c,\, \tilde I,\, b,\, d\}. @@ -266,9 +272,9 @@ have been repaid. An allocation is **immune from repudiation** if $$ -v^B\bigl(\sigma \mid Q^t, Y_{t+1}\bigr) +v\bigl(\sigma \mid Q^t, Y_{t+1}\bigr) \geq -v^B_{\text{aut}}(Y_{t+1}) +v_{\text{aut}}(Y_{t+1}) $$ (eq:atkeson_no_repudiation) for all $t$, histories $Q^t$, and output realizations $Y_{t+1}$. @@ -283,13 +289,13 @@ The planner chooses among allocations that satisfy five restrictions: 1. Feasibility {eq}`eq:atkeson_feasibility` 2. Borrower individual rationality: - $v^B(\sigma \mid Q^t) \geq v^B_{\text{aut}}(Q_t)$ + $v(\sigma \mid Q^t) \geq v_{\text{aut}}(Q_t)$ 3. Lender participation {eq}`eq:atkeson_lender_ir` 4. Immunity from repudiation {eq}`eq:atkeson_no_repudiation` 5. Incentive compatibility {eq}`eq:atkeson_ic` An allocation is **constrained Pareto optimal** if it maximizes the -borrower's initial payoff $v^B(\sigma)$ over this constrained set. +borrower's initial payoff $v(\sigma)$ over this constrained set. Borrower individual rationality is part of the general feasible set. @@ -451,7 +457,7 @@ At the constrained optimum, the expected value of repayments to lenders is nondecreasing in investment: $$ -\sum_{Y'} d'(Y')\,[g_0(Y')-g_1(Y')] \ge 0. +\sum_{Y'} d'(Y')\,[g_0(Y')-g_1(Y')] \geq 0. $$ This is the weak form of Atkeson's repayment-monotonicity condition; a strict @@ -461,7 +467,8 @@ The constrained-optimal investment choice is interior: $I^* \in (0,I_{\max})$. ``` -Together with {prf:ref}`atkeson_assumption_technology`, +Together with the concavity of $\lambda$ and the monotone likelihood-ratio +condition introduced above, {prf:ref}`atkeson_assumption_first_order` justifies replacing the incentive-compatibility condition by the relaxed first-order inequality used in the Lagrangian. @@ -486,7 +493,7 @@ $$ &+ \lambda_f (Q + b - c - I) \\ &+ \lambda_\ell \left[\beta\sum_j d_j g_j(I) - b\right] \\ &+ \beta\sum_j \mu_j g_j(I) - \left[v_j - v^B_{\text{aut}}(Y_j')\right] \\ + \left[v_j - v_{\text{aut}}(Y_j')\right] \\ &+ \eta \left[-(1-\beta)u'(Q+b-I) + \beta\sum_j v_j g_{I,j}\right] \\ @@ -533,7 +540,7 @@ When {eq}`eq:atkeson_vj_foc` requires $\mu_j > 0$, complementary slackness implies that the no-repudiation constraint binds: $$ -\bar v(Y_j' - d_j) = v^B_{\text{aut}}(Y_j'). +\bar v(Y_j' - d_j) = v_{\text{aut}}(Y_j'). $$ Repayment $d_j$ is then at its maximum and the new loan available at the @@ -543,7 +550,7 @@ Thus the borrower has a **capital outflow**: $$ \underbrace{d_j}_{\text{repayment to old lender}} - \geq \underbrace{b' \bigl(Q_j'\bigr)}_{\text{new loan from young lender}}. + \geq \underbrace{b^* \bigl(Q_j'\bigr)}_{\text{new loan from young lender}}. $$ Strict capital outflow requires the inequality to be strict. @@ -577,8 +584,13 @@ import matplotlib.pyplot as plt Let's start by defining the model primitives and the state grid. -The probabilities satisfy the monotone likelihood ratio property: -$g_{\ell}(Y_L)/g_h(Y_L) > 1 > g_{\ell}(Y_H)/g_h(Y_H)$. +In the code the favorable distribution $g_0$ is `g_high`, the output +distribution when investment is at its maximum, and the unfavorable +distribution $g_1$ is `g_low`, the distribution when investment is zero. + +With only two outputs, the monotone likelihood-ratio property +$g_0(Y_i)/g_1(Y_i)$ increasing in $i$ reduces to +$g_0(Y_H)/g_1(Y_H) > 1 > g_0(Y_L)/g_1(Y_L)$. Thus $Y_L$ is evidence of low investment, while $Y_H$ is evidence of high investment. @@ -1215,7 +1227,7 @@ In that region, repayment after bad news about investment is not fully offset by new borrowing, so the borrower exports capital. This is the numerical analogue of Atkeson's capital-outflow condition -$d_j \geq b'(Q'_j)$ for the lowest output realization. +$d_j \geq b^*(Q'_j)$ for the lowest output realization. Outside the shaded region, low output is still punished through a lower continuation state, but that punishment does not show up as a literal net @@ -1558,11 +1570,11 @@ near the upper part of the grid. *Signal quality and capital flows.* Replace the output distribution with the more symmetric values -$g_h = (0.40, 0.60)$ and $g_\ell = (0.60, 0.40)$, so that output is a +$g_0 = (0.40, 0.60)$ and $g_1 = (0.60, 0.40)$, so that output is a weaker signal of investment. 1. Recompute the autarky and Program P* value functions. -2. Plot the low-output net capital outflow curve $d(Y_L) - b'(Q'_L)$ as a +2. Plot the low-output net capital outflow curve $d(Y_L) - b^*(Q'_L)$ as a function of $Q$ for both the baseline and the weak-signal specification. 3. Explain intuitively why weaker signal quality changes the capital flow pattern. From 4be02011a694724340fb24ceb01532a9c7a200d6 Mon Sep 17 00:00:00 2001 From: HumphreyYang Date: Thu, 11 Jun 2026 13:09:37 +1000 Subject: [PATCH 15/25] updates --- .../subjective_beliefs_business_cycles.md | 1033 +++++---- lectures/tsyrennikov_2013.md | 2039 ++++++++++++----- 2 files changed, 2105 insertions(+), 967 deletions(-) diff --git a/lectures/subjective_beliefs_business_cycles.md b/lectures/subjective_beliefs_business_cycles.md index 1b26f28b..3c03d3ec 100644 --- a/lectures/subjective_beliefs_business_cycles.md +++ b/lectures/subjective_beliefs_business_cycles.md @@ -12,7 +12,7 @@ kernelspec: --- (subjective_beliefs_bc)= -# Survey Data and Subjective Beliefs in Business Cycles +# Survey Data and Subjective Beliefs in Business Cycle Models ```{index} single: Subjective Beliefs; Business Cycles ``` @@ -20,13 +20,13 @@ kernelspec: ## Overview This lecture presents key ideas from {cite}`bhandari2025survey`, who study -whether household survey data on macroeconomic expectations can shed light on -business cycle dynamics. +whether household survey data on macroeconomic expectations can discipline +business cycle models. Their central finding is that household forecasts of unemployment and inflation -exhibit **systematic upward biases** relative to professional forecasters and -model-consistent rational expectations. These biases — which the authors call -*belief wedges* — are: +exhibit **systematic upward biases** relative to rational forecasts. + +These biases -- which the authors call *belief wedges* -- are: * **Persistent and countercyclical**: they are larger during recessions. * **Positively correlated**: optimism/pessimism about unemployment and inflation @@ -34,19 +34,21 @@ model-consistent rational expectations. These biases — which the authors call * **One-factor in structure**: a single latent state accounts for most variation across wedges. -The paper interprets this evidence through the lens of -**robust preferences** ({cite}`HansenSargent2001`; {cite}`HansenSargent2008`). +The paper represents this evidence through the lens of +**robust preferences** ({cite:t}`HansenSargent2001`; {cite:t}`HansenSargent2008`). -A household that fears model misspecification behaves as if it tilts -probabilities toward bad outcomes. +The robust preference serves as a model of pessimism and optimism: +agents act as if they overweight states that deliver low continuation values +(pessimism) and underweight those that deliver high continuation values +(optimism). When calibrated to the Michigan Survey of -Consumers (1982Q1–2019Q4), this mechanism yields a time-varying *belief shock* -that substantially reduces the well-known **unemployment volatility puzzle** — +Consumers (1982Q1-2019Q4), this mechanism yields a time-varying *belief shock* +that substantially reduces the well-known **unemployment volatility puzzle** --- the fact that standard New Keynesian models with only technology and monetary policy shocks generate far too little unemployment volatility. -By the end of this lecture you will understand: +In this lecture, we will cover: * How to define and measure belief wedges from household survey data. * How robust preferences generate time-varying subjective beliefs. @@ -54,10 +56,11 @@ By the end of this lecture you will understand: * Why a calibrated belief shock helps resolve the unemployment volatility puzzle. -## Setup +We start with the following imports ```{code-cell} ipython3 import datetime +from typing import NamedTuple import numpy as np import matplotlib.pyplot as plt @@ -73,8 +76,7 @@ Let $E_t[\cdot]$ denote expectations under the **data-generating** (objective) probability measure and $\tilde{E}_t[\cdot]$ denote **subjective** (survey) expectations. -For any scalar variable $z_{t+1}$, the **one-period belief -wedge** is +For any scalar variable $z_{t+1}$, the **one-period belief wedge** is $$ @@ -82,18 +84,30 @@ $$ $$ -A positive wedge means households are more pessimistic than the model predicts: -they expect $z_{t+1}$ to be higher than the model-consistent forecast. +A positive wedge means households expect $z_{t+1}$ to be higher than the +data-generating forecast. -For -unemployment and inflation this sign convention implies an upward bias. +For unemployment and inflation, this sign convention corresponds to an upward +forecast bias. -In practice, {cite}`BhandariBorovickaHo2024` measure -$\tilde{E}_t[\cdot]$ from the Michigan Survey of Consumers, and -$E_t[\cdot]$ from a benchmark DSGE model estimated on the same data. +The empirical objects in {cite}`bhandari2025survey` are mostly +one-year-ahead, or four-quarter, wedges. -The -discrepancy is the wedge. +We introduce the one-period wedge because it is the cleanest way to explain the +theory; the appendix below shows the multi-period version. + +In the data, $\tilde{E}_t[\cdot]$ is measured from the Michigan Survey of +Consumers. + +The benchmark $E_t[\cdot]$ is computed from a quarterly VAR, with Survey of +Professional Forecasters (SPF) forecasts used as an important robustness check. + +In the structural model, the same object is interpreted as the difference +between subjective and data-generating expectations. + +The raw Michigan unemployment question is categorical, so the paper converts it +into a quantitative forecast using the Carlson--Parkin procedure as adapted by +{cite}`MankiwReisWolfers2003`. ### Empirical facts @@ -102,7 +116,7 @@ Using data from 1982Q1 to 2019Q4, the authors document: | Statistic | Unemployment wedge | Inflation wedge | |---|---|---| | Mean | 0.52 pp | 1.22 pp | -| Standard deviation | 0.67 pp | 1.03 pp | +| Standard deviation | 0.57 pp | 0.97 pp | | Correlation with output gap | −0.49 | −0.30 | Both wedges are **positive on average** (households are pessimistic) and @@ -110,29 +124,38 @@ Both wedges are **positive on average** (households are pessimistic) and Moreover, the first principal component of the joint wedge series explains **78.6%** of its -variation — a striking one-factor structure. +variation --- a striking one-factor structure. -The following code simulates artificial wedge series that match these -moments, so we can visualise the key stylised facts before turning to theory. +The same one-factor pattern appears in the cross section. -```{code-cell} ipython3 -# --------------------------------------------------------------------------- -# Simulate stylised belief-wedge time series calibrated to match the -# empirical moments in Bhandari, Borovicka, Ho (2025). -# --------------------------------------------------------------------------- +In the Michigan Survey, households with high inflation forecasts are also more +likely to expect unemployment to rise and to report worse economic conditions. + +Similar patterns appear across demographic groups and in the FRBNY Survey of +Consumer Expectations (SCE). + +This evidence supports the interpretation that the wedges reflect a common +pessimism/optimism component rather than two unrelated forecast mistakes. -# Calibrated parameters (Table 1 of the paper) +The following code simulates a stylized one-factor wedge process. + +It matches the mean loadings implied by the calibration but abstracts from +measurement error and other idiosyncratic components, so the simulated wedges +are perfectly correlated. + +```{code-cell} ipython3 +# Parameters from Table 1 μ_θ = 5.64 # mean of belief-shock parameter θ ρ_θ = 0.714 # AR(1) persistence of θ -σ_θ = 4.3 # innovation volatility of θ (units of θ) +σ_θ = 4.3 # innovation volatility -# Wedge loadings: Δᵤ = cᵤ θ, Δπ = cπ θ (c chosen to match the means) -c_u = 0.52 / μ_θ # ≈ 0.0922 pp per unit of θ -c_π = 1.22 / μ_θ # ≈ 0.2163 pp per unit of θ +# Wedge loadings match the empirical means. +c_u = 0.52 / μ_θ +c_π = 1.22 / μ_θ -T = 152 # 38 years × 4 quarters +T = 152 # 38 years * 4 quarters -# Simulate the belief-shock AR(1) +# Simulate the belief shock. rng = np.random.default_rng(42) θ = np.zeros(T) θ[0] = μ_θ @@ -141,11 +164,10 @@ for t in range(1, T): + ρ_θ * θ[t-1] + σ_θ * rng.standard_normal()) -# Belief wedges (in percentage points) wedge_u = c_u * θ wedge_π = c_π * θ -# Generate quarters 1982Q1 – 2019Q4 +# Quarterly dates, 1982Q1-2019Q4 quarters = [datetime.date(1982 + (q // 4), 3 * (q % 4) + 1, 1) for q in range(T)] ``` @@ -188,7 +210,6 @@ mystnb: caption: one-factor structure of belief wedges name: fig-sbbc-wedge-scatter --- -# Show the one-factor structure: scatter of unemployment vs inflation wedge fig, ax = plt.subplots() sc = ax.scatter(wedge_u, wedge_π, c=range(T), cmap='RdYlGn_r', alpha=0.7, s=20) @@ -202,10 +223,22 @@ plt.tight_layout() plt.show() ``` -The scatter plot reveals the strong positive correlation between the two -wedges. +The first figure plots the simulated unemployment wedge in the top panel and +the simulated inflation wedge in the bottom panel. + +The dashed horizontal lines show the sample means. + +Both wedges rise and fall together because each is a fixed loading on the same +belief shock $\theta_t$. -Both series are high when the belief shock $\theta_t$ is high, and low otherwise. +The scatter plot makes this one-factor structure even clearer. + +Each point is one quarter, with the horizontal coordinate equal to the +unemployment wedge and the vertical coordinate equal to the inflation wedge. + +The points lie on an upward-sloping line because high-pessimism quarters have +large wedges for both variables, while low-pessimism quarters have small wedges +for both variables. This is the one-factor structure that motivates the theoretical framework. @@ -219,7 +252,7 @@ Why would households have systematically biased beliefs? One disciplined answer comes from **robust control** or **multiplier preferences** ({cite}`HansenSargent2001`, {cite}`HansenSargent2008`). -An agent who fears that her reference model may be misspecified solves +An agent represented by multiplier preferences solves $$ @@ -236,9 +269,14 @@ Here $m_{t+1}$ is a **likelihood ratio** (Radon–Nikodym derivative) that distorts the reference measure, and the last term is an entropy penalty that keeps the distortion from being too extreme. -The scalar $\theta_t > 0$ -controls the *degree* of concern for misspecification: larger $\theta_t$ means -more pessimism. +The scalar $\theta_t$ controls the direction and strength of the belief tilt. + +The minimisation problem above corresponds to $\theta_t > 0$: larger +$\theta_t$ means more pessimism. + +The paper also allows optimism ($\theta_t < 0$), in which case the analogous +inner problem is a maximisation that tilts probability toward +high-continuation-value states. The inner minimisation has a closed-form solution: @@ -274,7 +312,7 @@ ratio and the variable of interest. When $V_{t+1}$ is high in states where $z_{t+1}$ is also high, $m_{t+1}^*$ will be low in those states, making the -covariance negative — i.e.\ the agent *underestimates* good-state variables. +covariance negative --- i.e.\ the agent *underestimates* good-state variables. For unemployment (which varies inversely with good economic outcomes), the wedge is positive: pessimists expect higher unemployment than the model predicts. @@ -321,94 +359,116 @@ $$ $$ When $V_x > 0$ (good consumption state is good) and $\theta_t > 0$ -(pessimism), the wedge is negative — the agent *underestimates* +(pessimism), the wedge is negative --- the agent *underestimates* consumption growth. For unemployment (enter with a negative sign in the value function), the same pessimism generates a **positive** wedge. +We now turn this illustration into code, building it up from small pieces. + +The first ingredient is the slope $V_x$ of the continuation value. + +It solves the scalar Riccati equation, which we write as a quadratic +$a V_x^2 + b V_x + c = 0$ and solve with the quadratic formula. + +We keep the root that collapses to the rational-expectations value +$V_x^{RE} = u_x / (1 - \beta\rho_x)$ as the pessimism parameter $\mu_\theta \to 0$. + ```{code-cell} ipython3 -# --------------------------------------------------------------------------- -# Illustrate the optimal belief distortion in the simple endowment economy. -# --------------------------------------------------------------------------- +def solve_Vx(β, ρ_x, σ_x, μ_θ): + """ + Solve the scalar Riccati equation for the value-function slope Vx: -class BeliefDistortionModel: + Vx = u_x - (β/2) μ_θ σ_x**2 Vx**2 + β ρ_x Vx, with u_x = 1 - β. """ - Simple scalar AR(1) endowment economy illustrating the robust-preference - mechanism from Bhandari, Borovicka, Ho (2024). + u_x = 1.0 - β # marginal utility of log consumption + Vx_re = u_x / (1.0 - β * ρ_x) # rational-expectations (θ = 0) value - State dynamics: x_{t+1} = ρ_x * x_t + σ_x * w_{t+1} - Period utility: u(x_t) = (1 - β) * x_t [log utility] - Continuation value (linearised): V_t = Vx * x_t + Vq + # Coefficients of a Vx**2 + b Vx + c = 0 + a = 0.5 * β * σ_x**2 * μ_θ + b = 1.0 - β * ρ_x + c = -u_x - Under the distorted measure the shock innovation has mean - ν_t = -θ_t * Vx * σ_x - which produces the belief wedge - Δ_t^(1)(x) = σ_x * ν_t = -θ_t * Vx * σ_x^2. - """ + if abs(a) < 1e-14: # no pessimism: equation is linear + return Vx_re + + disc = b**2 - 4.0 * a * c + if disc < 0: # no real root: fall back to RE + return Vx_re + + # Keep the root closest to the rational-expectations value + r1 = (-b + np.sqrt(disc)) / (2.0 * a) + r2 = (-b - np.sqrt(disc)) / (2.0 * a) + return r1 if abs(r1 - Vx_re) < abs(r2 - Vx_re) else r2 +``` + +We store the primitives in a `NamedTuple`, together with the solved slope +$V_x$, and use `create_belief_model` to build an instance. + +```{code-cell} ipython3 +class BeliefModel(NamedTuple): + β: float # discount factor + ρ_x: float # persistence of log consumption + σ_x: float # volatility of the consumption innovation + μ_θ: float # mean of the belief-shock parameter θ + ρ_θ: float # AR(1) persistence of θ + σ_θ: float # volatility of the θ innovation + Vx: float # slope of the linearised continuation value + + +def create_belief_model(β=0.994, ρ_x=0.85, σ_x=0.005, + μ_θ=5.64, ρ_θ=0.714, σ_θ=4.3): + """Build a belief model, solving the Riccati equation for Vx.""" + Vx = solve_Vx(β, ρ_x, σ_x, μ_θ) + return BeliefModel(β=β, ρ_x=ρ_x, σ_x=σ_x, + μ_θ=μ_θ, ρ_θ=ρ_θ, σ_θ=σ_θ, Vx=Vx) +``` + +Two functions map a value of $\theta_t$ into the implied distortion. + +The drift $\nu_t = -\theta_t V_x \sigma_x$ is the mean shift of the shock under +the subjective measure; the wedge $\Delta_t^{(1)}(x) = \sigma_x \nu_t$ is the +resulting forecast bias for the state. + +```{code-cell} ipython3 +def belief_drift(model, θ): + """Mean shift of the shock under subjective beliefs: ν = -θ Vx σ_x.""" + return -θ * model.Vx * model.σ_x - def __init__(self, β=0.994, ρ_x=0.85, σ_x=0.005, - μ_θ=5.64, ρ_θ=0.714, σ_θ=4.3): - self.β = β - self.ρ_x = ρ_x - self.σ_x = σ_x - self.μ_θ = μ_θ - self.ρ_θ = ρ_θ - self.σ_θ = σ_θ - self.Vx = self._solve_Vx() - - def _solve_Vx(self): - """Solve the scalar Riccati equation for Vx.""" - u_x = 1.0 - self.β # marginal utility of log consumption - - a = (self.β / 2.0) * self.σ_x**2 * self.μ_θ - b = -(1.0 - self.β * self.ρ_x) - c = u_x - - # Rational-expectations (θ=0) solution - Vx_re = u_x / (1.0 - self.β * self.ρ_x) - - if abs(a) < 1e-14: # essentially no pessimism - return Vx_re - - disc = b**2 - 4.0 * a * c - if disc < 0: - return Vx_re # fall back to RE if no real root - - r1 = (-b + np.sqrt(disc)) / (2.0 * a) - r2 = (-b - np.sqrt(disc)) / (2.0 * a) - return r1 if abs(r1 - Vx_re) < abs(r2 - Vx_re) else r2 - - def belief_drift(self, θ): - """Mean shift under subjective beliefs.""" - return -θ * self.Vx * self.σ_x - - def belief_wedge(self, θ): - """One-period belief wedge for the state.""" - return self.σ_x * self.belief_drift(θ) - - def simulate_θ(self, T=200, seed=42): - """Simulate the AR(1) belief-shock process.""" - rng = np.random.default_rng(seed) - θ = np.zeros(T) - θ[0] = self.μ_θ - for t in range(1, T): - θ[t] = ((1 - self.ρ_θ) * self.μ_θ - + self.ρ_θ * θ[t - 1] - + self.σ_θ * rng.standard_normal()) - return θ - - def simulate(self, T=200, seed=42): - """Simulate belief wedge time series.""" - θ = self.simulate_θ(T, seed) - return θ, self.belief_wedge(θ) - - -model = BeliefDistortionModel() -print(f"RE value of Vx: {(1-model.β)/(1-model.β*model.ρ_x):.4f}") + +def belief_wedge(model, θ): + """One-period belief wedge for the state: Δ = σ_x ν = -θ Vx σ_x**2.""" + return model.σ_x * belief_drift(model, θ) +``` + +A last helper simulates the AR(1) belief shock $\theta_t$. + +```{code-cell} ipython3 +def simulate_θ(model, T=200, seed=42): + """Simulate the AR(1) belief shock θ_t.""" + rng = np.random.default_rng(seed) + θ = np.zeros(T) + θ[0] = model.μ_θ + for t in range(1, T): + θ[t] = ((1 - model.ρ_θ) * model.μ_θ + + model.ρ_θ * θ[t-1] + + model.σ_θ * rng.standard_normal()) + return θ +``` + +Building the model at the baseline calibration, we compare the robust slope +$V_x$ with its rational-expectations counterpart, and report the mean belief +drift and wedge. + +```{code-cell} ipython3 +model = create_belief_model() + +Vx_re = (1 - model.β) / (1 - model.β * model.ρ_x) +print(f"RE value of Vx: {Vx_re:.4f}") print(f"Robust value of Vx: {model.Vx:.4f}") -print(f"Belief drift at θ̄: {model.belief_drift(model.μ_θ)*100:.4f} pp") -print(f"Belief wedge at θ̄: {model.belief_wedge(model.μ_θ)*100:.4f} pp") +print(f"Belief drift at θ_bar: {belief_drift(model, model.μ_θ) * 100:.4f} pp") +print(f"Belief wedge at θ_bar: {belief_wedge(model, model.μ_θ) * 100:.4f} pp") ``` ```{code-cell} ipython3 @@ -418,17 +478,15 @@ mystnb: caption: objective and subjective shock distributions name: fig-sbbc-shock-distributions --- -# Compare the objective (N(0,1)) and subjective shock distributions. -# The actual drift ν = -θ * Vx * σ_x is tiny on a unit-shock axis. -# We plot the standardised drift ν / σ_x instead. +# Scale the tiny drift by σ_x for visibility. θ_vals = [0, model.μ_θ, 2 * model.μ_θ] labels = ['θ = 0 (rational)', - f'θ = θ̄ = {model.μ_θ:.1f} (mean)', - f'θ = 2θ̄ (pessimistic)'] + f'θ = θ_bar = {model.μ_θ:.1f} (mean)', + f'θ = 2θ_bar (pessimistic)'] colors = ['black', 'steelblue', 'firebrick'] -# ν_tilde = ν / σ_x = -θ * Vx. +# Drift in units of σ_x ν_tilde = [-θ * model.Vx for θ in θ_vals] x_grid = np.linspace(-4, 4, 500) @@ -440,8 +498,8 @@ for μ, label, color in zip(ν_tilde, labels, colors): ax.axvline(0, color='grey', linestyle=':', linewidth=0.8) ax.set_xlabel( - 'standardised innovation $(w_{t+1} - \\nu_t)$ ' - 'with $\\nu_t = -\\theta_t V_x \\sigma_x$' + 'innovation shift in units of $\\sigma_x$: ' + '$\\nu_t / \\sigma_x = -\\theta_t V_x$' ) ax.set_ylabel('density') ax.legend() @@ -450,12 +508,20 @@ plt.show() print("Mean shifts (in units of σ_x):") for μ, label in zip(ν_tilde, labels): - print(f" {label:35s} ν̃ = {μ:.4f}") + print(f" {label:35s} ν_tilde = {μ:.4f}") ``` The figure shows how pessimism (higher $\theta_t$) shifts the perceived distribution of future shocks to the left. +The black curve is the objective distribution, centered at zero. + +The blue and red curves are subjective distributions for progressively larger +values of $\theta_t$. + +The horizontal axis measures the shift in units of $\sigma_x$, so the leftward +movement is the normalized subjective drift $\nu_t / \sigma_x$. + An agent with $\theta_t > 0$ believes bad shocks are more likely than they actually are. @@ -476,20 +542,22 @@ w_{t+1} \sim N(0, I_k). $$ +Write the local scalar belief factor as +$\vartheta_t = \bar\theta(\bar{x} + x_{1t})$. + Under the optimal belief distortion the shocks are re-centred: $$ -w_{t+1} \;\sim\; N\!\left(- \theta_t (\bar{x} + x_{1t}) - (V_x \psi_w)',\; I_k\right), +w_{t+1} \;\sim\; N\!\left(- \vartheta_t (V_x \psi_w)',\; I_k\right), $$ where $V_x$ is the row vector of first derivatives of the continuation value and $\bar{x}$ is the non-stochastic steady state. -The perturbation is exact -to first order. +This perturbation preserves nontrivial first-order effects of belief +distortions. The resulting **belief wedge** for any variable $z$ with model-consistent expected value $\bar{z}' x$ is @@ -497,7 +565,7 @@ expected value $\bar{z}' x$ is $$ \Delta_t^{(1)}(z) -\;=\; -\theta_t (\bar{x} + x_{1t})\, \bar{z}' (\psi_w \psi_w') V_x'. +\;=\; -\vartheta_t\, \bar{z}' (\psi_w \psi_w') V_x'. $$ @@ -520,8 +588,8 @@ penalty on beliefs and vanishes under rational expectations ($\bar\theta = 0$). ### One-factor structure An important consequence of the formula for $\Delta_t^{(1)}(z)$ is that the -*time variation* in all belief wedges is driven by the **single scalar** -$\theta_t$. +*time variation* in all belief wedges is driven by the **single scalar** belief +factor $\vartheta_t$. The cross-sectional loadings $\bar{z}'(\psi_w\psi_w')V_x'$ are fixed by the model's structural parameters. @@ -537,15 +605,9 @@ mystnb: caption: wedge loadings implied by one factor name: fig-sbbc-one-factor-loadings --- -# --------------------------------------------------------------------------- -# Demonstrate the one-factor structure by computing wedges for two -# different variables as θ varies, holding structural parameters fixed. -# --------------------------------------------------------------------------- - θ_grid = np.linspace(0, 20, 200) -# Loading vector, proportional to bar_z' * ψ_w * ψ_w' * Vx'. -# Calibrated so that at mean θ the steady-state wedges match the data. +# Loadings match the mean empirical wedges. loading_u = c_u # 0.52 / 5.64 pp per unit of θ (unemployment) loading_π = c_π # 1.22 / 5.64 pp per unit of θ (inflation) @@ -564,8 +626,7 @@ axes[0].set_xlabel('belief-shock level $\\theta$') axes[0].set_ylabel('belief wedge (pp)') axes[0].legend() -# Scatter of (wedge_u, wedge_π) with θ as the driver. -θ_sim = model.simulate_θ(T=400, seed=7) +θ_sim = simulate_θ(model, T=400, seed=7) wu_sim = loading_u * θ_sim w_π_sim = loading_π * θ_sim axes[1].scatter(wu_sim, w_π_sim, c=θ_sim, cmap='Blues', alpha=0.6, s=12) @@ -576,6 +637,21 @@ plt.tight_layout() plt.show() ``` +The left panel plots the two one-period wedge formulas as functions of the +belief shock. + +Both lines slope upward, but the inflation line is steeper because the +calibration assigns inflation a larger loading on $\theta_t$. + +The vertical dashed line marks the average value $\bar{\theta}$, where the +lines match the empirical mean wedges of 0.52 and 1.22 percentage points. + +The right panel simulates $\theta_t$ and plots the resulting unemployment and +inflation wedges against each other. + +Since both are driven by the same scalar state, the simulated points trace out +an almost exact positive relation. + ## A New Keynesian model with belief distortions ### Model description @@ -587,17 +663,23 @@ New Keynesian model with a **search-and-matching** labour market The key components are: -**Households** — Have log utility in consumption and disutility of hours. -They apply robust preferences (indexed by $\theta_t$) when forming -subjective forecasts. +**Households** --- A representative household has log utility in consumption and +fully shares consumption risk across its employed and unemployed members. + +Employed members earn the wage, unemployed members receive a benefit flow $D$, +and the household applies robust preferences (indexed by $\theta_t$) when +forming subjective forecasts. -**Firms** — Post vacancies and match with workers. Calvo-style price -stickiness (parameter $\chi_p$) and wage stickiness ($\chi_w$) generate -standard New Keynesian Phillips curves. +**Firms** --- Labour-market firms post vacancies and match with searching workers, +while monopolistic intermediate-goods producers reset prices subject to Calvo +frictions (parameter $\chi_p$), generating a New Keynesian Phillips curve. -**Monetary policy** — A Taylor rule that reacts to inflation and the output gap. +Wages adjust sluggishly through a partial-adjustment rule (parameter $\chi_w$) +following {cite}`Shimer2010`. -**Exogenous shocks** — Three shocks drive the model: +**Monetary policy** --- A Taylor rule that reacts to inflation and the output gap. + +**Exogenous shocks** --- Three shocks drive the model: 1. **Belief shock** $\theta_t$: an AR(1) capturing time-varying pessimism. 2. **TFP shock** $a_t$: standard technology shock. @@ -610,23 +692,33 @@ The model is calibrated to quarterly U.S. data, 1982Q1–2019Q4. | Parameter | Symbol | Value | Description | |---|---|---|---| | Discount factor | $\beta$ | 0.994 | Quarterly | -| Elast. of substitution | $\varepsilon$ | 6 | Price markup | +| Elast. of substitution | $\varepsilon$ | 6 | Across intermediate goods | | Price stickiness | $\chi_p$ | 0.75 | Calvo parameter | -| Wage stickiness | $\chi_w$ | 0.925 | Calvo parameter | +| Wage rigidity | $\chi_w$ | 0.925 | Partial adjustment | +| Steady-state markup | $\lambda$ | 1.2 | | +| Policy-rule smoothing | $\rho_r$ | 0.84 | | +| Taylor-rule inflation loading | $r_\pi$ | 1.60 | | +| Taylor-rule output loading | $r_y$ | 0.028 | | | Mean pessimism | $\mu_\theta$ | 5.64 | | | Persistence of $\theta$ | $\rho_\theta$ | 0.714 | | | Volatility of $\theta$ shock | $\sigma_\theta$ | 4.3 | | | TFP persistence | $\rho_a$ | 0.840 | | | TFP volatility | $100\sigma_a$ | 0.568% | | | MP volatility | $100\sigma_r$ | 0.078% | | -| Matching elasticity | $\eta$ | 0.72 | Hosios condition | -| Worker bargaining | $\mu$ | 0.67 | | -| Job-separation rate | $\rho$ | 0.89 | Quarterly | +| Job survival probability | $\rho$ | 0.89 | Separation rate $1-\rho=0.11$ | +| Matching efficiency | $\mu$ | 0.67 | | +| Matching-function curvature | $\nu$ | 0.72 | From {cite}`Shimer2005` | +| Worker bargaining weight | $\eta$ | 0.72 | From {cite}`Shimer2005` | +| Vacancy posting cost | $\kappa_v$ | 0.09 | | +| Unemployment benefit flow | $D$ | 0.57 | | + +### Pedagogical reduced-form representation -### Simplified reduced-form representation +The paper solves a structural New Keynesian model. -We capture the model's linearised solution through a reduced-form -vector autoregression +For computation in this lecture, we use a small reduced-form vector +autoregression that preserves the main qualitative channels and keeps the code +transparent: $$ @@ -638,126 +730,130 @@ where $s_t = (u_t, \pi_t, y_t, \theta_t, a_t)'$ collects unemployment, inflation, output, the belief shock, and TFP, and $\epsilon_{t+1} \sim N(0, I_3)$ contains the three structural shocks. -The coefficient matrices $A$ and $B$ are calibrated so that the -impulse-response functions reproduce the key moments reported in Table 2 and -Figure 7 of {cite}`bhandari2025survey`. +The coefficient matrices $A$ and $B$ are not the paper's structural solution. + +They are chosen to give Figure 7-style impulse responses and Table 2-scale +unconditional volatilities. + +We index the five state variables with named constants, so that later code can +refer to, say, the belief shock as `I_THETA` rather than a bare number. ```{code-cell} ipython3 -class ReducedFormNKModel: +# Position of each variable in the state vector s_t +I_U, I_PI, I_Y, I_THETA, I_A = 0, 1, 2, 3, 4 +``` + +The factory `create_nk_model` builds the transition matrix $A$ and the shock +loadings $B$ from the exogenous-process parameters in Table 1. + +The belief shock $\theta_t$ and TFP $a_t$ follow AR(1) processes; the +endogenous variables inherit their own persistence and load on $\theta_t$, +$a_t$, and the monetary policy shock. + +```{code-cell} ipython3 +class NKModel(NamedTuple): + A: np.ndarray # state transition matrix + B: np.ndarray # shock loadings (columns: w_θ, w_a, w_r) + c_u: float # loading of the unemployment wedge on θ + c_π: float # loading of the inflation wedge on θ + + +def create_nk_model(): + """Build the pedagogical reduced-form NK model (state and shock matrices).""" + # Exogenous-process parameters from Table 1 + ρ_θ, σ_θ = 0.714, 4.3 + ρ_a, σ_a = 0.840, 0.00568 + + # Belief-wedge loadings on θ (match the mean empirical wedges) + c_u = 0.52 / 5.64 + c_π = 1.22 / 5.64 + + # Impact of the belief shock θ on the endogenous variables (per unit of θ) + φ_u_θ = 0.00648 / σ_θ + φ_π_θ = 0.00063 / σ_θ + φ_y_θ = -0.00807 / σ_θ + + # Impact of TFP on the endogenous variables + φ_u_a, φ_π_a, φ_y_a = -0.362, -0.1306, 1.0236 + + # Endogenous persistence (quarterly) + ρ_u, ρ_π, ρ_y = 0.35, 0.50, 0.35 + + A = np.array([ + [ρ_u, 0, 0, φ_u_θ, φ_u_a], # unemployment + [0, ρ_π, 0, φ_π_θ, φ_π_a], # inflation + [0, 0, ρ_y, φ_y_θ, φ_y_a], # output + [0, 0, 0, ρ_θ, 0 ], # belief shock + [0, 0, 0, 0, ρ_a ], # TFP + ]) + + # Columns: [w_θ, w_a, w_r] + B = np.array([ + [0, 0, 0.5e-3], # MP -> unemployment + [0, 0, -0.1e-3], # MP -> inflation + [0, 0, -0.5e-3], # MP -> output + [σ_θ, 0, 0 ], # θ innovation + [0, σ_a, 0 ], # TFP innovation + ]) + return NKModel(A=A, B=B, c_u=c_u, c_π=c_π) +``` + +Impulse responses are computed by iterating $s_{t+1} = A s_t$ from the impact +column of $B$, and the two belief wedges are read off as $c_u \theta_t$ and +$c_\pi \theta_t$. + +```{code-cell} ipython3 +def irf(model, shock_idx, T=25): """ - Reduced-form linear model calibrated to Bhandari, Borovicka, Ho (2024). + Impulse responses to a one-standard-deviation shock. - State vector s_t = [u_t, π_t, y_t, θ_t, a_t] - Shocks: ε = [w_θ, w_a, w_r] + shock_idx : 0 = belief shock, 1 = TFP shock, 2 = monetary policy shock. - Belief wedges: - Δ_u = c_u * θ_t - Δ_π = c_π * θ_t + Returns the state responses together with the unemployment and inflation + wedge responses. """ + A, B = model.A, model.B + resp = np.zeros((A.shape[0], T)) + s = B[:, shock_idx].copy() # impact response + for t in range(T): + resp[:, t] = s + s = A @ s + + wu = model.c_u * resp[I_THETA, :] + w_π = model.c_π * resp[I_THETA, :] + return resp, wu, w_π +``` + +For the unconditional moments we simulate the model and, separately, solve the +discrete Lyapunov equation $\Sigma = A\Sigma A' + BB'$ for the stationary +covariance. - # Index map for the state vector - I_U, I_PI, I_Y, I_THETA, I_A = 0, 1, 2, 3, 4 - - def __init__(self): - # ---- exogenous-process parameters (Table 1) ---- - self.ρ_θ = 0.714 - self.σ_θ = 4.3 - self.ρ_a = 0.840 - self.σ_a = 0.00568 - self.σ_r = 0.00078 - - # ---- wedge loadings on θ ---- - self.c_u = 0.52 / 5.64 - self.c_π = 1.22 / 5.64 - - # ---- calibrated impact effects ---- - # State variables are stored in FRACTIONS (e.g. u=0.06 for 6%). - # Display code converts: *100 for u,y and *400 for π. - # - # Belief shock targets from Figure 7. - φ_u_θ = 0.009 / self.σ_θ - φ_π_θ = 0.000875 / self.σ_θ - φ_y_θ = -0.009 / self.σ_θ - - # TFP shock targets from Figure 7. - φ_u_a = -0.40 - φ_π_a = -0.10 - φ_y_a = 1.20 - - # Persistence of endogenous variables (quarterly, reduced-form) - ρ_u = 0.35 - ρ_π = 0.50 - ρ_y = 0.35 - - # ---- state transition matrix ---- - self.A = np.array([ - [ρ_u, 0, 0, φ_u_θ, φ_u_a ], # unemployment - [0, ρ_π, 0, φ_π_θ, φ_π_a], # inflation - [0, 0, ρ_y, φ_y_θ, φ_y_a ], # output - [0, 0, 0, self.ρ_θ, 0 ], # belief shock - [0, 0, 0, 0, self.ρ_a], # TFP - ]) - - # ---- shock loading matrix ---- - # Columns: [w_θ, w_a, w_r]. All entries in fraction units. - self.B = np.array([ - [0, 0, 0.5e-3 ], # MP → u fraction - [0, 0, -0.1e-3 ], # MP → pi fraction - [0, 0, -0.5e-3 ], # MP → y fraction - [self.σ_θ, 0, 0 ], # θ innovation - [0, self.σ_a, 0 ], # TFP innovation - ]) - - def irf(self, shock_idx, T=25): - """ - Impulse-response function for a one-std-dev shock. - - Parameters - ---------- - shock_idx : int - 0 = belief shock, 1 = TFP shock, 2 = monetary policy shock - T : int - Number of periods - - Returns - ------- - resp : ndarray (5, T) responses of state vector - wu : ndarray (T,) unemployment wedge response - wpi : ndarray (T,) inflation wedge response - """ - n = self.A.shape[0] - resp = np.zeros((n, T)) - s = self.B[:, shock_idx].copy() # impact response - - for t in range(T): - resp[:, t] = s - s = self.A @ s - - wu = self.c_u * resp[self.I_THETA, :] - w_π = self.c_π * resp[self.I_THETA, :] - return resp, wu, w_π - - def simulate(self, T=200, seed=42): - """Simulate the model for T periods.""" - rng = np.random.default_rng(seed) - k = self.B.shape[1] - s = np.zeros((self.A.shape[0], T)) - for t in range(1, T): - s[:, t] = self.A @ s[:, t-1] + self.B @ rng.standard_normal(k) - return s - - def unconditional_stds(self, include_θ_shock=True): - """ - Unconditional standard deviations computed from the Lyapunov equation. - """ - B_use = self.B.copy() - if not include_θ_shock: - B_use[:, 0] = 0.0 # zero out the belief shock - Σ = solve_discrete_lyapunov(self.A, B_use @ B_use.T) - return np.sqrt(np.diag(Σ)) - - -nk = ReducedFormNKModel() +Passing `include_θ_shock=False` zeros out the belief shock, which isolates the +contribution of the TFP and monetary policy shocks. + +```{code-cell} ipython3 +def simulate_nk(model, T=200, seed=42): + """Simulate the model for T periods under the data-generating measure.""" + rng = np.random.default_rng(seed) + A, B = model.A, model.B + k = B.shape[1] + s = np.zeros((A.shape[0], T)) + for t in range(1, T): + s[:, t] = A @ s[:, t-1] + B @ rng.standard_normal(k) + return s + + +def unconditional_stds(model, include_θ_shock=True): + """Unconditional standard deviations from the discrete Lyapunov equation.""" + B_use = model.B.copy() + if not include_θ_shock: + B_use[:, 0] = 0.0 # shut down the belief shock + Σ = solve_discrete_lyapunov(model.A, B_use @ B_use.T) + return np.sqrt(np.diag(Σ)) +``` + +```{code-cell} ipython3 +nk = create_nk_model() ``` ## Quantitative results @@ -771,8 +867,11 @@ mechanism works this way: 1. Pessimistic households expect worse future outcomes and reduce consumption demand. -2. Lower demand raises unemployment and reduces output. -3. Upward wage pressure from labour-market tightness feeds into inflation. +2. Firms' valuation of new matches falls, vacancy posting declines, output + falls, and unemployment rises. +3. Firms that share the pessimistic beliefs put extra probability on + low-productivity, high-marginal-cost states, weakening the disinflationary + force and sometimes raising inflation briefly on impact. 4. The belief wedges jump on impact, then decay with the persistence $\rho_\theta = 0.714$. @@ -786,7 +885,7 @@ mystnb: T_irf = 25 periods = np.arange(T_irf) -resp_θ, wu_θ, w_π_θ = nk.irf(shock_idx=0, T=T_irf) +resp_θ, wu_θ, w_π_θ = irf(nk, shock_idx=0, T=T_irf) fig, axes = plt.subplots(2, 3, figsize=(13, 7)) axes = axes.flatten() @@ -794,12 +893,12 @@ axes = axes.flatten() ylabels = ['unemployment (pp)', 'inflation (pp, ann.)', 'output (%)', 'belief shock θ', 'unemployment wedge Δ(u) (pp)', 'inflation wedge Δ(π) (pp)'] -series = [resp_θ[0] * 100, # unemployment in pp (fraction × 100) - resp_θ[1] * 400, # inflation ann. pp (quarterly frac × 400) - resp_θ[2] * 100, # output in % (fraction × 100) +series = [resp_θ[0] * 100, # unemployment in pp (fraction * 100) + resp_θ[1] * 400, # inflation ann. pp (quarterly frac * 400) + resp_θ[2] * 100, # output in % (fraction * 100) resp_θ[3], # belief shock θ - wu_θ, # unemp. wedge (pp): c_u × θ, already in pp - w_π_θ] # infl. wedge (pp): c_π × θ, already in pp + wu_θ, # unemp. wedge (pp): c_u * θ, already in pp + w_π_θ] # infl. wedge (pp): c_π * θ, already in pp colors = ['steelblue'] * 3 + ['purple', 'steelblue', 'darkorange'] for ax, ylabel, y, color in zip(axes, ylabels, series, colors): @@ -812,13 +911,21 @@ plt.tight_layout() plt.show() ``` +This figure has six panels. + +The first row plots the macroeconomic responses of unemployment, inflation, +and output to a one-standard-deviation innovation in the belief shock. + +The second row plots the belief shock itself and the two implied survey wedges. + The impulse responses show that a belief shock: -* Raises unemployment persistently (peak effect around 1 pp). -* Raises inflation on impact, as higher pessimism tightens labour markets - in the model. +* Raises unemployment persistently. +* Raises inflation on impact, with the response gradually decaying back to zero + in this reduced-form representation. +* Lowers output, so the shock looks like a pessimistic recessionary force. * Generates belief wedges for both unemployment and inflation that closely - mirror the dynamics of $\theta_t$ itself — consistent with the one-factor + mirror the dynamics of $\theta_t$ itself --- consistent with the one-factor structure. ### The unemployment volatility puzzle @@ -840,18 +947,18 @@ mystnb: caption: model and data volatility comparison name: fig-sbbc-volatility-comparison --- -std_full = nk.unconditional_stds(include_θ_shock=True) -std_no_θ = nk.unconditional_stds(include_θ_shock=False) +std_full = unconditional_stds(nk, include_θ_shock=True) +std_no_θ = unconditional_stds(nk, include_θ_shock=False) labels_vol = ['Unemployment', 'Inflation', 'Output'] -idx = [nk.I_U, nk.I_PI, nk.I_Y] +idx = [I_U, I_PI, I_Y] scale = [100, 400, 100] # convert to pp (unemployment, annualised inflation, %) std_full_scaled = [std_full[i] * scale[j] for j, i in enumerate(idx)] std_no_θ_scaled = [std_no_θ[i] * scale[j] for j, i in enumerate(idx)] # Reference values from Table 2 of the paper -data_std = [1.70, 1.07, 2.23] # data standard deviations +data_std = [1.70, 1.37, 2.00] # unemployment, inflation, output x = np.arange(len(labels_vol)) width = 0.25 @@ -870,25 +977,30 @@ ax.set_ylabel('standard deviation (% or pp, ann.)') ax.legend() plt.tight_layout() plt.show() - -print("Unconditional standard deviations:") -print(f"{'Variable':<18} {'No belief shock':>16} " - f"{'With belief shock':>18} {'Data':>10}") -print('-' * 65) -for label, std_n, std_f, std_d in zip(labels_vol, std_no_θ_scaled, - std_full_scaled, data_std): - print(f"{label:<18} {std_n:>16.2f} {std_f:>18.2f} {std_d:>10.2f}") ``` -The table confirms the key quantitative message: without the belief shock, -unemployment volatility is far below its empirical counterpart, but adding -the calibrated belief shock nearly doubles it, bringing the model much closer -to the data. +The bar chart compares three standard deviations: the model without belief +shocks, the model with belief shocks, and the data. + +The main message is visible in the unemployment bars. + +Without the belief shock, unemployment volatility is far below its empirical +counterpart, but adding the calibrated belief shock brings the model much +closer to the data. + +The paper is also clear about a limitation of the benchmark model. + +Inflation is nearly acyclical in the data but countercyclical in the model, +suggesting that omitted wage or price markup shocks may be important for +unconditional inflation dynamics without overturning the belief-wedge mechanism. ### Impulse responses to TFP and monetary policy shocks For completeness, we also show responses to the other two shocks. +The figure below has TFP responses in the top row and monetary-policy responses +in the bottom row. + ```{code-cell} ipython3 --- mystnb: @@ -896,8 +1008,8 @@ mystnb: caption: impulse responses to TFP and monetary policy shocks name: fig-sbbc-tfp-mp-irfs --- -resp_a, _, _ = nk.irf(shock_idx=1, T=T_irf) # TFP shock -resp_r, _, _ = nk.irf(shock_idx=2, T=T_irf) # Monetary policy shock +resp_a, _, _ = irf(nk, shock_idx=1, T=T_irf) # TFP shock +resp_r, _, _ = irf(nk, shock_idx=2, T=T_irf) # Monetary policy shock fig, axes = plt.subplots(2, 3, figsize=(13, 7)) axes = axes.flatten() @@ -907,7 +1019,6 @@ series_r = [resp_r[0]*100, resp_r[1]*400, resp_r[2]*100] var_ylabels = ['unemployment (pp)', 'inflation (pp, ann.)', 'output (%)'] for j, (ylabel, ya, yr) in enumerate(zip(var_ylabels, series_a, series_r)): - # TFP axes[j].plot(periods, ya, color='steelblue', linewidth=2, label='TFP shock') axes[j].axhline(0, color='grey', linewidth=0.7, linestyle='--') @@ -915,7 +1026,6 @@ for j, (ylabel, ya, yr) in enumerate(zip(var_ylabels, series_a, series_r)): axes[j].set_xlabel('quarters') axes[j].legend(loc='best') - # Monetary policy axes[j+3].plot(periods, yr, color='darkorange', linewidth=2, label='MP shock') axes[j+3].axhline(0, color='grey', linewidth=0.7, linestyle='--') @@ -927,23 +1037,61 @@ plt.tight_layout() plt.show() ``` +The TFP shock raises output and lowers unemployment. + +Inflation also falls in this calibration, reflecting the disinflationary effect +of higher productivity. + +All three responses are persistent because TFP itself is persistent. + +The monetary-policy shock is much less persistent. + +In the plotted reduced-form calibration, a contractionary monetary-policy +innovation raises unemployment, lowers inflation, and lowers output on impact, +but the responses fade quickly because the monetary-policy shock is i.i.d. + +Unlike the belief-shock figure, these panels do not include belief wedges: +TFP and monetary-policy shocks do not move $\theta_t$ directly in this +pedagogical representation. + ### Role of firms' beliefs {cite}`bhandari2025survey` also study a variant in which **firms** hold subjective beliefs. -The key channel is through the price-setting equation: -when firms fear that future demand will be weaker than the model predicts, -they raise prices today to protect margins, generating **higher inflation** on -impact. +The key channel is through the price-setting equation. + +Price-setting firms that share the household's pessimism put extra probability +weight on states with lower productivity and higher marginal costs. -This mechanism strengthens the comovement between the unemployment +If firms instead have rational beliefs, they see the household pessimism shock +mainly as contractionary demand, inflation falls, and the inflation wedge is too +small. + +Firm beliefs therefore strengthen the comovement between the unemployment wedge and the inflation wedge, which is needed to match the data. The sign of the inflation response to a belief shock is therefore a diagnostic: positive responses to pessimistic shocks require firms (not just households) to hold subjective beliefs. +### Two diagnostic variants + +The paper uses two variants to show how survey wedges restrict the model. + +**No TFP shocks** --- Without supply-side uncertainty, pessimistic agents worry +mainly about demand-type shocks, so the model predicts a negative average +inflation wedge and negative comovement between inflation and unemployment +wedges, both of which are counterfactual. + +**Rational firms** --- If households are pessimistic but firms have rational +beliefs, unemployment still responds strongly to a belief shock. + +Inflation, however, falls on impact and the inflation wedge is much too small. + +This variant shows why firms' subjective beliefs matter for matching the joint +behaviour of inflation and unemployment forecasts. + ### Countercyclicality of wedges A final important prediction is that belief wedges are countercyclical. @@ -961,11 +1109,10 @@ mystnb: caption: simulated countercyclicality of belief wedges name: fig-sbbc-countercyclical-wedges --- -sim = nk.simulate(T=400, seed=99) -θ_sim = sim[nk.I_THETA] -y_sim = sim[nk.I_Y] * 100 +sim = simulate_nk(nk, T=400, seed=99) +θ_sim = sim[I_THETA] +y_sim = sim[I_Y] * 100 -# c_u and c_pi are in pp per unit θ, so the wedge is already in pp wu_sim_series = nk.c_u * θ_sim w_π_sim_series = nk.c_π * θ_sim @@ -990,23 +1137,56 @@ axes[2].set_xlabel('quarter') plt.tight_layout() plt.show() -# Confirm countercyclicality numerically corr_u = np.corrcoef(y_sim, wu_sim_series)[0, 1] corr_π = np.corrcoef(y_sim, w_π_sim_series)[0, 1] print(f"Corr(output gap, unemployment wedge) = {corr_u:.3f} " - f"(data: −0.49)") + f"(data: -0.49)") print(f"Corr(output gap, inflation wedge) = {corr_π:.3f} " - f"(data: −0.30)") + f"(data: -0.30)") ``` +The top panel plots the simulated output gap, while the middle and bottom +panels plot the unemployment and inflation wedges generated by the same +simulation. + +Periods with weak output tend to coincide with elevated wedges. + The simulated correlations are negative, confirming the countercyclicality predicted by the model and documented in the survey data. +### Further empirical checks + +{cite}`bhandari2025survey` also verify the mechanism with reduced-form +evidence. + +They estimate local projections of macroeconomic variables and survey forecasts +on innovations to the first principal component of the belief wedges. + +A positive innovation predicts higher unemployment, higher belief wedges, and +an inflation response that is briefly positive before turning mildly negative. + +They also run forecast-error regressions of the form + +$$ + +z_{t+j} - \tilde E_t[z_{t+j}] += b_0 + b_z z_t + b_f \tilde E_{t-1}[z_{t+j-1}] + \varepsilon_{t+j}, + +$$ + +where $z_t$ is inflation or unemployment. + +Under full-information rational expectations these errors should be mean zero +and unforecastable. + +The survey data and the calibrated model both generate predictable forecast +errors, providing another check on the subjective-belief mechanism. + ## Extensions The paper explores several important extensions: -**Heterogeneous beliefs** — A natural question is whether households and +**Heterogeneous beliefs** --- A natural question is whether households and firms should hold the same subjective beliefs. The paper shows that @@ -1016,23 +1196,29 @@ the inflation dynamics substantially. This separation is identified from the relative sizes of the unemployment and inflation wedges. -**Higher-order perturbation** — The first-order approximation provides -clean analytical formulas for belief wedges, but second-order effects -(precautionary savings, volatility feedback) matter for welfare analysis. +**Beyond the first-order homoskedastic case** --- The approximation is designed +to keep subjective-belief effects alive in a linear solution. + +In richer nonlinear or stochastic-volatility settings, belief wedges could also +move because the dispersion of continuation values changes. -The paper develops second-order expansions and shows they affect the wedge -levels but not the one-factor structure. +We do not pursue those extensions here. -**Idiosyncratic risk** — In the full model households face idiosyncratic -labour-market risk. +**Idiosyncratic risk** --- The benchmark model takes fluctuations in $\theta_t$ as +exogenous, but the paper also derives them endogenously. -The interaction between aggregate pessimism and -uninsurable idiosyncratic shocks amplifies the effect of belief distortions -on precautionary savings, strengthening the unemployment channel. +In a variant where households face uninsurable idiosyncratic risk, a rise in +that risk makes adverse states more likely from each household's viewpoint, so +pessimism and the belief wedges increase without any exogenous shock to +$\theta_t$. + +The paper offers suggestive support for this channel: the belief wedges +comove with the {cite}`Schmidt2016` index of idiosyncratic labour-income +skewness, which proxies for the risk of large losses such as job loss. ## Appendix: the series expansion method -This appendix follows the Online Appendix of {cite}`BhandariBorovickaHo2024` +This appendix follows the Online Appendix of {cite}`bhandari2025survey` and fills in the computational and theoretical details underlying the linearisation presented in the main lecture. @@ -1068,8 +1254,9 @@ $$ so that $E_t[x_{t+\tau} - x_t] = G_x^{(\tau)} x_{1t} + G_0^{(\tau)}$. Under the **subjective** measure, the mean of $w_{t+1}$ is shifted to -$\nu_t = H + HF x_{1t}$ (equation OA.1 of the appendix). For the -stationary model the relevant identifications are +$\nu_t = H + HF x_{1t}$ (equation OA.1 of the appendix). + +For the stationary model the relevant identifications are $$ @@ -1106,7 +1293,7 @@ with the forecast horizon. def compute_tau_wedge_loadings(ψ_x, ψ_w, H, H_bar, F, τ_max=20): """ Compute tau-period belief wedge loadings using the recursions from - Online Appendix OA.1 of Bhandari, Borovicka, Ho (2024). + Online Appendix OA.1 of Bhandari, Borovicka, Ho (2025). For simplicity we work with the scalar stationary case (all quantities are scalars or 1-d arrays). @@ -1126,12 +1313,9 @@ def compute_tau_wedge_loadings(ψ_x, ψ_w, H, H_bar, F, τ_max=20): wedge_slope = np.zeros((τ_max, n)) for τ in range(1, τ_max + 1): - # data-generating measure new_Gx = (Gx + np.eye(n)) @ ψ_x new_G0 = G0 + (Gx + np.eye(n)) @ ψ_w @ np.zeros(ψ_w.shape[1]) - # (constant-shock term is zero under objective measure) - # subjective measure new_Gx_tild = ((Gx_tild + np.eye(n)) @ ψ_x + (Gx_tild + np.eye(n)) @ ψ_w @ (H @ F)) new_G0_tild = (G0_tild @@ -1153,22 +1337,18 @@ mystnb: caption: multi-period belief wedge profile name: fig-sbbc-horizon-wedge --- -# ----------------------------------------------------------------- -# Illustrate the wedge horizon profile in the simple endowment economy -# using the solved BeliefDistortionModel. -# ----------------------------------------------------------------- - -# Scalar model: ψ_x = [[ρ_x]], ψ_w = [[σ_x]] +# Scalar model objects ψ_x_sc = np.array([[model.ρ_x]]) ψ_w_sc = np.array([[model.σ_x]]) F_sc = np.array([[model.μ_θ]]) # θ-bar H_sc = np.array([[-model.Vx * model.σ_x]]) # -(Vx ψ_w)' -H_bar_sc = model.μ_θ * model.ρ_x * np.array([[-model.Vx * model.σ_x]]) +x_bar_sc = 1.0 +H_bar_sc = -model.μ_θ * x_bar_sc * np.array([[model.Vx * model.σ_x]]) τ_max = 20 wc, ws = compute_tau_wedge_loadings(ψ_x_sc, ψ_w_sc, H_sc, H_bar_sc, F_sc, τ_max) -# For illustration, evaluate at x1t = +1 std dev of θ. +# Evaluate at a one-standard-deviation belief shock. θ_std = model.σ_θ / np.sqrt(1 - model.ρ_θ**2) fig, ax = plt.subplots() @@ -1186,9 +1366,19 @@ plt.tight_layout() plt.show() ``` +The horizontal axis is the forecast horizon, from one quarter ahead to twenty +quarters ahead. + +The blue line is the multi-period wedge evaluated at the mean state, and the +red dashed line evaluates the same wedge after a one-standard-deviation +increase in the belief shock. + +The distance from zero grows with the horizon because pessimistic beliefs +affect not only the next shock but also the expected path of future states. + ### The series expansion -{cite}`BhandariBorovickaHo2024` solve the full general-equilibrium model +{cite}`bhandari2025survey` solve the full general-equilibrium model using a **series expansion** (perturbation) method ({cite}`BorovickaHansen2014`). @@ -1256,16 +1446,17 @@ V_q = u_q - \frac{\beta}{2}\,\bar\theta\, \bar x\, $$ -The Riccati equation is quadratic in $V_x$. For the stationary scalar case it -reduces to +The Riccati equation is quadratic in $V_x$. + +For the stationary scalar case it reduces to $$ a\, V_x^2 + b\, V_x + c = 0, \qquad a = \frac{\beta}{2}\sigma_x^2 \bar\theta,\quad -b = -(1 - \beta\rho_x),\quad -c = u_x. +b = 1 - \beta\rho_x,\quad +c = -u_x. $$ @@ -1273,8 +1464,10 @@ $$ Substituting the first-order expansion into the distortion formula (OA.10) shows that the leading term $m_{0,t+1}$ is a lognormal change of -measure. With Gaussian shocks, this is equivalent to shifting the -innovation mean (equation OA.12): +measure. + +With Gaussian shocks, this is equivalent to shifting the innovation mean +(equation OA.12): $$ @@ -1398,7 +1591,7 @@ $$ The algorithm therefore decomposes cleanly into two stages: 1. **Stage 1 (rational-expectations block)**: solve (OA.24) and (OA.26) for - $\psi_x$, $\psi_w$ using the standard Blanchard–Kahn method — these are + $\psi_x$, $\psi_w$ using the standard Blanchard–Kahn method --- these are *unaffected* by the belief shock. 2. **Stage 2 (belief distortion block)**: given $\psi_x, \psi_w, V_x$, @@ -1409,16 +1602,6 @@ This separation is a major practical advantage: existing rational-expectations solvers can be used for Stage 1 with only a wrapper for Stage 2. ```{code-cell} ipython3 -# ----------------------------------------------------------------- -# Demonstrate the limiting Stage 2 fixed point in a stylised scalar economy. -# -# Setup: -# - Endogenous state x, belief shock f (= θ_t) -# - ψ_x (1x1), ψ_w (1x1) known from Stage 1 -# - Vx known from the Riccati equation -# - Solve the first-order fixed point for Vf and ψ_xf -# ----------------------------------------------------------------- - β = model.β ρ_x = model.ρ_x σ_x = model.σ_x @@ -1426,22 +1609,16 @@ solvers can be used for Stage 1 with only a wrapper for Stage 2. σ_f = model.σ_θ Vx = model.Vx -# Stage 1 objects (RE solution) +# Stage 1 rational-expectations objects ψ_x_s1 = ρ_x ψ_w_s1 = σ_x -# gx+ = β * (1 - β) in the simple log-utility endowment economy -# (partial derivative of marginal utility w.r.t. x_{t+1}) +# Simple log-utility derivative with respect to x_{t+1} gx_plus = β * (1 - β) θ_f = 1.0 # f is θ in the partitioned state. -# First-order scalar fixed point. -# -# The full nonlinear backward recursion is stable in the paper's full model, -# but the stripped-down scalar example can diverge because it lacks the -# stabilising equilibrium blocks. We therefore solve the first-order limiting -# system directly. +# First-order scalar fixed point denom = gx_plus * ψ_x_s1 - (1 - β) E_const = (gx_plus * ψ_w_s1) * ψ_w_s1 * Vx * θ_f penalty_const = (β * θ_f / 2.0) * (Vx * ψ_w_s1)**2 @@ -1470,8 +1647,9 @@ print("The steady-state wedge for x: Δ = ψ_w * ν_bar =", ### Sequence problem and dynamic consistency The recursive formulation used throughout the lecture emerges from the -following sequence problem (Online Appendix OA.3). Define the discounted -entropy functional +following sequence problem (Online Appendix OA.3). + +Define the discounted entropy functional $$ @@ -1482,7 +1660,9 @@ $$ $$ -where $M_{t,t+j} = \prod_{k=1}^j m_{t+k}$. The agent's problem is +where $M_{t,t+j} = \prod_{k=1}^j m_{t+k}$. + +The agent's problem is $$ @@ -1499,7 +1679,9 @@ pessimism will evolve. This differs from the infinite-horizon discounted entropy used in {cite}`HansenSargent2001`, which is not generally dynamically consistent -when $\theta_t$ is time-varying. The recursive form is: +when $\theta_t$ is time-varying. + +The recursive form is: $$ @@ -1515,22 +1697,11 @@ $$ V_t^* = \max_{y_t} \min_{\substack{m_{t+1}>0 \\ E_t[m_{t+1}]=1}} u_t + \frac{\beta}{\theta_t} E_t[m_{t+1} \log m_{t+1}] - + E_t[m_{t+1} V_{t+1}^*]. + + \beta E_t[m_{t+1} V_{t+1}^*]. $$ ```{code-cell} ipython3 -# ----------------------------------------------------------------- -# Illustrate the role of dynamic consistency by comparing two penalty -# specifications: -# (a) Paper specification: E_t = (β/θ_t) * H_t + β * E[m*E_{t+1}] -# where H_t = E_t[m_{t+1} log m_{t+1}] -# (b) A myopic version that uses only the one-period entropy: -# E_t^{myopic} = (β/θ_t) * H_t -# -# We compare the implied belief wedge as θ varies. -# ----------------------------------------------------------------- - θ_path = np.array([3.0, 5.64, 8.0, 12.0]) # rising pessimism scenario def one_period_entropy(θ, Vx, σ_x): @@ -1545,7 +1716,7 @@ print(f"{'θ_t':>8} {'H_t (entropy)':>16} {'Δ(x) = σ_x ν_t (pp)':>22}") print('-' * 52) for th in θ_path: H = one_period_entropy(th, model.Vx, model.σ_x) - bw = model.belief_wedge(th) * 100 + bw = belief_wedge(model, th) * 100 print(f"{th:>8.2f} {H:>16.6f} {bw:>22.4f}") print() @@ -1561,14 +1732,14 @@ print("constraining the agent from distorting beliefs too heavily.") **Belief wedge sign** -In the simple endowment economy of the `BeliefDistortionModel`, suppose the state -variable is log consumption $x_t$ with $\rho_x = 0.90$, $\sigma_x = 0.01$, +In the simple endowment economy built by `create_belief_model`, suppose the +state variable is log consumption $x_t$ with $\rho_x = 0.90$, $\sigma_x = 0.01$, $\beta = 0.99$. -(a) Compute $V_x$ under rational expectations and under pessimism +1. Compute $V_x$ under rational expectations and under pessimism $\mu_\theta = 4$. -(b) What is the sign of the belief wedge for consumption growth? -(c) If instead the agent forecasts unemployment (which enters the value +2. What is the sign of the belief wedge for consumption growth? +3. If instead the agent forecasts unemployment (which enters the value function with a negative sign, so $u_x < 0$), what is the sign of the unemployment belief wedge? ```{exercise-end} @@ -1579,7 +1750,7 @@ $\beta = 0.99$. :class: dropdown ``` -**Part (a)** — Under rational expectations ($\theta = 0$): +**Part (a)** --- Under rational expectations ($\theta = 0$): $$ @@ -1597,12 +1768,12 @@ $$ Vx_re_ex = (1 - β_ex) / (1 - β_ex * ρ_x_ex) print(f"V_x (rational expectations): {Vx_re_ex:.4f}") -m_ex = BeliefDistortionModel(β=β_ex, ρ_x=ρ_x_ex, - σ_x=σ_x_ex, μ_θ=μ_θ_ex) -print(f"V_x (with pessimism θ̄={μ_θ_ex}): {m_ex.Vx:.4f}") +m_ex = create_belief_model(β=β_ex, ρ_x=ρ_x_ex, + σ_x=σ_x_ex, μ_θ=μ_θ_ex) +print(f"V_x (with pessimism θ_bar={μ_θ_ex}): {m_ex.Vx:.4f}") ``` -**Part (b)** — The belief wedge for consumption growth is +**Part (b)** --- The belief wedge for consumption growth is $$ @@ -1614,8 +1785,9 @@ $$ Since $V_x > 0$ and $\theta_t > 0$, the wedge is **negative**: pessimistic agents underestimate consumption growth relative to the model. -**Part (c)** — For unemployment, $u_x < 0$, so $V_x^u < 0$. The belief -wedge becomes +**Part (c)** --- For unemployment, $u_x < 0$, so $V_x^u < 0$. + +The belief wedge becomes $$ @@ -1636,7 +1808,7 @@ This matches the empirical finding of a positive mean unemployment wedge. **Persistence and wedge volatility** -Using the `BeliefDistortionModel` class, vary $\rho_\theta$ from 0.3 to +Using `create_belief_model`, vary $\rho_\theta$ from 0.3 to 0.95 (holding $\sigma_\theta = 4.3$ fixed) and plot how the standard deviation of the belief wedge changes. @@ -1660,9 +1832,9 @@ mystnb: wedge_stds = [] for ρ in ρ_vals: - m_temp = BeliefDistortionModel(ρ_θ=ρ) - θ_sim_temp = m_temp.simulate_θ(T=5000, seed=0) - wedge_sim_temp = m_temp.belief_wedge(θ_sim_temp) + m_temp = create_belief_model(ρ_θ=ρ) + θ_sim_temp = simulate_θ(m_temp, T=5000, seed=0) + wedge_sim_temp = belief_wedge(m_temp, θ_sim_temp) wedge_stds.append(np.std(wedge_sim_temp)) fig, ax = plt.subplots() @@ -1673,6 +1845,12 @@ plt.tight_layout() plt.show() ``` +The figure plots the persistence parameter $\rho_\theta$ on the horizontal +axis and the simulated standard deviation of the belief wedge on the vertical +axis. + +The curve slopes upward. + Higher persistence $\rho_\theta$ means that a given innovation to $\theta_t$ has more prolonged effects: the unconditional variance of an AR(1) with volatility $\sigma$ is $\sigma^2 / (1 - \rho^2)$, which increases in $\rho$. @@ -1689,7 +1867,7 @@ inherits this relationship and rises with $\rho_\theta$. **Unemployment volatility decomposition** -Using the `ReducedFormNKModel` class: +Using the reduced-form NK model built by `create_nk_model`: (a) Compute the fraction of unemployment variance explained by each of the three shocks. @@ -1704,14 +1882,11 @@ Using the `ReducedFormNKModel` class: ``` ```{code-cell} ipython3 -# Variance decomposition via the Lyapunov equation, shock-by-shock - shock_names = ['Belief shock (θ)', 'TFP shock', 'MP shock'] var_labels = ['Unemployment', 'Inflation', 'Output'] -nk2 = ReducedFormNKModel() +nk2 = create_nk_model() -# Compute the variance of each variable attributable to each shock n_states = nk2.A.shape[0] var_by_shock = np.zeros((n_states, 3)) @@ -1720,13 +1895,11 @@ for j in range(3): Σ_j = solve_discrete_lyapunov(nk2.A, B_j) var_by_shock[:, j] = np.diag(Σ_j) -# Total variance var_total = var_by_shock.sum(axis=1) -# Print share of variance for key variables print(f"{'Variable':<16}", *[f"{s:>20}" for s in shock_names]) print('-' * 77) -for i, label in zip([nk2.I_U, nk2.I_PI, nk2.I_Y], var_labels): +for i, label in zip([I_U, I_PI, I_Y], var_labels): shares = var_by_shock[i] / var_total[i] * 100 print(f"{label:<16}", *[f"{s:>19.1f}%" for s in shares]) ``` @@ -1749,7 +1922,7 @@ policy shocks play a smaller role for both variables. **Changing the degree of pessimism** -Solve the Riccati equation in the `BeliefDistortionModel` for a grid of +Solve the Riccati equation (`solve_Vx`) for a grid of $\mu_\theta$ values from 0 (rational expectations) to 15. For each value, @@ -1780,9 +1953,9 @@ wedge_ss = [] Vx_re = (1 - 0.994) / (1 - 0.994 * 0.85) for μ in μ_grid: - m_temp = BeliefDistortionModel(μ_θ=μ) + m_temp = create_belief_model(μ_θ=μ) Vx_vals.append(m_temp.Vx) - wedge_ss.append(m_temp.belief_wedge(μ) * 100) # in pp + wedge_ss.append(belief_wedge(m_temp, μ) * 100) # in pp fig, axes = plt.subplots(1, 2, figsize=(11, 4)) @@ -1801,14 +1974,22 @@ plt.tight_layout() plt.show() ``` +The left panel plots the solved value-function slope $V_x$ against the mean +pessimism parameter $\mu_\theta$. + +The dashed horizontal line is the rational-expectations benchmark. + +The right panel plots the corresponding steady-state belief wedge. + As $\mu_\theta$ rises, the Riccati equation introduces an additional curvature term that lowers $V_x$ (less marginal value to the current state) because the agent effectively prices in the possibility of bad future outcomes. -The steady-state wedge grows approximately linearly in -$\mu_\theta$, since $\Delta^{(1)} \propto \mu_\theta V_x \sigma_x^2$ and -$V_x$ is approximately constant for small $\mu_\theta$. +The steady-state consumption wedge becomes more negative, approximately +linearly in magnitude, since +$\Delta^{(1)} \propto -\mu_\theta V_x \sigma_x^2$ and $V_x$ is approximately +constant for small $\mu_\theta$. ```{solution-end} ``` diff --git a/lectures/tsyrennikov_2013.md b/lectures/tsyrennikov_2013.md index 7ed5807f..b6afab43 100644 --- a/lectures/tsyrennikov_2013.md +++ b/lectures/tsyrennikov_2013.md @@ -3,10 +3,12 @@ jupytext: text_representation: extension: .md format_name: myst + format_version: 0.13 + jupytext_version: 1.19.1 kernelspec: - display_name: Python 3 - language: python name: python3 + display_name: Python 3 (ipykernel) + language: python --- (tsyrennikov_2013)= @@ -14,36 +16,89 @@ kernelspec: ## Overview -This lecture studies {cite:t}`Tsyrennikov2013`, which extends {cite:t}`Atkeson1991` -(see the companion lecture {doc}`atkeson_1991`) in two directions: +This lecture studies {cite:t}`Tsyrennikov2013`, which revisits the +infinite-horizon moral-hazard and limited-enforcement model of +{cite:t}`Atkeson1991` (see {doc}`atkeson_1991`) and makes two main +contributions: -1. **Continuous investment** — the borrower chooses a continuous investment - level rather than a binary one, and the paper proves that the - **first-order approach** (FOA) to the incentive-compatibility constraint is - valid. This brings the model much closer to empirically relevant calibrations. -2. **Calibration and quantitative analysis** — the model is calibrated to - Argentina's business cycle data and compared against a limited-enforcement - (Eaton–Gersowitz-style) model. +1. **First-order approach**: it proves ({prf:ref}`tsyrennikov_foa_lemma`) that + the borrower's incentive-compatibility constraint can be replaced by its + first-order condition, which makes the optimal contract with continuous + investment tractable to compute. +2. **Calibration and quantitative analysis**: it calibrates the model to + Argentina's business cycle and compares moral hazard against a pure + limited-enforcement benchmark. Unlike standard sovereign-default models, + contracts here are allowed to be fully state contingent. -The central finding is that *moral hazard, not limited enforcement, drives the -key empirical regularities of emerging market economies*: high and volatile -interest rate spreads, limited consumption risk-sharing, and crisis-like -dynamics in which capital inflows suddenly stop. +The central finding is that *moral hazard, not limited enforcement, does most +of the work* in matching several key features of emerging market economies: +high, volatile and countercyclical interest rate spreads, limited consumption +risk-sharing, and crisis-like dynamics in which capital inflows halt and +interest rates spike. -The key mechanism is that moral hazard severely restricts *state contingency* in -repayment schedules. +The mechanism is that moral hazard severely restricts the amount of +*state contingency* that repayment schedules can provide. -In the language of {cite}`Atkeson1991`, the optimal -contract is nearly *non-contingent* on output — a theoretical justification for -why simple debt contracts dominate in practice. +As a result, the optimal repayment is nearly *non-contingent* on output. -```{note} -This lecture uses the same notation as the {doc}`atkeson_1991` lecture, -writing $\beta$ for the borrower's discount factor (Tsyrennikov writes $\beta$ -for the borrower and $\beta_c$ for the lender). -``` +This justifies why non-contingent debt is an optimal way to finance an emerging +economy. + +Moral hazard also gives the model a strong internal propagation mechanism: even +i.i.d. output shocks generate persistent movements in output through +investment. + +Tsyrennikov is also explicit about the model's main weakness. + +The mechanism improves the behavior of consumption, output and spreads, but it +does not fully match the observed current-account dynamics. + +## Empirical motivation + +The paper starts from three facts about Argentina, viewed as a representative +emerging market economy over 1993--2005. + +First, consumption is almost perfectly correlated with output and is at least as +volatile as output. + +Second, interest rate spreads are high, volatile and countercyclical. -## The model +Third, after a sequence of bad output realizations, capital inflows stop or +reverse. + +For comparison, Canada displays much smoother consumption and much weaker +spread-output comovement. + +The following reduced version of the paper's data table highlights the contrast. + + +Here and in the moments table below, $E(\cdot)$ is a mean, $\sigma(\cdot)$ a +standard deviation, and $\rho(\cdot,\cdot)$ a correlation, while $\rho(y)$ is the +first-order autocorrelation of output. + +The variables are consumption $c$, output $y$, the trade balance $tb$, and the +interest-rate spread $r$ over the world risk-free rate, in annualized percentage +points. + +| country and period | $\sigma(c)/\sigma(y)$ | $\rho(c,y)$ | $E(r)$ | $\sigma(r)$ | $\rho(r,y)$ | $\rho(tb,y)$ | +| --- | ---: | ---: | ---: | ---: | ---: | ---: | +| Canada, 1993:Q1--2001:Q4 | 0.55 | 0.62 | 1.51 | 0.33 | 0.23 | 0.27 | +| Argentina, 1993:Q1--2001:Q4 | 1.11 | 0.97 | 8.18 | 4.73 | -0.58 | -0.81 | +| Argentina, 1993:Q1--2005:Q4 | 1.15 | 0.99 | 7.86 | 4.78 | -0.68 | -0.82 | + +So $\sigma(c)/\sigma(y)$ is consumption volatility relative to output, +$\rho(c,y)$ is consumption-output comovement, and a negative $\rho(r,y)$ or +$\rho(tb,y)$ means the spread or trade balance is countercyclical. + +The moral-hazard interpretation is that foreign creditors cannot fully observe +the use of borrowed funds. + +This is plausible when national accounts are noisy, when governments can blur +the line between consumption and investment, or when the level of investment is +observable but its effective quality is distorted by misallocation, corruption +or weak institutions. + +## The environment ### Technology and preferences @@ -54,161 +109,576 @@ repayment), borrows $b$ from a short-lived risk-neutral lender, invests $I$, and consumes $$ -c \;=\; n + b - \theta I, \quad \theta > 0. -$$ +c = n + b - \theta I, \qquad \theta > 0. +$$ (eq:tsyrennikov_budget) -Given investment $I$, next period's output $Y'$ is drawn from +Given investment $I$, next period's output is random and takes one of two +ordered values $Y_1 < Y_2$. + +Following {cite:t}`Atkeson1991`, output is drawn from a mixture of two fixed +distributions $g_0$ and $g_1$, where $g_{kj}$ denotes the probability that +distribution $g_k$ assigns to output state $Y_j$. + +Here $g_0$ is the *favorable* distribution: it places more weight on high output +than $g_1$ does, so $g_0$ first-order stochastically dominates $g_1$. + +Investment controls the mixing weight $\lambda(I) \in [0,1]$, the probability +that output is drawn from the favorable distribution $g_0$: $$ -g(Y_j \mid I) \;=\; \bigl(1 - \lambda(I)\bigr)\,g_{0j} - + \lambda(I)\,g_{1j}, \qquad j = 1, 2, -$$ +g(Y_j \mid I) = \lambda(I)\,g_{0j} + + \bigl(1 - \lambda(I)\bigr)\,g_{1j}, \qquad j = 1, 2, +$$ (eq:tsyrennikov_output_law) + +so $g(Y_j \mid I)$ is the probability of output state $Y_j$ given investment +$I$. -where $\lambda : \mathbb{R}_+ \to [0,1]$ is strictly increasing and strictly -concave, so higher investment stochastically dominates lower investment. +The weight $\lambda : \mathbb{R}_+ \to [0,1]$ is strictly increasing and strictly +concave. -Tsyrennikov restricts to two output states and sets +Higher investment therefore raises the weight on $g_0$, so the output +distribution under higher investment first-order stochastically dominates that +under lower investment, with diminishing returns. + +```{note} +This is the same mixture technology, and uses the same labelling, as in +{doc}`atkeson_1991`: the weight $\lambda(I)$ multiplies the favorable +distribution $g_0$, so more investment makes high output more likely. +``` + +Tsyrennikov restricts to two output states, so the favorable distribution puts +all its mass on high output and the unfavorable one on low output: $$ -\text{Pr}(Y_1 \mid I) = 1 - \lambda(I), \qquad -\text{Pr}(Y_2 \mid I) = \lambda(I), \qquad Y_1 < Y_2, +g_0 = (g_{0,1},\,g_{0,2}) = (0,\,1), \qquad +g_1 = (g_{1,1},\,g_{1,2}) = (1,\,0). +$$ + +The output probabilities then reduce to + $$ +\Pr(Y_1 \mid I) = 1 - \lambda(I), \qquad +\Pr(Y_2 \mid I) = \lambda(I). +$$ (eq:tsyrennikov_two_state_output) + +It is convenient to record how investment moves the output distribution. + +Let $\Delta g_j \equiv g_{0j} - g_{1j}$, so that +$\partial g(Y_j \mid I)/\partial I = \lambda'(I)\,\Delta g_j$. -so $g_{0,1}=1,\;g_{0,2}=0,\;g_{1,1}=0,\;g_{1,2}=1$ and -$\Delta g_j \equiv g_{1j} - g_{0j} = (-1, 1)$. +In the two-state model $\Delta g = (-1,\,1)$: a marginal increase in investment +shifts probability away from low output and toward high output. -The functional form $\lambda(I) = \min(I^\nu, 1)$ with $\nu \in (0,1)$ -is strictly concave and gives an interior optimum. +The functional form $\lambda(I) = \min(I^\nu, 1)$ with $\nu \in (0,1)$ is +strictly concave and gives an interior optimum. The borrower's preferences are CRRA: $$ -U^B = \mathbb{E}_0 \sum_{t=0}^\infty \beta^t \, u(c_t), +U = \mathbb{E}_0 \sum_{t=0}^\infty \beta^t \, u(c_t), \quad u(c) = \frac{c^{1-\gamma}}{1-\gamma}, \quad \gamma > 1. -$$ +$$ (eq:tsyrennikov_preferences) + +Lenders are risk-neutral and discount the future at factor $\beta_c \geq \beta$, +so they lend at the international gross risk-free rate $1/\beta_c$. + +Each lender lives for two periods with endowment $M$, so the loan cannot exceed +it: $b \leq M$. -Lenders discount at rate $\beta_c \geq \beta$ (the international risk-free -rate) and have endowment $M$ each period, so $b \leq M$. +The assumption $\beta \leq \beta_c$ reflects that the government of an emerging +economy may be more impatient than a typical international lender. + +A contract between the two parties specifies the loan $b$ and the repayment +$d_j$ that the borrower makes after each output state $Y_j$. ### Two frictions -**Moral hazard (MH)**: lenders observe output but not investment. +There are two frictions that limit the contract's ability to smooth consumption and investment across states. + +**Moral hazard**: lenders observe output but neither investment nor +consumption. -The incentive-compatibility (IC) constraint requires that the borrower finds the -contracted investment $I$ to be in their own best interest. +The incentive-compatibility (IC) constraint {eq}`eq:tsyrennikov_ic` requires +that the borrower finds the recommended investment $I$ privately optimal. -**Limited enforcement (LE)**: the borrower can default, suffering a one-time +**Limited enforcement**: the borrower can default, suffering a one-time output penalty: if default occurs when output is $Y_j$, the borrower retains only $\delta Y_j$ (with $\delta \in (0,1)$) and then lives in autarky. -The participation constraint requires +Let $v_{\text{aut}}^{\delta}(Y_j)$ denote the value after default in state +$j$: $$ -V(Y_j - d_j) \;\geq\; V_{\text{aut}}(\delta\,Y_j), \quad \forall j, +v_{\text{aut}}^{\delta}(Y_j) + = + \max_{0 \leq I \leq Y_j} + \left\{ + u(\delta Y_j - \theta I) + + \beta \sum_k g(Y_k \mid I) v_{\text{aut}}(Y_k) + \right\}. +$$ (eq:tsyrennikov_default_value) + +Here $v_{\text{aut}}$ is the borrower's no-credit autarky value, defined in the +next subsection; the superscript $\delta$ marks the one-period output loss +incurred on entering autarky. + +The enforcement constraint requires + $$ +v(Y_j - d_j) \geq v_{\text{aut}}^{\delta}(Y_j), \qquad j = 1,2, +$$ (eq:tsyrennikov_enforcement) -where $V$ is the contract value function and $V_{\text{aut}}$ is the autarky -value function. +where $v$ is the contract value function. + +A larger $\delta$ means a milder default penalty and hence a better outside +option after default. ### The autarky value function Without access to credit ($b = 0$), the borrower solves $$ -V_{\text{aut}}(n) = \max_{I \in [0,\,n/\theta]} - \Bigl[u(n - \theta I) + \beta\,\bigl[(1-\lambda(I))\,V_{\text{aut}}(Y_1) - + \lambda(I)\,V_{\text{aut}}(Y_2)\bigr]\Bigr]. -$$ +v_{\text{aut}}(n) = + \max_{0 \leq I \leq n} + \Bigl[u(n - \theta I) + \beta\,\bigl[(1-\lambda(I))\,v_{\text{aut}}(Y_1) + + \lambda(I)\,v_{\text{aut}}(Y_2)\bigr]\Bigr]. +$$ (eq:tsyrennikov_autarky) Note that the continuation values depend only on $Y_1$ and $Y_2$, not on $n$. +### The frictionless benchmark + +If investment is contractible and contracts are fully enforceable, the borrower +can trade a full set of Arrow securities with the risk-neutral lender. + +The optimal repayment schedule then delivers state-independent continuation net +worth: + +$$ +Y_j - d_j = n' \qquad \text{for all } j. +$$ + +Net worth converges to a constant, so consumption and investment are eventually +constant, and the risk-sharing index defined below equals one. + +This benchmark implies uninhibited risk-sharing and a strongly procyclical +current account --- the opposite of what the data show. + +It also shows what moral hazard limits: the ability to make repayments +strongly state contingent without weakening investment incentives. + ### The recursive contract The state variable is net worth $n$. +The optimal long-term contract can be represented recursively with this +single state is established in {cite:t}`Atkeson1991`, and we take it as given. -The value function satisfies the Bellman -equation +Let $$ -V(n) = \max_{b,\,d,\,I} - \Bigl[u(n+b-\theta I) + \beta\,\sum_j g(Y_j\mid I)\,V(Y_j - d_j)\Bigr] +n_j' := Y_j - d_j +$$ + +be next period's net worth after output $Y_j$ and repayment $d_j$. + +The contract value satisfies the Bellman equation + +$$ +v(n) = \max_{b,\,d,\,I} + \Bigl[u(n+b-\theta I) + \beta\,\sum_j g(Y_j\mid I)\,v(Y_j - d_j)\Bigr] +$$ (eq:tsyrennikov_bellman) + +subject to the following constraints. + +A feasibility constraint combines the budget constraint {eq}`eq:tsyrennikov_budget`, with +nonnegative consumption $c = n + b - \theta I \geq 0$ and investment $I \geq 0$. + +Lender participation requires the loan not to exceed the discounted expected +value of repayments, + +$$ +b \leq \beta_c \sum_j g(Y_j \mid I)\,d_j, +$$ (eq:tsyrennikov_lender_ir) + +while the lender endowment constraint caps the loan at the lender's resources, +$b \leq M$. + +The contract must also satisfy incentive compatibility {eq}`eq:tsyrennikov_ic` +and the enforcement constraint {eq}`eq:tsyrennikov_enforcement`. + +The incentive constraint says that the borrower chooses the recommended +investment from the feasible set: + $$ +I \in \arg\max_{0 \leq \hat I \leq n+b} + \left\{ + u(n+b-\theta \hat I) + + \beta \sum_j g(Y_j\mid \hat I) v(Y_j-d_j) + \right\}. +$$ (eq:tsyrennikov_ic) -subject to feasibility, lender participation ($b \leq \beta_c \sum_j -g_j(I)\,d_j$), incentive compatibility, and enforcement constraints. +Since $v$ is strictly increasing, limited enforcement can also be written as an +endogenous borrowing limit: + +$$ +d_j \leq \bar d_j + := Y_j - v^{-1}\!\left(v_{\text{aut}}^{\delta}(Y_j)\right). +$$ (eq:tsyrennikov_borrowing_limit) ## The first-order approach -A key contribution of {cite:t}`Tsyrennikov2013` is **Lemma 1**, which shows that -replacing the full IC constraint with the first-order condition +The incentive constraint {eq}`eq:tsyrennikov_ic` is awkward to impose directly, +because it requires re-solving the borrower's investment problem inside the +contracting problem. + +Following {cite:t}`Rogerson1985`, {cite:t}`Tsyrennikov2013` replaces it with the +borrower's first-order condition + +$$ +-\theta\,u'(c) + \beta\,\lambda'(I)\,\sum_j \Delta g_j\,v(Y_j-d_j) \geq 0, +$$ (eq:tsyrennikov_relaxed_ic) + +which holds with equality whenever investment is interior. + +```{prf:lemma} Validity of the first-order approach +:label: tsyrennikov_foa_lemma + +Replacing the incentive constraint {eq}`eq:tsyrennikov_ic` with the relaxed +first-order condition {eq}`eq:tsyrennikov_relaxed_ic` does not change the +solution of the contract problem {eq}`eq:tsyrennikov_bellman`. +``` + +This is Lemma 1 of {cite:t}`Tsyrennikov2013`. + +```{prf:proof} +Fix a contract $(b, d_1, d_2)$, write $n_j' = Y_j - d_j$ and +$c(\hat I) = n + b - \theta\hat I$, and let $$ --\theta\,u'(c) + \beta\,\lambda'(I)\,\sum_j \Delta g_j\,V(Y_j-d_j) \geq 0 +S \;:=\; \sum_j \Delta g_j\, v(n_j') \;=\; v(n_2') - v(n_1') $$ -does *not* alter the solution. +be the continuation-value spread. -The key step is showing that at any feasible -contract, $\sum_j \Delta g_j\,V(Y_j-d_j) \geq 0$, which ensures the -borrower's objective is strictly concave in $I$ and the FOC holds with -equality. +Using $g(Y_j\mid\hat I) = \lambda(\hat I)\,g_{0j} + (1-\lambda(\hat I))\,g_{1j}$, +a borrower who is offered this contract and invests $\hat I$ obtains -This result (analogous to {cite}`Rogerson1985`) validates the -relaxed formulation used in the numerical solution. +$$ +\begin{aligned} +W(\hat I) +&:= u(c(\hat I)) + \beta \sum_j g(Y_j\mid\hat I)\, v(n_j') \\ +&\;= u(c(\hat I)) + \beta\bigl[v(n_1') + \lambda(\hat I)\, S\bigr], +\end{aligned} +$$ -With the FOA, the optimality condition for investments is +with first two derivatives $$ -\theta\,u'(c) \;=\; \beta\,\lambda'(I)\,\bigl[V(n_2') - V(n_1')\bigr], -$$ (foa) +W'(\hat I) = -\theta\, u'(c(\hat I)) + \beta\,\lambda'(\hat I)\, S, +\qquad +W''(\hat I) = \theta^2\, u''(c(\hat I)) + \beta\,\lambda''(\hat I)\, S . +$$ + +The incentive constraint {eq}`eq:tsyrennikov_ic` is +$I \in \arg\max_{\hat I \in [0,\, n+b]} W(\hat I)$, while the relaxed condition +{eq}`eq:tsyrennikov_relaxed_ic` is $W'(I) \geq 0$. + +First, we show that *a nonnegative spread makes the first-order condition sufficient.* + +Suppose $S \geq 0$. + +Since $u'' < 0$ and $\lambda'' < 0$, we have $\theta^2 u''(c) < 0$ and +$\beta\,\lambda''(\hat I)\, S \leq 0$, so $W''(\hat I) < 0$ for every $\hat I$. + +Hence $W$ is strictly concave on $[0,\, n+b]$ and has a unique maximizer. + +The Inada condition $u'(0) = +\infty$ rules out the upper corner +$\hat I = (n+b)/\theta$, where $c = 0$, so the maximizer is either interior, with +$W'(I) = 0$, or the lower corner $I = 0$. + +In either case the borrower's choice is characterized by its first-order +condition, so {eq}`eq:tsyrennikov_ic` and {eq}`eq:tsyrennikov_relaxed_ic` --- the +latter holding with equality at an interior optimum --- select the same +investment. + +Next, we need to show that *every optimal contract has $S \geq 0$, so the first-order condition is sufficient at the optimum.* + +Suppose an optimal contract had $S < 0$, that is $v(n_2') < v(n_1')$. + +Then for every $\hat I \in (0,\, n+b]$ both terms of $W'(\hat I)$ are negative, +because $u' > 0$, $\lambda' > 0$ and $S < 0$, so $W$ is strictly decreasing and +the borrower invests $I = 0$. + +At $I = 0$ we have $\lambda(0) = 0$, so output equals $Y_1$ with probability one +and the borrower's payoff $u(n+b) + \beta\, v(n_1')$ is independent of $n_2'$. + +Now raise the high-output continuation to $\tilde n_2' = n_1'$ --- equivalently set +$\tilde d_2 = Y_2 - n_1'$ --- leaving $b$, $d_1$ and the recommended $I = 0$ +unchanged. + +This contract is still feasible: lender participation +{eq}`eq:tsyrennikov_lender_ir` weights $d_2$ by $g(Y_2\mid 0) = 0$ and is +unaffected, while the state-$2$ enforcement constraint +{eq}`eq:tsyrennikov_enforcement` is relaxed because +$v(\tilde n_2') = v(n_1') > v(n_2')$. + +It delivers the same borrower payoff but now has spread +$\tilde S = v(n_1') - v(n_1') = 0 \geq 0$. + +This gives one direction: an optimal contract can always be taken to have +$S \geq 0$, and then the argument above makes its first-order condition coincide +with incentive compatibility, so the original optimum is feasible for the +relaxed problem. + +For the other direction, the relaxed constraint +$W'(I) = -\theta u'(c) + \beta\lambda'(I)\, S \geq 0$ can hold only when $S > 0$, +because $u' > 0$ and $\lambda' > 0$. + +So the relaxed problem only ever considers contracts with $S \geq 0$, where its +first-order condition is genuine incentive compatibility. + +The relaxed problem therefore neither loses the original optimum nor admits a +contract that is not incentive compatible, which proves the lemma. +``` + +The subtle part of the argument is why the planner may freely raise $n_2'$. + +The figure below illustrates the two facts it rests on. + +```{code-cell} ipython3 +--- +mystnb: + figure: + caption: why a negative spread lets the planner move the high-output continuation + name: fig-tsy-foa-proof +--- +import numpy as np +import matplotlib.pyplot as plt + +# Illustrative numbers for the borrower's investment problem +β_, θ_, ν_, γ_ = 0.98, 0.105, 0.95, 2.0 +nb = 1.0 # current resources n + b +v1 = -1.0 # continuation value v(n_1') after low output + +def prob_high(I): # λ(I) = Pr(Y_2 | I) + return np.minimum(I ** ν_, 1.0) + +def W(I, S): # borrower's payoff from investing I given spread S + c = nb - θ_ * I + return c ** (1 - γ_) / (1 - γ_) + β_ * (v1 + prob_high(I) * S) + +I_vals = np.linspace(0.0, 0.8, 400) + +fig, axes = plt.subplots(1, 2, figsize=(12, 4)) + +# Left: borrower's payoff under the original (S<0) and modified (S=0) contract +for S, lab, col in [(-0.5, r'original, $S<0$', 'C3'), + (0.0, r'modified, $S=0$', 'C0')]: + axes[0].plot(I_vals, W(I_vals, S), color=col, lw=2, label=lab) +axes[0].plot(0.0, W(0.0, 0.0), 'ko', ms=7) +axes[0].annotate('both invest $\\hat I=0$:\nsame realized payoff', + xy=(0.0, W(0.0, 0.0)), xytext=(0.22, W(0.0, 0.0) - 0.12), + arrowprops=dict(arrowstyle='->')) +axes[0].set_xlabel(r'investment $\hat I$') +axes[0].set_ylabel(r'borrower payoff $W(\hat I)$') +axes[0].set_title('a negative spread makes the borrower invest nothing') +axes[0].legend() + +# Right: probability of the high state as a function of investment +axes[1].plot(I_vals, prob_high(I_vals), 'C0', lw=2) +axes[1].plot(0.0, 0.0, 'ko', ms=7) +axes[1].annotate("$\\hat I=0$: high state never occurs,\nso $n_2'$ is off-path", + xy=(0.0, 0.0), xytext=(0.16, 0.5), + arrowprops=dict(arrowstyle='->')) +axes[1].set_xlabel(r'investment $\hat I$') +axes[1].set_ylabel(r'$\lambda(\hat I)=\Pr(Y_2|\hat I)$') +axes[1].set_title(r'the high state has zero probability at $\hat I=0$') + +plt.tight_layout() +plt.show() +``` + +The left panel plots the borrower's payoff $W(\hat I)$ from investing $\hat I$, +under the original contract (red, $S < 0$) and the modified one (blue, $S = 0$). + +Both are decreasing, so under either contract the borrower invests $\hat I = 0$ +and obtains the same realized payoff: the two contracts are identical on the path +that actually occurs. + +The right panel says why the modification is costless --- at $\hat I = 0$ the +high-output state has probability $\lambda(0) = 0$, so it is never reached. + +The continuation value $n_2'$ attached to that state is therefore pure off-path +bookkeeping, and the planner can slide it up to $n_1'$ --- raising the spread to +$S = 0$ --- without changing anything the borrower or the lender ever +experiences. + +At an interior optimum {eq}`eq:tsyrennikov_relaxed_ic` holds with equality, +giving the first-order condition used in the computation, + +$$ +\theta\,u'(c) += \beta\,\lambda'(I)\,\bigl[v(n_2') - v(n_1')\bigr], +$$ (eq:tsyrennikov_foa) where $n_j' = Y_j - d_j$ is next period's net worth after state $j$. -A -higher spread $V(n_2') - V(n_1')$ — more reward in the high state — -supports a higher investment level. +A larger continuation-value spread $v(n_2') - v(n_1')$ means a larger reward +after high output and supports a higher investment level. ## The Euler equation and implied interest rate -The Euler equation (Appendix A of {cite:t}`Tsyrennikov2013`) for the MH model is +To characterize the optimal contract, attach multipliers to the constraints of +problem {eq}`eq:tsyrennikov_bellman`. + +Let $\kappa \geq 0$ be the multiplier on lender participation +{eq}`eq:tsyrennikov_lender_ir`, $\phi \geq 0$ the multiplier on the endowment +limit $b \leq M$, $\mu \geq 0$ the multiplier on the relaxed incentive +constraint {eq}`eq:tsyrennikov_relaxed_ic`, and $\xi_j \geq 0$ the multiplier on +each enforcement constraint {eq}`eq:tsyrennikov_enforcement`. + +The Lagrangian is + +$$ +\begin{aligned} +\mathcal{L} =\ & u(n+b-\theta I) + \beta \sum_j g(Y_j\mid I)\,v(Y_j-d_j) + + \kappa\Bigl(\beta_c \sum_j g(Y_j\mid I)\,d_j - b\Bigr) + \phi\,(M-b) \\ + &+ \mu\Bigl(-\theta u'(n+b-\theta I) + + \beta\lambda'(I)\sum_j \Delta g_j\,v(Y_j-d_j)\Bigr) + + \beta \sum_j g(Y_j\mid I)\,\xi_j\bigl(v(Y_j-d_j) + - v_{\text{aut}}^{\delta}(Y_j)\bigr). +\end{aligned} +$$ (eq:tsyrennikov_lagrangian) + +The envelope theorem gives $v'(n) = u'(c) - \mu\theta u''(c)$ +({ref}`Exercise 3 `). + +The first-order condition for $b$ then yields $$ -V'(n) \;=\; V'(n_j')\!\left[1 + \mu\, - \frac{\lambda'(I)\,\Delta g_j}{g(Y_j\mid I)}\right] + \phi, +v'(n) = \kappa + \phi, $$ -where $\mu \geq 0$ is the multiplier on the FOA constraint and $\phi \geq 0$ -on the lender endowment $b \leq M$. +and the first-order condition for each $d_j$ yields -Because $\Delta g_1 = -1 < 0$, the factor for the low state is less than one: -$V'(n_1') > V'(n)$. +$$ +\kappa = \frac{\beta}{\beta_c}\,v'(n_j') + \left[(1+\xi_j) + \mu\,\frac{\lambda'(I)\,\Delta g_j}{g(Y_j\mid I)}\right]. +$$ + +Combining the two delivers the **Euler equation** + +$$ +v'(n) = \frac{\beta}{\beta_c}\,v'(n_j') + \left[(1+\xi_j) + \mu\,\frac{\lambda'(I)\,\Delta g_j}{g(Y_j\mid I)}\right] + + \phi. +$$ (eq:tsyrennikov_euler) + +As a useful special case, in the pure moral-hazard economy the enforcement +multipliers vanish ($\xi_j = 0$), leaving + +$$ +v'(n) = \frac{\beta}{\beta_c}\,v'(n_j') + \left[1 + \mu\,\frac{\lambda'(I)\,\Delta g_j}{g(Y_j\mid I)}\right] + \phi. +$$ + +In the calibration $\beta/\beta_c \approx 0.99$, so the leading factor is just +below one, which on its own makes net worth drift down slowly. + +Because $\Delta g_1 = -1 < 0$, the low-output likelihood term is negative. -By concavity of $V$, the borrower's net worth falls in -the low state. +When the endowment constraint is slack ($\phi = 0$) and the bracket is positive, +the equation pushes $v'(n_1')$ above $v'(n)$. -This is the **immiseration** property: moral hazard forces -the borrower to bear more risk than would be optimal with full information -(cf.\ {cite}`ThomasWorrall1990`, {cite}`AtkesonLucas1992`). +By concavity of $v$, the borrower's net worth then falls in the low state. -The borrower faces an **implied interest rate** +This is the **immiseration** property: moral hazard forces the borrower to bear +more risk than would be optimal with full information +(see, e.g., {cite:t}`ThomasWorrall1990`, {cite:t}`AtkesonLucas1992`). + +To isolate this force, set $\beta = \beta_c$, $\phi = 0$ and $\xi_j = 0$. + +Multiplying the Euler equation by $g(Y_j\mid I)$ and summing over $j$ gives + +$$ +v'(n) = \mathbb{E}\,v'(n_j') + \mu\,\lambda'(I)\sum_j \Delta g_j\,v'(n_j') + \;\leq\; \mathbb{E}\,v'(n_j'), +$$ + +because the last term is nonpositive: $\Delta g$ shifts probability toward high +output, where continuation net worth is higher and $v'$ is lower. + +So $v'(n)$ is a submartingale and, by concavity, expected net worth drifts +downward. + +Limited enforcement without moral hazard reverses the sign. + +Setting $\mu = 0$ (again with $\beta = \beta_c$ and $\phi = 0$) leaves +$v'(n) = \mathbb{E}\,v'(n_j') + \sum_j g(Y_j\mid I)\,\xi_j v'(n_j') +\geq \mathbb{E}\,v'(n_j')$, since $\xi_j \geq 0$, which implies upward drift in +continuation net worth under concavity. + +The optimal contract can be reinterpreted as a government-borrower that, instead +of signing a contract, faces an **implied interest rate** schedule $R(n)$ on each +unit borrowed: $$ R(n) \;=\; \frac{u'(c(n))}{\beta\,\sum_j g(Y_j\mid I(n))\,u'(c(n_j'(n)))}, $$ -where $c(n_j'(n))$ is next period's consumption if state $j$ is realised. +where $c(n_j'(n))$ is next period's consumption if state $j$ is realized. -This rate is counter-cyclical: when $n$ is low, past incentive provision has +This rate is countercyclical: when $n$ is low, past incentive provision has depressed the continuation values, raising the marginal utility spread and increasing $R$. ## Computation -We now implement these ideas numerically using the parameterisation from +We now implement a lightweight numerical illustration using the parameterisation +from {cite:t}`Tsyrennikov2013`. + +The code solves three economies: + +1. **MH**: moral hazard only, with the lender endowment constraint $b \leq M$. +2. **MH+LE**: moral hazard and limited enforcement, without the exogenous + lender endowment constraint. +3. **LE**: limited enforcement only, again without the exogenous lender + endowment constraint. + +In the two limited-enforcement economies, the value constraint +{eq}`eq:tsyrennikov_enforcement` is imposed through the endogenous borrowing +limit {eq}`eq:tsyrennikov_borrowing_limit`. + +### Algorithm + +The state is current net worth $n$. + +For each $n$, the code searches over continuation net worths +$(n_1', n_2')$. + +In the moral-hazard economies, the first-order approach determines the +recommended investment for each candidate continuation pair. + +In the LE economy, investment is contractible, so the planner chooses it +directly from its first-order condition. + +For the pure MH economy, the loan is the smaller of the lender-participation +amount and the endowment $M$. + +For MH+LE and LE, borrowing is limited endogenously by the borrower's default +value. + +The resulting policy functions are intended to show the economic mechanism and +to move the lecture figures closer to Figures 3 and 4 of {cite:t}`Tsyrennikov2013`. +They should still not be read as a full replication of the paper's numerical +algorithm, which uses cubic splines and tighter convergence tolerances. + +### Parameters + In addition to what's in Anaconda, this lecture will need the following library: ```{code-cell} ipython3 @@ -217,29 +687,34 @@ In addition to what's in Anaconda, this lecture will need the following library: !pip install jax ``` -### Parameters +The computation uses JAX to vectorize the Bellman updates. + +We will use the following imports: ```{code-cell} ipython3 import numpy as np from typing import NamedTuple -from scipy.interpolate import interp1d from jax import config config.update("jax_enable_x64", True) import jax import jax.numpy as jnp import matplotlib.pyplot as plt +``` -# Model parameters +We store the parameters in a `NamedTuple`, with defaults calibrated to Argentina +as in {cite:t}`Tsyrennikov2013`. + +```{code-cell} ipython3 class Model(NamedTuple): - β: float # borrower discount factor - β_c: float # lender (world) discount factor - γ: float # CRRA coefficient - θ: float # investment resource cost (θ in budget n+b = c+θI) - ν: float # λ(I) = I^ν (probability of high output) - δ: float # borrower keeps fraction δ of output on default - M: float # lender endowment - Y1: float # low output state - Y2: float # high output state + β: float # borrower discount factor + β_c: float # lender discount factor + γ: float # CRRA coefficient + θ: float # resource cost of investment + ν: float # curvature in λ(I) = I^ν + δ: float # fraction of output retained after default + M: float # lender endowment + Y1: float # low output state + Y2: float # high output state def create_model(β=0.980, β_c=0.990, γ=2.0, θ=0.105, ν=0.950, @@ -265,255 +740,649 @@ model = create_model() model.ν, model.δ, model.M, model.Y1, model.Y2) Y = np.array([Y1, Y2]) +print(f"Output states: Y1 = {Y1:.4f}, Y2 = {Y2:.4f}") +print(f"β = {β}, β_c = {β_c}, γ = {γ}, θ = {θ}, ν = {ν}") +``` -# Investment technology: λ(I) = I^ν (probability of high output) -def λ(I): - return np.minimum(I**ν, 1.0) +Next we define the model primitives. + +The probability of high output is $\lambda(I) = \min(I^\nu, 1)$, period utility +`u` is CRRA, and `u_prime` is its derivative $u'(c) = c^{-\gamma}$. + +We write each with `jnp`, so the same function runs inside the JIT-compiled +Bellman updates and on the plain arrays used in the plotting and simulation +code. -def λ_jax(I): +```{code-cell} ipython3 +def λ(I): + """Probability of high output, λ(I) = min{I^ν, 1}.""" return jnp.minimum(I**ν, 1.0) -def dλ(I): - """λ'(I) = ν * I^{ν−1} (for I > 0).""" - return ν * I**(ν - 1.0) -# Utility def u(c): - c = np.maximum(c, 1e-12) - return c**(1.0 - γ) / (1.0 - γ) - -def u_jax(c): + """CRRA period utility.""" c = jnp.maximum(c, 1e-12) return c**(1.0 - γ) / (1.0 - γ) -def u_prime(c): - return np.maximum(c, 1e-12)**(-γ) -def u_prime_jax(c): +def u_prime(c): + """Marginal utility u'(c) = c^{-γ}.""" return jnp.maximum(c, 1e-12)**(-γ) +``` + +Finally we build the grids. +`n_grid` discretizes net worth, `I_search_grid` is the investment grid used by +the autarky step, and the mesh `(n1p_candidates, n2p_candidates)` holds the +candidate continuation pairs $(n_1', n_2')$ searched by the moral-hazard step. + +```{code-cell} ipython3 # Net-worth grid -N_n = 150 -n_lo = 0.08 -n_hi = 1.30 +N_n = 100 +n_lo = 0.20 +n_hi = 1.20 n_grid = np.linspace(n_lo, n_hi, N_n) n_grid_j = jnp.asarray(n_grid) -# Search grids used by Bellman operators below. -N_I_search = 500 +# Investment search grid used by the autarky Bellman step +N_I_search = 350 I_search_grid = np.linspace(0.0, 1.0, N_I_search) I_search_grid_j = jnp.asarray(I_search_grid) -N_policy = 90 -n1p_candidates = np.linspace(max(δ * Y1, n_lo), +# Mesh of candidate continuation pairs (n_1', n_2') for the MH step +N_policy = 70 +n1p_candidates = np.linspace(n_lo, min(Y1 * 1.1, n_hi - 1e-4), N_policy) -n2p_candidates = np.linspace(max(δ * Y2, n_lo), +n2p_candidates = np.linspace(n_lo, min(Y2 * 1.05, n_hi - 1e-4), N_policy) n1p_mesh, n2p_mesh = np.meshgrid(n1p_candidates, n2p_candidates, indexing='ij') n1p_flat_j = jnp.asarray(n1p_mesh.ravel()) n2p_flat_j = jnp.asarray(n2p_mesh.ravel()) - -print(f"Output states: Y1 = {Y1:.4f}, Y2 = {Y2:.4f}") -print(f"β = {β}, β_c = {β_c}, γ = {γ}, θ = {θ}, ν = {ν}") ``` +The grid sizes above are deliberately modest so the lecture can execute quickly. + +For a closer replication of the paper's spline computation, increase `N_n`, +`N_policy`, `N_I_search`, and the `CONTRACT_MAX_ITER` value used by the +contract solvers below. + ### Autarky value function +We solve the autarky problem {eq}`eq:tsyrennikov_autarky` by value function +iteration. + +The Bellman step is vectorized: it evaluates every net-worth state against the +whole investment search grid at once and keeps the best investment. + +Because the borrower has no credit in autarky, next period's net worth is just +the realized output, so the continuation values are simply $v(Y_1)$ and +$v(Y_2)$. + ```{code-cell} ipython3 @jax.jit -def autarky_step_jax(V, β_val): - """One vectorised Bellman step for the autarky problem.""" - EV1 = jnp.interp(Y1, n_grid_j, V) - EV2 = jnp.interp(Y2, n_grid_j, V) +def autarky_step_jax(v, β_val): + """One vectorized Bellman step for the autarky problem.""" + # Continuation values: next-period net worth is the realized output + Ev1 = jnp.interp(Y1, n_grid_j, v) + Ev2 = jnp.interp(Y2, n_grid_j, v) + # Evaluate every (net worth, investment) pair on the search grid I = I_search_grid_j[None, :] c = n_grid_j[:, None] - θ * I - l = λ_jax(I) - obj = u_jax(c) + β_val * ((1.0 - l) * EV1 + l * EV2) - obj = jnp.where(c > 1e-10, obj, -jnp.inf) + l = λ(I) + obj = u(c) + β_val * ((1.0 - l) * Ev1 + l * Ev2) + + # Investment cannot exceed net worth and consumption must be positive + feasible = (I <= n_grid_j[:, None]) & (c > 1e-10) + obj = jnp.where(feasible, obj, -jnp.inf) idx = jnp.argmax(obj, axis=1) return jnp.max(obj, axis=1), I_search_grid_j[idx] -def autarky_policy(V_arr, β_val=None): +def autarky_policy(v_arr, β_val=None): """Return the autarky value update and investment policy on n_grid.""" if β_val is None: β_val = β - V_new, I_pol = autarky_step_jax(jnp.asarray(V_arr), β_val) - return np.asarray(V_new), np.asarray(I_pol) + v_new, I_pol = autarky_step_jax(jnp.asarray(v_arr), β_val) + return np.asarray(v_new), np.asarray(I_pol) +``` +We iterate the step to convergence. -def autarky_bellman_at_n(n, Vf, β_val=None): - """ - Solve the autarky Bellman at state n given current iterate Vf. - Returns (V_new, I_opt). - Uses the fact that continuation values only depend on Y1, Y2. - """ +```{code-cell} ipython3 +def autarky_vfi(β_val=None, tol=1e-8, max_iter=3000, verbose=False): + """Value function iteration for the autarky problem.""" if β_val is None: β_val = β - EV1 = float(Vf(Y1)) - EV2 = float(Vf(Y2)) - I_max = min(max(n / θ - 1e-8, 0.0), 1.0) - I = I_search_grid[I_search_grid <= I_max] - if I.size == 0: - I = np.array([0.0]) + v = jnp.zeros(N_n) + for it in range(max_iter): + v_new, _ = autarky_step_jax(v, β_val) + diff = float(jnp.max(jnp.abs(v_new - v))) + v = v_new + if diff < tol: + if verbose: + print( + f"Autarky VFI converged in {it+1} iterations " + f"(diff = {diff:.2e})" + ) + break + + return np.asarray(v) + + +v_aut = autarky_vfi(verbose=True) +``` + +### Default values and borrowing limits + +Limited enforcement is imposed by updating the minimum continuation net worth +that keeps the borrower from defaulting. - c = n - θ * I - obj = u(c) + β_val * ((1.0 - λ(I)) * EV1 + λ(I) * EV2) - idx = np.argmax(obj) - return float(obj[idx]), float(I[idx]) +If $V$ is the current contract value and +$v_{\text{aut}}^\delta(Y_j)$ is the value of defaulting in state $j$, the +borrowing-limit form of the enforcement constraint is + +$$ +n_j' \geq V^{-1}\!\left(v_{\text{aut}}^\delta(Y_j)\right). +$$ +The code below computes the two default values and updates these two lower +boundaries during value function iteration. -def autarky_vfi(β_val=None, tol=1e-8, max_iter=3000): +```{code-cell} ipython3 +_, I_aut = autarky_policy(v_aut) + + +def default_values(v_aut_arr, β_val=None): + """Values after default, including the one-period output loss δ.""" if β_val is None: β_val = β - V = jnp.zeros(N_n) - for it in range(max_iter): - V_new, _ = autarky_step_jax(V, β_val) - diff = float(jnp.max(jnp.abs(V_new - V))) - V = V_new - if diff < tol: - print(f"Autarky VFI converged in {it+1} iterations (diff = {diff:.2e})") - break + Ev1 = np.interp(Y1, n_grid, v_aut_arr) + Ev2 = np.interp(Y2, n_grid, v_aut_arr) + vals = [] + for Yj in Y: + I = I_search_grid + c = δ * Yj - θ * I + l = np.minimum(I**ν, 1.0) + c_safe = np.maximum(c, 1e-12) + util = c_safe**(1.0 - γ) / (1.0 - γ) + obj = util + β_val * ((1 - l) * Ev1 + l * Ev2) + feasible = (I <= Yj) & (c > 1e-10) + vals.append(float(np.max(np.where(feasible, obj, -np.inf)))) + return np.asarray(vals) + + +def inverse_value(v_arr, target): + """Approximate V^{-1}(target) on the net-worth grid.""" + v_mono = np.maximum.accumulate(v_arr) + return float(np.interp(target, v_mono, n_grid, + left=n_grid[0], right=n_grid[-1])) - return np.asarray(V) +def borrowing_limit_nbars(v_arr, v_default): + """Minimum feasible continuation net worths implied by enforcement.""" + return np.asarray([inverse_value(v_arr, val) for val in v_default]) -V_aut = autarky_vfi() + +v_aut_delta = default_values(v_aut) +print("Default values:", np.round(v_aut_delta, 4)) ``` -### Moral hazard model +### Contracting models -For each state $n$, we optimise over continuation states -$(n_1', n_2')$ where $n_j' = Y_j - d_j$. For every candidate -$(n_1', n_2')$: +For moral hazard, we use the first-order approach. -1. Compute $\Delta V = V(n_2') - V(n_1')$. -2. With lender participation binding, the loan is - $b^* = \beta_c\bigl[(1-\lambda(I))(Y_1-n_1') + \lambda(I)(Y_2-n_2')\bigr]$. -3. Substitute into the FOA equation and solve for $I^*$: +The pure MH economy evaluates two loan regimes for each candidate +continuation pair: $$ -\theta\,\bigl[A + \lambda(I^*)\,\Delta B - \theta I^*\bigr]^{-\gamma} - \;=\; \beta\,\lambda'(I^*)\,\Delta V, +b = \beta_c\,\mathbb E[d_j] +\quad\text{and}\quad +b = M. $$ -where $A \equiv n + \beta_c (Y_1-n_1')$ and -$\Delta B \equiv \beta_c\bigl[(Y_2-n_2') - (Y_1-n_1')\bigr]$. +The first is lender participation binding; the second is the lender endowment +constraint binding. + +For MH+LE, we set the exogenous cap to a very large value and rely on the +endogenous borrowing limits instead. -This reduces the optimisation to two dimensions. +For LE, investment is observable, so the planner chooses it directly. ```{code-cell} ipython3 +BIG_LOAN_CAP = 1e6 +CONTRACT_TOL = 1e-5 +CONTRACT_MAX_ITER = 10_000 + + +def contract_initial_upper(β_val, loan_upper): + """High initial value; starting too low can converge back to autarky.""" + c_upper = n_hi + loan_upper + return np.full(N_n, float(u(c_upper)) / (1.0 - β_val)) + + @jax.jit -def mh_bellman_step_jax(V, V_aut_arr, β_val, β_c_val): - """ - One vectorised Bellman step for the moral-hazard model. +def mh_bellman_step_jax(v, v_aut_arr, I_aut_arr, nbar1, nbar2, + loan_cap, β_val, β_c_val): + """One Bellman step for MH, with optional LE bounds and loan cap.""" + v1 = jnp.interp(n1p_flat_j, n_grid_j, v) + v2 = jnp.interp(n2p_flat_j, n_grid_j, v) + Δv = v2 - v1 + + d1 = Y1 - n1p_flat_j + d2 = Y2 - n2p_flat_j + enforce_feasible = ((n1p_flat_j[None, :] >= nbar1 - 1e-10) + & (n2p_flat_j[None, :] >= nbar2 - 1e-10)) + shape_ref = n_grid_j[:, None] + 0.0 * n1p_flat_j[None, :] + I_hi_base = jnp.ones_like(shape_ref) * (1.0 - 1e-6) + + def lender_value(I): + l = λ(I) + return β_c_val * ((1 - l) * d1[None, :] + l * d2[None, :]) + + def solve_mh_root(c_of_I): + I_hi = I_hi_base + + def shrink_hi(_, I_hi_val): + return jnp.where(c_of_I(I_hi_val) < 1e-8, + 0.9 * I_hi_val, I_hi_val) + + I_hi = jax.lax.fori_loop(0, 35, shrink_hi, I_hi) + I_lo = jnp.full_like(I_hi, 1e-7) + + def foa(I): + λ_prime = ν * jnp.maximum(I, 1e-12)**(ν - 1.0) + return θ * u_prime(c_of_I(I)) - β_val * λ_prime * Δv[None, :] + + foa_lo = foa(I_lo) + foa_hi = foa(I_hi) + valid = ((Δv[None, :] > 1e-10) & (I_hi > 1e-6) + & (foa_lo < 0.0) & (foa_hi > 0.0) + & (c_of_I(I_hi) > 1e-8)) + + def bisect_body(_, state): + lo, hi = state + mid = 0.5 * (lo + hi) + f_mid = foa(mid) + hi = jnp.where(f_mid > 0.0, mid, hi) + lo = jnp.where(f_mid > 0.0, lo, mid) + return lo, hi + + I_lo, I_hi = jax.lax.fori_loop(0, 35, bisect_body, (I_lo, I_hi)) + return 0.5 * (I_lo + I_hi), valid + + # Regime 1: lender participation binds. + def c_lp(I): + return n_grid_j[:, None] + lender_value(I) - θ * I + + I_lp, valid_lp = solve_mh_root(c_lp) + b_lp = lender_value(I_lp) + + # Regime 2: the exogenous loan cap binds. This regime is inactive when + # loan_cap is set to BIG_LOAN_CAP. + def c_cap(I): + return n_grid_j[:, None] + loan_cap - θ * I + + I_cap, valid_cap = solve_mh_root(c_cap) + b_cap = jnp.full_like(I_cap, loan_cap) + + def evaluate(I_star, b, valid): + c = n_grid_j[:, None] + b - θ * I_star + l = λ(I_star) + Ev = (1 - l) * v1[None, :] + l * v2[None, :] + obj = u(c) + β_val * Ev + ic_feasible = I_star <= n_grid_j[:, None] + b + 1e-6 + feasible = (valid & enforce_feasible & ic_feasible + & (c > 1e-10) & (b >= -1e-8)) + return jnp.where(feasible, obj, -jnp.inf) + + obj_lp = evaluate(I_lp, b_lp, valid_lp & (b_lp <= loan_cap + 1e-7)) + cap_has_resources = lender_value(I_cap) >= loan_cap - 1e-7 + obj_cap = evaluate(I_cap, b_cap, valid_cap & cap_has_resources) + + use_cap = obj_cap > obj_lp + obj = jnp.where(use_cap, obj_cap, obj_lp) + I_all = jnp.where(use_cap, I_cap, I_lp) + b_all = jnp.where(use_cap, b_cap, b_lp) - For each candidate pair (n_1', n_2'), the FOA for I is solved by a - compiled bisection. The Bellman maximisation is then a batch grid search - over continuation states. - """ - V1 = jnp.interp(n1p_flat_j, n_grid_j, V) - V2 = jnp.interp(n2p_flat_j, n_grid_j, V) - ΔV = V2 - V1 + idx = jnp.argmax(obj, axis=1) + best_val = jnp.max(obj, axis=1) + has_feasible = jnp.isfinite(best_val) + use_fallback = (~has_feasible) | (best_val <= v_aut_arr) - A = n_grid_j[:, None] + β_c_val * (Y1 - n1p_flat_j)[None, :] - ΔB = β_c_val * ((Y2 - n2p_flat_j) - (Y1 - n1p_flat_j)) + pol_n1p = jnp.where(use_fallback, Y1, n1p_flat_j[idx]) + pol_n2p = jnp.where(use_fallback, Y2, n2p_flat_j[idx]) + pol_I = jnp.where(use_fallback, I_aut_arr, + jnp.take_along_axis(I_all, idx[:, None], axis=1)[:, 0]) + pol_b = jnp.where(use_fallback, 0.0, + jnp.take_along_axis(b_all, idx[:, None], axis=1)[:, 0]) + v_new = jnp.where(use_fallback, v_aut_arr, best_val) + + return v_new, pol_n1p, pol_n2p, pol_I, pol_b + + +@jax.jit +def le_bellman_step_jax(v, v_aut_arr, I_aut_arr, nbar1, nbar2, + β_val, β_c_val): + """One Bellman step for the limited-enforcement-only economy.""" + v1 = jnp.interp(n1p_flat_j, n_grid_j, v) + v2 = jnp.interp(n2p_flat_j, n_grid_j, v) + Δv = v2 - v1 + + d1 = Y1 - n1p_flat_j + d2 = Y2 - n2p_flat_j + Δd = d2 - d1 + A = n_grid_j[:, None] + β_c_val * d1[None, :] + ΔB = β_c_val * Δd + enforce_feasible = ((n1p_flat_j[None, :] >= nbar1 - 1e-10) + & (n2p_flat_j[None, :] >= nbar2 - 1e-10)) def c_of_I(I): return A + (I**ν) * ΔB[None, :] - θ * I - I_hi = jnp.minimum(A / θ * 0.999, 1.0 - 1e-6) - I_hi = jnp.maximum(I_hi, 1e-6) + shape_ref = n_grid_j[:, None] + 0.0 * n1p_flat_j[None, :] + I_hi = jnp.ones_like(shape_ref) * (1.0 - 1e-6) def shrink_hi(_, I_hi_val): - return jnp.where(c_of_I(I_hi_val) < 1e-8, 0.9 * I_hi_val, I_hi_val) + return jnp.where(c_of_I(I_hi_val) < 1e-8, + 0.9 * I_hi_val, I_hi_val) - I_hi = jax.lax.fori_loop(0, 40, shrink_hi, I_hi) + I_hi = jax.lax.fori_loop(0, 35, shrink_hi, I_hi) I_lo = jnp.full_like(I_hi, 1e-7) - def foa(I): - return (θ * u_prime_jax(c_of_I(I)) - - β_val * ν * jnp.maximum(I, 1e-12)**(ν - 1.0) - * ΔV[None, :]) + def marginal(I): + λ_prime = ν * jnp.maximum(I, 1e-12)**(ν - 1.0) + current_gain = u_prime(c_of_I(I)) * (λ_prime * ΔB[None, :] - θ) + continuation_gain = β_val * λ_prime * Δv[None, :] + return current_gain + continuation_gain - foa_lo = foa(I_lo) - foa_hi = foa(I_hi) - valid = ((ΔV[None, :] > 1e-10) & (I_hi > 1e-6) - & (foa_lo < 0.0) & (foa_hi > 0.0)) + f_lo = marginal(I_lo) + f_hi = marginal(I_hi) + has_root = (f_lo > 0.0) & (f_hi < 0.0) def bisect_body(_, state): lo, hi = state mid = 0.5 * (lo + hi) - f_mid = foa(mid) - hi = jnp.where(f_mid > 0.0, mid, hi) - lo = jnp.where(f_mid > 0.0, lo, mid) + f_mid = marginal(mid) + lo = jnp.where(f_mid > 0.0, mid, lo) + hi = jnp.where(f_mid > 0.0, hi, mid) return lo, hi - I_lo, I_hi = jax.lax.fori_loop(0, 45, bisect_body, (I_lo, I_hi)) - I_star = 0.5 * (I_lo + I_hi) - c = c_of_I(I_star) - l = λ_jax(I_star) - b = β_c_val * ((1 - l) * (Y1 - n1p_flat_j)[None, :] - + l * (Y2 - n2p_flat_j)[None, :]) - EV = (1 - l) * V1[None, :] + l * V2[None, :] - obj = u_jax(c) + β_val * EV - - feasible = valid & (c > 1e-10) & (b <= M + 1e-6) + I_lo_b, I_hi_b = jax.lax.fori_loop(0, 35, bisect_body, (I_lo, I_hi)) + I_root = 0.5 * (I_lo_b + I_hi_b) + I_star = jnp.where(f_lo <= 0.0, 0.0, + jnp.where(f_hi >= 0.0, I_hi, I_root)) + + l = λ(I_star) + b = β_c_val * ((1 - l) * d1[None, :] + l * d2[None, :]) + c = n_grid_j[:, None] + b - θ * I_star + Ev = (1 - l) * v1[None, :] + l * v2[None, :] + obj = u(c) + β_val * Ev + feasible = (enforce_feasible & (c > 1e-10) & (b >= -1e-8) + & ((has_root | (f_lo <= 0.0) | (f_hi >= 0.0)))) obj = jnp.where(feasible, obj, -jnp.inf) idx = jnp.argmax(obj, axis=1) best_val = jnp.max(obj, axis=1) has_feasible = jnp.isfinite(best_val) - use_fallback = (~has_feasible) | (best_val <= V_aut_arr) + use_fallback = (~has_feasible) | (best_val <= v_aut_arr) pol_n1p = jnp.where(use_fallback, Y1, n1p_flat_j[idx]) pol_n2p = jnp.where(use_fallback, Y2, n2p_flat_j[idx]) - pol_I = jnp.where(use_fallback, 0.0, + pol_I = jnp.where(use_fallback, I_aut_arr, jnp.take_along_axis(I_star, idx[:, None], axis=1)[:, 0]) pol_b = jnp.where(use_fallback, 0.0, jnp.take_along_axis(b, idx[:, None], axis=1)[:, 0]) - V_new = jnp.where(use_fallback, V_aut_arr, best_val) + v_new = jnp.where(use_fallback, v_aut_arr, best_val) - return V_new, pol_n1p, pol_n2p, pol_I, pol_b + return v_new, pol_n1p, pol_n2p, pol_I, pol_b -def mh_vfi(V_aut, β_val=None, β_c_val=None, tol=1e-3, max_iter=60, - relaxation=0.5): - """Value function iteration for the moral hazard model.""" +def update_nbars(v_arr, nbars, v_default, relaxation=0.5): + """Damped update of endogenous borrowing limits.""" + target = borrowing_limit_nbars(v_arr, v_default) + target = np.clip(target, n_lo, n_hi) + return (1 - relaxation) * nbars + relaxation * target + + +def mh_vfi(v_aut, β_val=None, β_c_val=None, tol=CONTRACT_TOL, + max_iter=CONTRACT_MAX_ITER, + relaxation=0.6, limited_enforcement=False, loan_cap=M, + verbose=False, return_limits=False): + """Value function iteration for MH and MH+LE.""" if β_val is None: β_val = β if β_c_val is None: β_c_val = β_c - V = V_aut.copy() + _, I_aut_arr = autarky_policy(v_aut, β_val=β_val) + v_default = default_values(v_aut, β_val=β_val) + nbars = np.array([n_lo, n_lo]) + loan_upper = loan_cap if loan_cap < BIG_LOAN_CAP / 2 else Y2 - n_lo + v = contract_initial_upper(β_val, loan_upper) + label = 'MH+LE' if limited_enforcement else 'MH' + for it in range(max_iter): - V_raw, pol_n1p, pol_n2p, pol_I, pol_b = mh_bellman_step_jax( - jnp.asarray(V), jnp.asarray(V_aut), β_val, β_c_val) - V_raw = np.asarray(V_raw) - V_new = (1 - relaxation) * V + relaxation * V_raw - diff = np.max(np.abs(V_new - V)) - V = V_new - print(f" iter {it+1:3d}, max|ΔV| = {diff:.5f}") + v_raw, pol_n1p, pol_n2p, pol_I, pol_b = mh_bellman_step_jax( + jnp.asarray(v), jnp.asarray(v_aut), jnp.asarray(I_aut_arr), + nbars[0], nbars[1], loan_cap, β_val, β_c_val) + v_raw = np.asarray(v_raw) + v_new = (1 - relaxation) * v + relaxation * v_raw + limit_diff = 0.0 + if limited_enforcement: + nbars_new = update_nbars(v_new, nbars, v_default) + limit_diff = np.max(np.abs(nbars_new - nbars)) + nbars = nbars_new + diff = max(np.max(np.abs(v_new - v)), limit_diff) + v = v_new + if verbose and ((it + 1) % 20 == 0 or diff < tol): + print(f" iter {it+1:3d}, diff = {diff:.5f}, " + f"nbars = {nbars}") if diff < tol: - print(f"MH VFI converged in {it+1} iterations.") break + if verbose: + status = 'converged' if diff < tol else 'stopped' + print( + f"{label} VFI {status} after {it + 1} iterations: " + f"diff = {diff:.3e}, nbars = {np.round(nbars, 4)}" + ) + _, pol_n1p, pol_n2p, pol_I, pol_b = mh_bellman_step_jax( - jnp.asarray(V), jnp.asarray(V_aut), β_val, β_c_val) + jnp.asarray(v), jnp.asarray(v_aut), jnp.asarray(I_aut_arr), + nbars[0], nbars[1], loan_cap, β_val, β_c_val) + + result = (v, np.asarray(pol_n1p), np.asarray(pol_n2p), + np.asarray(pol_I), np.asarray(pol_b)) + if return_limits: + return result + (nbars,) + return result + + +def le_vfi(v_aut, β_val=None, β_c_val=None, tol=CONTRACT_TOL, + max_iter=CONTRACT_MAX_ITER, + relaxation=0.6, verbose=False, return_limits=False): + """Value function iteration for the LE-only economy.""" + if β_val is None: + β_val = β + if β_c_val is None: + β_c_val = β_c + + _, I_aut_arr = autarky_policy(v_aut, β_val=β_val) + v_default = default_values(v_aut, β_val=β_val) + nbars = np.array([n_lo, n_lo]) + v = contract_initial_upper(β_val, Y2 - n_lo) + + for it in range(max_iter): + v_raw, pol_n1p, pol_n2p, pol_I, pol_b = le_bellman_step_jax( + jnp.asarray(v), jnp.asarray(v_aut), jnp.asarray(I_aut_arr), + nbars[0], nbars[1], β_val, β_c_val) + v_raw = np.asarray(v_raw) + v_new = (1 - relaxation) * v + relaxation * v_raw + nbars_new = update_nbars(v_new, nbars, v_default) + limit_diff = np.max(np.abs(nbars_new - nbars)) + nbars = nbars_new + diff = max(np.max(np.abs(v_new - v)), limit_diff) + v = v_new + if verbose and ((it + 1) % 20 == 0 or diff < tol): + print(f" iter {it+1:3d}, diff = {diff:.5f}, " + f"nbars = {nbars}") + if diff < tol: + break + + if verbose: + status = 'converged' if diff < tol else 'stopped' + print( + f"LE VFI {status} after {it + 1} iterations: " + f"diff = {diff:.3e}, nbars = {np.round(nbars, 4)}" + ) + + _, pol_n1p, pol_n2p, pol_I, pol_b = le_bellman_step_jax( + jnp.asarray(v), jnp.asarray(v_aut), jnp.asarray(I_aut_arr), + nbars[0], nbars[1], β_val, β_c_val) + + result = (v, np.asarray(pol_n1p), np.asarray(pol_n2p), + np.asarray(pol_I), np.asarray(pol_b)) + if return_limits: + return result + (nbars,) + return result + + +v_mh, pol_n1p, pol_n2p, pol_I, pol_b = mh_vfi(v_aut, verbose=True) - return (V, np.asarray(pol_n1p), np.asarray(pol_n2p), - np.asarray(pol_I), np.asarray(pol_b)) +(v_mhle, pol_n1p_mhle, pol_n2p_mhle, pol_I_mhle, pol_b_mhle, + nbars_mhle) = mh_vfi(v_aut, limited_enforcement=True, + loan_cap=BIG_LOAN_CAP, verbose=True, + return_limits=True) +(v_le, pol_n1p_le, pol_n2p_le, pol_I_le, pol_b_le, + nbars_le) = le_vfi(v_aut, verbose=True, return_limits=True) +``` + +### Policy diagnostics + +The helper functions below convert policies into repayments, risk-sharing +indices, capital-outflow schedules and implied interest rates. -print("Running moral hazard VFI…") -V_mh, pol_n1p, pol_n2p, pol_I, pol_b = mh_vfi(V_aut) +```{code-cell} ipython3 +def λ_np(I): + """NumPy version of λ for plotting and simulation.""" + return np.minimum(np.asarray(I)**ν, 1.0) + + +def make_policy(name, n1p, n2p, I, b, v, nbars=None): + """Collect a regime's policy arrays and derived schedules.""" + d1 = Y1 - n1p + d2 = Y2 - n2p + l = λ_np(I) + return { + 'name': name, + 'n1p': n1p, + 'n2p': n2p, + 'I': I, + 'b': b, + 'v': v, + 'nbars': nbars, + 'd1': d1, + 'd2': d2, + 'λ': l, + 'Enp': (1 - l) * n1p + l * n2p, + 'RSI': (d2 - d1) / (Y2 - Y1), + 'ca1': Y1 - n_grid - b, + 'ca2': Y2 - n_grid - b + } + + +policies = { + 'MH': make_policy('MH', pol_n1p, pol_n2p, pol_I, pol_b, v_mh), + 'MH+LE': make_policy('MH+LE', pol_n1p_mhle, pol_n2p_mhle, + pol_I_mhle, pol_b_mhle, v_mhle, nbars_mhle), + 'LE': make_policy('LE', pol_n1p_le, pol_n2p_le, + pol_I_le, pol_b_le, v_le, nbars_le) +} + + +def policy_at(policy, key, n): + """Interpolate a policy at net worth n, clipping to the grid support.""" + n_clip = np.clip(n, n_grid[0], n_grid[-1]) + return float(np.interp(n_clip, n_grid, policy[key])) + + +def next_period_c(policy, n_next): + """Consumption at the start of next period given continuation net worth.""" + b_next = policy_at(policy, 'b', n_next) + I_next = policy_at(policy, 'I', n_next) + return n_next + b_next - θ * I_next + + +def implied_R(policy, n): + """Implied one-period gross interest rate at net worth n.""" + b = policy_at(policy, 'b', n) + I = policy_at(policy, 'I', n) + n1p = policy_at(policy, 'n1p', n) + n2p = policy_at(policy, 'n2p', n) + c = n + b - θ * I + l = λ_np(I) + c1p = next_period_c(policy, n1p) + c2p = next_period_c(policy, n2p) + denom = β * ((1 - l) * float(u_prime(c1p)) + + l * float(u_prime(c2p))) + return float(u_prime(c)) / denom if denom > 1e-10 else np.nan + + +def implied_R_schedule(policy): + return np.asarray([implied_R(policy, n) for n in n_grid]) + + +def repeated_low_limit(policy, T=100): + """Approximate the lowest net worth reached after repeated low outputs.""" + n = Y1 + path = [n] + for _ in range(T): + n = policy_at(policy, 'n1p', n) + path.append(n) + return float(np.min(path[-20:])) + + +n_low_mh = repeated_low_limit(policies['MH']) +n_low_le = repeated_low_limit(policies['LE']) +print(f"Approximate low-state limit, MH: {n_low_mh:.4f}") +print(f"Approximate low-state limit, LE: {n_low_le:.4f}") + + +def print_policy_diagnostics(policies): + """Print compact diagnostics for the computed policy functions.""" + print("\nPolicy diagnostics:") + for name, policy in policies.items(): + active = policy['λ'] > 0.01 + rsi_active = policy['RSI'][active] + n1_floor = np.sum(np.isclose(policy['n1p'], n_lo)) + n2_floor = np.sum(np.isclose(policy['n2p'], n_lo)) + nbars = policy['nbars'] + nbars_text = "none" if nbars is None else np.array2string( + np.round(nbars, 4)) + print( + f"{name:5s}: " + f"λ=[{np.nanmin(policy['λ']):.3f}, {np.nanmax(policy['λ']):.3f}], " + f"b=[{np.nanmin(policy['b']):.3f}, {np.nanmax(policy['b']):.3f}], " + f"RSI_active_mean={np.nanmean(rsi_active):.4f}, " + f"RSI_active_max={np.nanmax(rsi_active):.4f}, " + f"n1'=[{np.nanmin(policy['n1p']):.3f}, " + f"{np.nanmax(policy['n1p']):.3f}], " + f"n2'=[{np.nanmin(policy['n2p']):.3f}, " + f"{np.nanmax(policy['n2p']):.3f}], " + f"floor_hits=({n1_floor}, {n2_floor}), " + f"nbars={nbars_text}" + ) + + +print_policy_diagnostics(policies) ``` ### Value functions and insurance @@ -525,163 +1394,184 @@ mystnb: caption: value functions and risk-sharing index name: fig-tsy-value-rsi --- -fig, axes = plt.subplots(1, 2) +fig, axes = plt.subplots(1, 2, figsize=(12, 4)) -# Left: value functions -axes[0].plot(n_grid, V_aut, lw=2, label='Autarky') -axes[0].plot(n_grid, V_mh, lw=2, ls='--', label='Moral hazard') +axes[0].plot(n_grid, v_aut, lw=2, color='0.45', label='Autarky') +for name, style in [('MH', '-'), ('MH+LE', '--'), ('LE', ':')]: + axes[0].plot(n_grid, policies[name]['v'], lw=2, ls=style, label=name) axes[0].set_xlabel('net worth $n$') axes[0].set_ylabel('value') axes[0].legend() -# Right: Risk-sharing index RSI = (d_2 - d_1) / (Y_2 - Y_1) -# d_j = Y_j - n_j', so RSI = (n_1' - n_2') / (Y_2 - Y_1) + 1 -# ... actually RSI = (d_2 - d_1)/(Y_2-Y_1) = ((Y2-n2p)-(Y1-n1p))/(Y2-Y1) -d1_mh = Y1 - pol_n1p -d2_mh = Y2 - pol_n2p -RSI_mh = (d2_mh - d1_mh) / (Y2 - Y1) -active_mh = λ(pol_I) > 0.01 -RSI_mh_plot = np.where(active_mh, RSI_mh, np.nan) - -axes[1].plot(n_grid, RSI_mh_plot, lw=2, color='C1') -axes[1].axhline(1.0, ls=':', color='k', lw=1, label='Full insurance (RSI=1)') -axes[1].axhline(0.0, ls='--', color='k', lw=1, label='Non-contingent debt (RSI=0)') +for name, style in [('MH', '-'), ('MH+LE', '--'), ('LE', ':')]: + active = policies[name]['λ'] > 0.01 + rsi_plot = np.where(active, policies[name]['RSI'], np.nan) + axes[1].plot(n_grid, rsi_plot, lw=2, ls=style, label=name) +axes[1].axhline(1.0, ls=':', color='k', lw=1, + label='Full insurance') +axes[1].axhline(0.0, ls='--', color='k', lw=1, + label='Non-contingent debt') axes[1].set_xlabel('net worth $n$') axes[1].set_ylabel('risk-sharing index') -axes[1].set_ylim(-0.1, 1.2) +axes[1].set_ylim(-0.15, 1.15) axes[1].legend() plt.tight_layout() plt.show() -print(f"\nMean RSI (active-investment states): {np.nanmean(RSI_mh_plot):.4f}") -print("→ Repayment is nearly state non-contingent (RSI ≈ 0)") -print("→ Moral hazard justifies why simple non-contingent debt is optimal") +for name in ['MH', 'MH+LE', 'LE']: + active = policies[name]['λ'] > 0.01 + rsi_active = policies[name]['RSI'][active] + print(f"{name:5s}: mean RSI = {np.mean(rsi_active): .4f}, " + f"max RSI = {np.max(rsi_active): .4f}") ``` -A key finding of {cite:t}`Tsyrennikov2013` emerges immediately: the risk-sharing -index is close to *zero* on the active-investment region. +In {cite:t}`Tsyrennikov2013`, the moral-hazard economy has essentially +state non-contingent repayment: the maximal risk-sharing index is below 0.01. + +In the limited-enforcement economy, by contrast, the same index is about 0.80 +on average, so the contract offers a significant amount of insurance. + +The compact grid computation should be read against that benchmark. -Moral hazard requires -spreading continuation values to incentivise investment, but this is achieved -by differentiating *net worth* $n_j'$, not repayment $d_j = Y_j - n_j'$. +The diagnostic to watch is not the exact maximum at grid endpoints, but whether +the repayment schedule $\{d_1(n), d_2(n)\}$ is nearly state non-contingent under +moral hazard and much more state contingent under limited enforcement. -The near-equality $d_1 \approx d_2$ means repayment is essentially -*non-contingent on output* — the model rationalises why emerging market -borrowers use plain debt instruments rather than GDP-linked securities. +This is the paper's central policy result: under moral hazard nearly all the +risk is assumed by the risk-averse borrower, and insurance comes mainly through +access to borrowing rather than through state-contingent repayment. -### Optimal investment +### Policy functions + +The next figure follows the structure and terminology of Figure 3 in +{cite:t}`Tsyrennikov2013`. + +Panel F multiplies the MH and MH+LE risk-sharing indices by 10, as in the +paper, so their near-zero variation is visible on the same scale as LE. ```{code-cell} ipython3 --- mystnb: figure: - caption: optimal investment and continuation net worth - name: fig-tsy-investment + caption: policy functions in the MH, MH+LE and LE economies + name: fig-tsy-policy-functions --- -fig, axes = plt.subplots(1, 2) - -# Compute autarky optimal investment -_, I_aut = autarky_policy(V_aut) - -axes[0].plot(n_grid, λ(I_aut), lw=2, label='Autarky') -axes[0].plot(n_grid, λ(pol_I), lw=2, ls='--', label='Moral hazard') -axes[0].set_xlabel('net worth $n$') -axes[0].set_ylabel(r'$\lambda(I) = \Pr(Y_2 \mid I)$') -axes[0].legend() - -axes[1].plot(n_grid, pol_n1p, lw=2, - label=r"$n_1' = Y_1 - d_1$ (after low output)") -axes[1].plot(n_grid, pol_n2p, lw=2, ls='--', - label=r"$n_2' = Y_2 - d_2$ (after high output)") -axes[1].plot(n_grid, n_grid, lw=1, ls=':', color='k', label='45° line') -axes[1].set_xlabel('net worth $n$') -axes[1].set_ylabel("continuation net worth $n_j'$") -axes[1].legend(fontsize=10) +fig, axes = plt.subplots(3, 2, figsize=(12, 10), sharex=True) +ax = axes.ravel() + +for a in ax: + a.axvline(n_low_mh, color='0.25', lw=1, ls='--') + a.axvline(n_low_le, color='0.55', lw=1, ls=':') + a.set_xlim(0.38, 1.02) + +ax[0].plot(n_grid, policies['MH']['λ'], lw=2, label='MH') +ax[0].plot(n_grid, policies['MH+LE']['λ'], lw=2, ls='--', label='MH+LE') +ax[0].plot(n_grid, policies['LE']['λ'], lw=2, ls=':', label='LE') +ax[0].set_ylabel(r'$\lambda(I)$') +ax[0].set_title('A. investment') +ax[0].legend(fontsize=9) + +for name, style in [('MH', '-'), ('MH+LE', '--'), ('LE', ':')]: + ax[1].plot(n_grid, policies[name]['Enp'], lw=2, ls=style, label=name) +ax[1].plot(n_grid, n_grid, color='k', lw=1, ls=':', label='45-degree') +ax[1].set_ylabel(r"$E[n']$") +ax[1].set_title('B. expected future net worth') +ax[1].legend(fontsize=9) + +ax[2].plot(n_grid, policies['MH']['b'], lw=2, label=r'$b_{MH}$') +ax[2].plot(n_grid, policies['MH']['d1'], lw=2, ls='--', label=r'$d_{1,MH}$') +ax[2].plot(n_grid, policies['MH']['d2'], lw=2, ls=':', label=r'$d_{2,MH}$') +ax[2].set_ylabel('loan and repayment') +ax[2].set_title('C. MH contract') +ax[2].legend(fontsize=9) + +ax[3].plot(n_grid, policies['LE']['b'], lw=2, label=r'$b_{LE}$') +ax[3].plot(n_grid, policies['LE']['d1'], lw=2, ls='--', label=r'$d_{1,LE}$') +ax[3].plot(n_grid, policies['LE']['d2'], lw=2, ls=':', label=r'$d_{2,LE}$') +ax[3].set_ylabel('loan and repayment') +ax[3].set_title('D. LE contract') +ax[3].legend(fontsize=9) + +ax[4].plot(n_grid, policies['MH']['ca1'], lw=2, label=r'$ca_{1,MH}$') +ax[4].plot(n_grid, policies['MH']['ca2'], lw=2, ls='--', label=r'$ca_{2,MH}$') +ax[4].plot(n_grid, policies['LE']['ca1'], lw=2, ls=':', label=r'$ca_{1,LE}$') +ax[4].plot(n_grid, policies['LE']['ca2'], lw=2, ls='-.', label=r'$ca_{2,LE}$') +ax[4].axhline(0, color='k', lw=0.8) +ax[4].set_xlabel('net worth $n$') +ax[4].set_ylabel('capital outflows') +ax[4].set_title('E. capital outflows') +ax[4].legend(fontsize=8) + +ax[5].plot(n_grid, 10 * policies['MH']['RSI'], lw=2, label=r'$10\times$ MH') +ax[5].plot(n_grid, 10 * policies['MH+LE']['RSI'], lw=2, ls='--', + label=r'$10\times$ MH+LE') +ax[5].plot(n_grid, policies['LE']['RSI'], lw=2, ls=':', label='LE') +ax[5].axhline(0, color='k', lw=0.8) +ax[5].set_xlabel('net worth $n$') +ax[5].set_ylabel('risk-sharing index') +ax[5].set_title('F. risk sharing') +ax[5].legend(fontsize=9) plt.tight_layout() plt.show() ``` -Investment under moral hazard is *lower* than in autarky at high net worth -levels and more sensitive to $n$ at low levels. +Panel A plots the optimal weight on the high-output outcome, +$\lambda(I(n))$. -After a low output -realisation, net worth drops sharply ($n_1' \ll n$), depressing future -investment and perpetuating the crisis — the model's **internal propagation -mechanism**. +In the MH economy, investment is sensitive to the financial position of the +borrower at low levels of net worth. -### Implied interest rate +This positive-slope part of the investment policy is the paper's internal +propagation mechanism: after a low-output realization, net worth declines, +investment declines, and probability weight shifts toward the low-output +outcome. -```{code-cell} ipython3 ---- -mystnb: - figure: - caption: implied interest rate and spread - name: fig-tsy-interest-rate ---- -# Compute implied interest rate R(n) -# R(n) = u'(c(n)) / [β * Σ_j g_j(I(n)) * u'(c'(n_j'(n)))] -# where c'(n_j') = n_j' + b*(n_j') - θ I*(n_j') (next period's consumption) - -pol_b_fn = interp1d(n_grid, pol_b, fill_value='extrapolate', bounds_error=False) -pol_I_fn = interp1d(n_grid, pol_I, fill_value='extrapolate', bounds_error=False) -pol_n1p_fn = interp1d(n_grid, pol_n1p, fill_value='extrapolate', bounds_error=False) -pol_n2p_fn = interp1d(n_grid, pol_n2p, fill_value='extrapolate', bounds_error=False) - -def next_period_c(np_val): - """Consumption at the start of next period given continuation n'.""" - b_next = float(pol_b_fn(np_val)) - I_next = float(pol_I_fn(np_val)) - return np_val + b_next - θ * I_next - -R_n = np.empty(N_n) -for k, n in enumerate(n_grid): - b = pol_b[k] - I = pol_I[k] - c = n + b - θ * I - l = λ(I) - n1p = pol_n1p[k] - n2p = pol_n2p[k] - c1p = next_period_c(n1p) - c2p = next_period_c(n2p) - denom = β * ((1-l)*u_prime(c1p) + l*u_prime(c2p)) - R_n[k] = u_prime(c) / denom if denom > 1e-10 else np.nan - -# Annualised spread over world rate -R_world = 1.0 / β_c # gross world rate -spread_ann = (R_n**4 - R_world**4) # approximate annualised spread - -fig, axes = plt.subplots(1, 2) - -axes[0].plot(n_grid, R_n, lw=2) -axes[0].axhline(R_world, ls='--', color='k', lw=1, - label=f'World rate $1/\\beta_c = {R_world:.3f}$') -axes[0].set_xlabel('net worth $n$') -axes[0].set_ylabel('implied gross interest rate $R(n)$') -axes[0].legend() +Panel B plots expected future net worth, +$E[Y_j-d_j(n)]$. -axes[1].plot(n_grid, np.clip(spread_ann * 100, -1, 50), lw=2) -axes[1].axhline(0, ls='--', color='k', lw=0.8) -axes[1].set_xlabel('net worth $n$') -axes[1].set_ylabel('annualised spread over world rate (%)') +The MH schedule lies below the LE schedule at high net worth, so net worth +drifts down faster in the MH economy. -plt.tight_layout() -plt.show() -``` +At low net worth, expected future net worth can decrease with current net worth +because the endogenous improvement in the output distribution raises the +probability of the large repayment. + +Panels C and D plot the optimal loan and repayment schedules. + +In the MH economy, $b(n)$, $d_1(n)$ and $d_2(n)$ are close to one another: the +contract is close to state non-contingent debt. + +In the LE economy, repayment varies much more across output states, reflecting +the larger amount of insurance provided by the contract. + +The LE investment schedule is also higher and less volatile: investment is +observable, so the creditor can dictate more investment than the borrower would +choose under moral hazard. + +Panel E plots capital outflows, denoted $ca_j(n)$ in the paper. + +Current output matters because it determines the repayment due on the previous +contract. + +At high net worth, the insurance effect dominates, so capital outflows are more +positively related to output. -The interest rate spread rises sharply at low net worth levels, consistent with -the Argentine data. +At low net worth, the incentive effect becomes stronger: a low-output realization +must reduce the borrower's net worth, which can increase capital outflows. -The mechanism is the **MH Euler equation**: when $n$ is -low, the borrower's continuation value is depressed and the spread in marginal -utilities across future states increases $R(n)$. +Panel F is the risk-sharing index. + +In the paper, this panel is the visual counterpart to the state +non-contingency result: RSI is close to zero in the MH economy and much larger +in the LE economy. ### Crisis dynamics -{cite:t}`Tsyrennikov2013` shows that a string of low output realisations +{cite:t}`Tsyrennikov2013` shows that a string of low output realizations generates gradual debt accumulation followed by a sudden stop in which capital -inflows cease and interest rates spike — a pattern consistent with the +inflows cease and interest rates spike --- a pattern consistent with the Argentina 2001 experience. ```{code-cell} ipython3 @@ -691,159 +1581,184 @@ mystnb: caption: simulated crisis dynamics name: fig-tsy-crisis --- -def simulate_crisis(T_crisis=8): +def simulate_crisis(policy, T_crisis=8): """ - Simulate crisis path: T_crisis periods of low output (Y_1) starting - from zero debt (n_0 = Y2, high initial net worth). + Simulate T_crisis periods of low output Y1, starting with zero debt. """ - n = Y2 # start with high net worth + n = Y1 records = {'n': [n], 'debt_over_Y': [], 'R': [], 'ca_over_Y': [], 'λ': []} - for t in range(T_crisis): - b = float(pol_b_fn(n)) - I = float(pol_I_fn(n)) - n1p = float(pol_n1p_fn(n)) - n2p = float(pol_n2p_fn(n)) - - c = n + b - θ * I - l = λ(I) - c1p = next_period_c(n1p) - c2p = next_period_c(n2p) - denom = β * ((1-l)*u_prime(c1p) + l*u_prime(c2p)) - R = u_prime(c) / denom if denom > 1e-10 else np.nan - - # Debt = promised repayment − principal rolled over - # Approximate debt/output = b / Y1 (loan at current period) - debt_Y = b / Y1 + for _ in range(T_crisis): + b = policy_at(policy, 'b', n) + I = policy_at(policy, 'I', n) + n1p = policy_at(policy, 'n1p', n) - # Current account = d_t − b_t (repayment received − new loan given) - # At t=0, d_0 = 0 (no old contract); approximate d_t = Y1 - n - d_approx = Y1 - n - ca = d_approx - b + debt_Y = (Y1 - n) / Y1 + ca = Y1 - n - b + R = implied_R(policy, n) records['debt_over_Y'].append(debt_Y) records['R'].append(R) records['ca_over_Y'].append(ca / Y1) - records['λ'].append(l) + records['λ'].append(λ_np(I)) - n = n1p # low output path + n = n1p + records['n'].append(n) - records['n'] += [float(pol_n1p_fn(records['n'][-1]))] return records -crisis = simulate_crisis(T_crisis=8) -t_ax = np.arange(len(crisis['R'])) +crises = {name: simulate_crisis(policy, T_crisis=8) + for name, policy in policies.items()} +t_ax = np.arange(8) +styles = {'MH': ('o-', 'C0'), 'MH+LE': ('s--', 'C1'), 'LE': ('^:', 'C2')} + +fig, axes = plt.subplots(2, 2, figsize=(12, 8), sharex=True) + +for name, crisis in crises.items(): + marker, color = styles[name] + axes[0, 0].plot(t_ax, crisis['debt_over_Y'], marker, + lw=2, color=color, label=name) +axes[0, 0].set_ylabel('debt / output') +axes[0, 0].legend(fontsize=9) + +for name, crisis in crises.items(): + marker, color = styles[name] + axes[0, 1].plot(t_ax, np.asarray(crisis['R'])**4, marker, + lw=2, color=color, label=name) +axes[0, 1].axhline((1 / β_c)**4, ls='--', color='k', lw=0.8, + label='World rate') +axes[0, 1].set_ylabel('annualized gross rate') +axes[0, 1].legend(fontsize=9) + +for name, crisis in crises.items(): + marker, color = styles[name] + axes[1, 0].plot(t_ax, crisis['ca_over_Y'], marker, + lw=2, color=color, label=name) +axes[1, 0].axhline(0, ls='--', color='k', lw=0.8) +axes[1, 0].set_xlabel('quarter') +axes[1, 0].set_ylabel('current account / output') + +for name, crisis in crises.items(): + marker, color = styles[name] + axes[1, 1].plot(t_ax, crisis['λ'], marker, + lw=2, color=color, label=name) +axes[1, 1].set_xlabel('quarter') +axes[1, 1].set_ylabel(r'$\lambda(I) = \Pr(Y_2 \mid I)$') -fig, axes = plt.subplots(2, 2, sharex=True) +plt.tight_layout() +plt.show() -axes[0,0].plot(t_ax, crisis['debt_over_Y'], 'o-', lw=2) -axes[0,0].set_ylabel('debt / output') +for name, crisis in crises.items(): + low_path_prob = np.prod(1 - np.asarray(crisis['λ'])) + print(f"{name:5s}: probability of this low-output path = " + f"{low_path_prob:.4f}") +``` -axes[0,1].plot(t_ax, np.array(crisis['R'])**4 * 100, 's-', lw=2, color='C1') -axes[0,1].axhline((1/β_c)**4 * 100, ls='--', color='k', lw=0.8, - label='World rate') -axes[0,1].set_ylabel('annualised gross rate (%)') -axes[0,1].legend(fontsize=9) +The simulation should be read in the same way as Figure 4 in the paper. -axes[1,0].plot(t_ax, crisis['ca_over_Y'], '^-', lw=2, color='C2') -axes[1,0].axhline(0, ls='--', color='k', lw=0.8) -axes[1,0].set_xlabel('quarter') -axes[1,0].set_ylabel('current account / output') +Starting from zero debt, a path of low-output realizations makes the MH economy +steadily accumulate obligations. -axes[1,1].plot(t_ax, crisis['λ'], 'D-', lw=2, color='C3') -axes[1,1].set_xlabel('quarter') -axes[1,1].set_ylabel(r'$\lambda(I) = \Pr(Y_2 \mid I)$') +Debt/output and the current account move before the interest rate does. -plt.tight_layout() -plt.show() -``` +When the borrower nearly exhausts borrowing capacity, the interest rate jumps. + +Thus the interest rate is a **late warning** about the economy's health, unlike +debt and the current account. + +Panel C shows the current account first increasing gradually, meaning that +capital inflows gradually shrink, and then moving sharply when borrowing +capacity is nearly exhausted. -The simulation reproduces the stylised crisis pattern of {cite:t}`Tsyrennikov2013`, -Fig. 4: +Panel D shows the probability of the high-output outcome. -- **Panel A**: Debt steadily accumulates as the borrower is pushed toward the - borrowing limit by repeated low output. -- **Panel B**: Interest rates remain near the world rate initially but spike - sharply once the borrower approaches the borrowing limit — the - **late-warning** property. -- **Panel C**: The current account first worsens gradually (capital inflows - shrink) and then abruptly turns around as the borrowing limit is reached. -- **Panel D**: Investment collapses as net worth falls, further reducing the - probability of high future output — the **internal propagation mechanism**. +As the borrower's net worth deteriorates, investment falls and +$\lambda(I)$ falls, making the low-output path more likely than it would be in +the frictionless or LE economies. + +The paper reports the MH+LE economy as visually close to the MH economy, so the +main comparison is between MH and LE. ### MH versus limited enforcement -A crucial result of {cite:t}`Tsyrennikov2013` is that *limited enforcement adds -little* to the model's performance relative to moral hazard alone. +A crucial result of {cite:t}`Tsyrennikov2013` is that limited enforcement, +alone or together with moral hazard, has nearly no effect on the model's +performance relative to moral hazard alone. -Under LE (no moral hazard), optimal repayments are *highly state -contingent* (RSI ≈ 0.8), providing near-full insurance. +The reason is visible in the Euler equations. -The borrower's net -worth drifts *upward* under LE (unlike MH where it drifts downward), so -interest rate spreads are transitory rather than persistent. +Moral hazard and limited enforcement push the dynamics in opposite directions. -The following code illustrates the key theoretical distinction by computing -the Euler equation implications under each friction separately. +Moral hazard requires the creditor to spread the continuation value of the +borrower across future states, which unloads risk onto the borrower and produces +immiseration. -```{code-cell} ipython3 ---- -mystnb: - figure: - caption: expected continuation net worth - name: fig-tsy-mh-le ---- -# Illustrate the Euler equation implications theoretically -fig, ax = plt.subplots() +Limited enforcement without moral hazard pushes expected net worth upward until +the enforcement constraints become non-operative. -# Under MH (μ > 0, γ_j = 0): -# V'(n) = V'(n_j') [1 + μ λ'(I) Δg_j / g_j(I)] + φ -# → low-state factor < 1: V'(n1') > V'(n) → n1' < n (net worth falls) -# → high-state factor > 1: V'(n2') < V'(n) → n2' > n (net worth rises) -# -# Under LE (μ = 0, γ_j > 0): -# V'(n) = V'(n_j') [1 + γ_j] + φ ≥ V'(n_j') -# → V'(n_j') ≤ V'(n) → n_j' ≥ n (net worth drifts upward) +When both frictions are present, limited enforcement can **turn off** moral +hazard near the borrowing limits: the borrowing limits already spread +continuation values enough to provide incentives, so the incentive multiplier +collapses. -# Stylised illustration using the computed MH policy -Vf_mh = interp1d(n_grid, V_mh, fill_value='extrapolate', bounds_error=False) +## Quantitative comparison -expected_np_mh = (1 - λ(pol_I)) * pol_n1p + λ(pol_I) * pol_n2p +The paper's simulation results show why the moral-hazard mechanism matters. -ax.plot(n_grid, expected_np_mh, lw=2, label='MH: E[n\']') -ax.plot(n_grid, n_grid, lw=1, ls=':', color='k', label='45° line') +The table below reports a reduced version of the model moments. -# Under LE (approximate): net worth is a supermartingale, E[n'] ≥ n * β/β_c -expected_np_le = n_grid * (β / β_c) -ax.plot(n_grid, expected_np_le, lw=2, ls='--', color='C2', - label=r'LE: E[n\'] $\approx$ $(\beta/\beta_c)\,n$ (drifts up)') +The MH+LE economy is very close to the MH economy in the paper's simulations, +so the reduced table reports the MH column as the relevant moral-hazard +benchmark. -ax.set_xlabel('current net worth $n$') -ax.set_ylabel("expected continuation net worth $E[n']$") -ax.legend(fontsize=10) -plt.tight_layout() -plt.show() -``` +| moment | data | MH, i.i.d. | LE, i.i.d. | MH, persistent | LE, persistent | +| --- | ---: | ---: | ---: | ---: | ---: | +| $E(r)$ | 8.18 | 3.84 | 4.27 | 3.26 | 4.01 | +| $\sigma(r)$ | 4.73 | 5.20 | 2.51 | 4.73 | 1.96 | +| $\sigma(c)/\sigma(y)$ | 1.11 | 0.72 | 0.24 | 0.84 | 0.40 | +| $\rho(c,y)$ | 0.97 | 0.82 | 0.62 | 0.96 | 0.67 | +| $\rho(r,y)$ | -0.58 | -0.68 | -0.81 | -0.74 | -0.42 | +| $\rho(tb,y)$ | -0.81 | 0.69 | 0.98 | 0.62 | 0.92 | +| $\rho(y)$ | 0.94 | 0.28 | 0.07 | 0.75 | 0.63 | + +Moral hazard raises consumption-output comovement and generates volatile, +countercyclical spreads. -Under moral hazard, $\mathbb{E}[n']$ is pulled below $n$ in the -high-net-worth active contracting region: net worth drifts down and the -borrower spends substantial time near the borrowing limit, generating -persistent interest rate spreads. +It also creates endogenous output persistence even when the exogenous shocks +are i.i.d. -Under limited enforcement, -$\mathbb{E}[n'] \geq (\beta/\beta_c)\,n$: net worth drifts toward a stationary -level and the borrower eventually escapes financial stress. +The persistent-shocks extension strengthens this result. + +The paper replaces the i.i.d. transition law with + +$$ +\Pr(Y_2 \mid I, Y_1) = \lambda(I)(1-p), +\qquad +\Pr(Y_2 \mid I, Y_2) = p + \lambda(I)(1-p), +$$ + +using $p=0.50$ in the recalibration. + +This persistent component improves the fit for consumption and output. + +The current-account problem remains. + +In the data, the trade balance is strongly countercyclical, while the model's +trade balance is still procyclical in the reported simulations. + +Tsyrennikov argues that stochastic growth or multiperiod capital would likely +be needed to address this shortcoming. ## Empirical test {cite:t}`Tsyrennikov2013` proposes a test to distinguish moral hazard from limited enforcement. -After a low past output realisation ($y_{t-1} = Y_1$), +After a low past output realization ($y_{t-1} = Y_1$), the MH contract lowers net worth sharply, reducing future consumption -smoothing. +smoothing. This prediction is: @@ -853,28 +1768,46 @@ $$ $$ while the LE economy gives the opposite ordering (insurance is better after -low realisations). +low realizations). -Using Argentine quarterly data (1993–2005), the observed -correlations are 0.98 (after low output) vs. 0.91 (after high output) — +Using Argentine quarterly data (1993--2005), the observed +correlations are 0.98 (after low output) vs. 0.91 (after high output) --- *consistent with moral hazard*. +The paper reports the following comparison for the persistent-shocks economy: + +| statistic | data | LE | MH | +| --- | ---: | ---: | ---: | +| $\rho(c_t,y_t \mid y_{t-1}=Y_1)$ | 0.98 | 0.61 | 0.97 | +| $\rho(c_t,y_t \mid y_{t-1}=Y_2)$ | 0.91 | 0.99 | 0.96 | + +The limited-enforcement model predicts stronger consumption-output correlation +after high past output. + +The data show the opposite ordering, which is closer to the moral-hazard +prediction. + +This test relies on the maintained assumption that state-contingent contracting +is feasible. + ## Exercises ```{exercise-start} :label: tsyrennikov_2013_ex1 ``` -**Effect of default penalty.** The parameter $\delta \in (0,1)$ controls -the severity of the output loss upon default. - -1. Compute $V_{\text{aut}}$ for $\delta \in \{0.5,\, 0.795,\, 0.95\}$. -2. For each $\delta$, evaluate the enforcement threshold - $V_{\text{aut}}(\delta Y_1)$ and $V_{\text{aut}}(\delta Y_2)$. -3. Discuss: how does a harsher default penalty affect the tightness of the - enforcement constraint and (via the Euler equation) the interest rate - spread? At $\delta = 1$ the LE constraint becomes - $V(n_j') \geq V_{\text{aut}}(Y_j)$; at $\delta \to 0$ it is vacuous. +**Effect of the default penalty.** The parameter $\delta \in (0,1)$ is the +fraction of output retained after default. + +1. Using $v_{\text{aut}}$, compute + $v_{\text{aut}}^{\delta}(Y_j)$ for + $\delta \in \{0.5,\, 0.795,\, 0.95\}$ and $j=1,2$. +2. For each $\delta$, compare the two enforcement thresholds. +3. Discuss: how does a milder default penalty, corresponding to a larger + $\delta$, affect the tightness of the enforcement constraint and, via the + Euler equation, the interest rate spread? At $\delta = 1$ the LE constraint + becomes $v(n_j') \geq v_{\text{aut}}^{1}(Y_j)$; at $\delta \to 0$ it is + weak. ```{exercise-end} ``` @@ -891,37 +1824,60 @@ mystnb: --- fig, ax = plt.subplots() -Vaut_f_global = interp1d(n_grid, V_aut, fill_value='extrapolate', bounds_error=False) - -for δ_val, ls, color in [(0.50, ':', 'C0'), (0.795, '--', 'C1'), (0.95, '-', 'C2')]: - thresh1 = float(Vaut_f_global(δ_val * Y1)) - thresh2 = float(Vaut_f_global(δ_val * Y2)) - # Net worth lower bound from enforcement: n_j' >= V^{-1}(thresh_j) - # For illustration plot the thresholds - print(f"δ={δ_val:.3f}: V_aut(δ·Y1)={thresh1:.3f}, V_aut(δ·Y2)={thresh2:.3f}") +def default_values_for_delta(δ_val, v_aut_arr, β_val=None): + """Compute v_aut^δ(Y_j), j=1,2, from the paper's default problem.""" + if β_val is None: + β_val = β -ax.plot(n_grid, V_aut, lw=2) -for δ_val, label in [(0.50, 'δ=0.50'), (0.795, 'δ=0.795'), (0.95, 'δ=0.95')]: - t1 = float(Vaut_f_global(δ_val * Y1)) - t2 = float(Vaut_f_global(δ_val * Y2)) - ax.axhline(t1, ls=':', lw=1.5, label=f'{label}: V_aut(δ·Y1)') + Ev1 = np.interp(Y1, n_grid, v_aut_arr) + Ev2 = np.interp(Y2, n_grid, v_aut_arr) + out = [] + + for Yj in (Y1, Y2): + I = I_search_grid[I_search_grid <= min(Yj, 1.0)] + c = δ_val * Yj - θ * I + l = λ(I) + obj = u(c) + β_val * ((1.0 - l) * Ev1 + l * Ev2) + obj = np.where(c > 1e-10, obj, -np.inf) + out.append(float(np.max(obj))) + + return out + +for δ_val in (0.50, 0.795, 0.95): + thresh1, thresh2 = default_values_for_delta(δ_val, v_aut) + print( + f"δ={δ_val:.3f}: v_aut^δ(Y1)={thresh1:.3f}, " + f"v_aut^δ(Y2)={thresh2:.3f}" + ) + +ax.plot(n_grid, v_aut, lw=2) +for δ_val, color in [(0.50, 'C0'), (0.795, 'C1'), (0.95, 'C2')]: + t1, t2 = default_values_for_delta(δ_val, v_aut) + ax.axhline(t1, ls=':', color=color, lw=1.5, + label=fr'$\delta={δ_val}$: $v_{{\rm aut}}^\delta(Y_1)$') + ax.axhline(t2, ls='--', color=color, lw=1.0, + label=fr'$\delta={δ_val}$: $v_{{\rm aut}}^\delta(Y_2)$') -ax.set_xlabel('net worth $n$'); ax.set_ylabel('$V_{\\rm aut}(n)$') +ax.set_xlabel('net worth $n$') +ax.set_ylabel(r'$v_{\rm aut}(n)$') ax.legend(fontsize=9) plt.tight_layout() plt.show() ``` -A harsher default penalty (larger $\delta$) raises the enforcement thresholds, -tightening the participation constraints and reducing the scope for -state-contingent repayment. +A larger $\delta$ means a milder default penalty. + +It raises the enforcement thresholds, tightens the participation constraints, +and reduces the scope for state-contingent repayment. -Paradoxically, this may *reduce* the interest -rate spread by forcing the lender to offer more consumption insurance to keep -the borrower from defaulting. +In the full model, this can make limited enforcement bind before the +moral-hazard constraint does. + +Near the borrowing limit, limited enforcement can already force enough +continuation-value dispersion to reduce the incentive multiplier. At $\delta \to 0$ the enforcement constraint is -vacuous and the model collapses to pure moral hazard. +weak and the model approaches pure moral hazard. ```{solution-end} ``` @@ -932,7 +1888,7 @@ vacuous and the model collapses to pure moral hazard. **Discounting wedge and impatience.** -1. Re-solve the MH model for $\beta = \beta_c = 0.990$ (equal discounting — +1. Re-solve the MH model for $\beta = \beta_c = 0.990$ (equal discounting --- no impatience wedge) and for $\beta = 0.950$ (larger wedge). 2. For each case, plot the expected continuation net worth $\mathbb{E}[n'] = (1-\lambda(I^*))n_1' + \lambda(I^*)n_2'$ against $n$. @@ -940,7 +1896,7 @@ vacuous and the model collapses to pure moral hazard. hazard in determining the stationary distribution of net worth? *Hint*: When $\beta = \beta_c$ the only force pushing net worth down is moral -hazard (immiseration). When $\beta < \beta_c$ there is an additional +hazard (immiseration). When $\beta < \beta_c$ there is an additional front-loading incentive that the lender can exploit. ```{exercise-end} ``` @@ -958,10 +1914,14 @@ mystnb: --- fig, ax = plt.subplots() -for β_val, ls, color in [(0.990, '-', 'C0'), (0.980, '--', 'C1'), (0.950, ':', 'C2')]: - V_a_tmp = autarky_vfi(β_val=β_val) - V_mh_tmp, pol_n1p_tmp, pol_n2p_tmp, pol_I_tmp, _ = mh_vfi( - V_a_tmp, β_val=β_val, max_iter=80) +for β_val, ls, color in [ + (0.990, '-', 'C0'), + (0.980, '--', 'C1'), + (0.950, ':', 'C2') +]: + v_a_tmp = autarky_vfi(β_val=β_val) + v_mh_tmp, pol_n1p_tmp, pol_n2p_tmp, pol_I_tmp, _ = mh_vfi( + v_a_tmp, β_val=β_val) E_np = ((1 - λ(pol_I_tmp)) * pol_n1p_tmp + λ(pol_I_tmp) * pol_n2p_tmp) @@ -982,10 +1942,9 @@ toward the borrowing limit. When $\beta = \beta_c$ moral hazard alone drives immiseration, while impatience accelerates it further. -A small wedge -(as calibrated by Tsyrennikov) is significant: it is *equivalent to -increasing the borrower's discount rate by 2% per annum* (even though -the assumed difference in quarterly rates is only 0.010). +A small wedge, as calibrated by Tsyrennikov, is significant: it is equivalent +to increasing the borrower's discount rate by 2% per annum, even though the +assumed difference in quarterly rates is only 0.010. ```{solution-end} ``` @@ -994,22 +1953,19 @@ the assumed difference in quarterly rates is only 0.010). :label: tsyrennikov_2013_ex3 ``` -**Non-contingency of optimal debt.** - -The *risk-sharing index* $\text{RSI}(n) = (d_2(n) - d_1(n)) / (Y_2 - Y_1)$ -measures how state-contingent the repayment schedule is. +**The envelope condition.** In deriving the Euler equation +{eq}`eq:tsyrennikov_euler` we used the envelope result -RSI = 1 is full -insurance; RSI = 0 is non-contingent debt. +$$ +v'(n) = u'(c) - \mu\,\theta\, u''(c), +\qquad c = n + b - \theta I . +$$ -1. Compute RSI for the MH model you have already solved. -2. Now set $\beta = \beta_c$ (equal discounting) and recompute. Does - removing the impatience wedge change the near-zero RSI result? -3. Explain *theoretically* why moral hazard drives RSI toward zero. +Derive it from the Lagrangian {eq}`eq:tsyrennikov_lagrangian`. -*Hint*: From the Euler equation, the spread in marginal utilities -$u'(c_1') / u'(c_2')$ depends on the IC multiplier $\mu$. A larger $\mu$ -spreads continuation values but *not necessarily* repayments. +*Hint*: By the envelope theorem, differentiate $\mathcal{L}$ with respect to the +state $n$, holding the controls $(b, d, I)$ and the multipliers fixed. Identify +which terms of $\mathcal{L}$ actually depend on $n$. ```{exercise-end} ``` @@ -1017,23 +1973,24 @@ spreads continuation values but *not necessarily* repayments. :class: dropdown ``` -```{code-cell} ipython3 -# RSI for the baseline MH model -d1_mh = Y1 - pol_n1p -d2_mh = Y2 - pol_n2p -RSI = (d2_mh - d1_mh) / (Y2 - Y1) -active_RSI = RSI[λ(pol_I) > 0.01] - -print(f"Baseline MH: mean RSI = {np.mean(active_RSI):.4f}, " - f"max RSI = {np.max(active_RSI):.4f}") -print() -print("Theoretical explanation:") -print(" Under moral hazard the planner must spread V(n2') - V(n1') to") -print(" incentivise investment via the FOA. This is achieved by setting") -print(" n2' > n1', i.e. spreading *net worth*, not repayments.") -print(" Since d_j = Y_j - n_j', if n2' - n1' = Y2 - Y1 then d1 = d2 (RSI=0).") -print(" Moral hazard forces this near-equality, making debt non-contingent.") -``` +The state $n$ enters the Lagrangian {eq}`eq:tsyrennikov_lagrangian` only through +current consumption $c = n + b - \theta I$, and only in two terms: the period +utility $u(n+b-\theta I)$ and the incentive term $-\mu\theta\,u'(n+b-\theta I)$. + +By the envelope theorem we differentiate with respect to $n$ holding the controls +and multipliers fixed. + +Since $\partial c/\partial n = 1$, + +$$ +v'(n) = \frac{\partial \mathcal{L}}{\partial n} + = u'(c)\cdot 1 + \mu\bigl(-\theta\, u''(c)\cdot 1\bigr) + = u'(c) - \mu\,\theta\, u''(c). +$$ + +Every other term depends only on the controls $(b, d, I)$ and on the +continuation values $v(Y_j - d_j)$, none of which involve the current state $n$, +so each contributes zero to $\partial \mathcal{L}/\partial n$. ```{solution-end} ``` From 9b37be794869d832ac520b7d737efa418fb9aa31 Mon Sep 17 00:00:00 2001 From: HumphreyYang Date: Thu, 11 Jun 2026 15:01:12 +1000 Subject: [PATCH 16/25] updates --- lectures/tsyrennikov_2013.md | 704 +++++++++++++++++++++++++++++++---- 1 file changed, 622 insertions(+), 82 deletions(-) diff --git a/lectures/tsyrennikov_2013.md b/lectures/tsyrennikov_2013.md index b6afab43..53cf7a7f 100644 --- a/lectures/tsyrennikov_2013.md +++ b/lectures/tsyrennikov_2013.md @@ -53,6 +53,68 @@ Tsyrennikov is also explicit about the model's main weakness. The mechanism improves the behavior of consumption, output and spreads, but it does not fully match the observed current-account dynamics. +### The mechanism without figures + +A compact way to read the model is as follows. + +The borrower can use foreign funds for either current consumption or investment. +Investment is costly today, but it raises the probability of high output +tomorrow. + +If investment were observable and contracts were fully enforceable, the lender +could insure the borrower almost completely. + +The contract would make the borrower's continuation net worth nearly the same +after low and high output, smoothing consumption across states. + +Moral hazard prevents this. + +When investment is hidden, full insurance gives the borrower too little reason +to invest. + +To make investment privately attractive, the contract must reward high output +with a higher continuation value than low output: + +$$ +v(n_2') > v(n_1'). +$$ + +This continuation-value spread is the borrower's incentive to invest. + +It also means that the risk-averse borrower must bear output risk. + +The optimal contract therefore cannot use much state-contingent repayment to +smooth consumption. + +It ends up looking close to non-contingent debt: repayments vary little across +output states, and insurance comes mainly from access to borrowing rather than +from repayments that adjust strongly to output. + +The same force creates persistence. + +After a low-output realization, the borrower's net worth falls. + +Lower net worth reduces investment, lower investment lowers the probability of +high output, and the economy becomes more likely to remain weak. + +Thus even i.i.d. output shocks generate persistent output dynamics through the +investment channel. + +When net worth is low, the borrower is also closer to its borrowing limit and +continuation values are more distorted by incentive provision. + +This raises the implied interest rate, making spreads high, volatile and +countercyclical. + +Limited enforcement works differently. + +It restricts repayments because the borrower must prefer repayment to default, +but by itself it can still allow substantial state-contingent insurance. + +Tsyrennikov's main quantitative result is that moral hazard, rather than +limited enforcement, is the friction that makes the optimal contract resemble +non-contingent debt and that generates the crisis-like dynamics. + ## Empirical motivation The paper starts from three facts about Argentina, viewed as a representative @@ -444,7 +506,50 @@ contract that is not incentive compatible, which proves the lemma. The subtle part of the argument is why the planner may freely raise $n_2'$. -The figure below illustrates the two facts it rests on. +Here is the same point without using a figure. + +If $S < 0$, then the high-output continuation value is lower than the low-output +continuation value: + +$$ +v(n_2') < v(n_1'). +$$ + +But investment raises the probability of the high-output state. + +So with $S < 0$, investment has two private costs for the borrower. + +It lowers current consumption, because $c = n + b - \theta I$, and it also +raises the probability of receiving the worse continuation value. + +The borrower therefore has no reason to invest and chooses $I=0$. + +At $I=0$, the two-state technology gives $\lambda(0)=0$, so the high-output +state occurs with probability zero. + +This means that the promised high-output continuation $n_2'$ is off the +equilibrium path. + +Changing it does not affect current consumption, the borrower's payoff along the +realized path, or the lender's participation constraint, because the repayment +$d_2$ receives zero probability weight. + +Raising $n_2'$ also relaxes the high-state enforcement constraint, since the +borrower is promised more value in that state. + +Thus the planner can raise $n_2'$ up to $n_1'$ without changing the allocation +that actually occurs. + +After this change the spread is $S=0$ instead of $S<0$. + +Hence any candidate optimum with a negative spread can be replaced by an +equivalent candidate with a nonnegative spread. + +That is why the relaxed problem can focus on $S \geq 0$, where the borrower's +payoff is concave in investment and the first-order condition is sufficient for +incentive compatibility. + +The figure below is only an optional visual check of this logic. ```{code-cell} ipython3 --- @@ -674,8 +779,55 @@ The resulting policy functions are intended to show the economic mechanism and to move the lecture figures closer to Figures 3 and 4 of {cite:t}`Tsyrennikov2013`. -They should still not be read as a full replication of the paper's numerical -algorithm, which uses cubic splines and tighter convergence tolerances. +They should not be read as a full replication of the paper's numerical +algorithm. + +The paper solves the Bellman equation iteratively, approximates the value +function by a cubic spline on $[0.2, 1.2]$ with 100 nodes, and stops when the +sup-norm change in the value function is below $10^{-5}$. + +For the limited-enforcement economies, Appendix B updates the endogenous +borrowing limits with a damped rule that gives one-half weight to the previous +limit and one-half weight to the new limit implied by the current value +function. + +The lecture code below uses the same recursive economic problem, but a more +transparent two-stage approximation. + +First, it computes the fixed point quickly with JAX, Howard policy iteration, +linear interpolation of the value function, and a finite mesh of continuation +net worth pairs $(n_1', n_2')$. + +Second, it polishes the resulting policy functions by re-optimizing each +state's contract locally with SciPy, using a cubic spline approximation to the +converged value function. + +The polishing step parameterizes the continuation pair by the low-state +continuation $n_1'$ and the risk-sharing index + +$$ +\operatorname{RSI} + = \frac{d_2-d_1}{Y_2-Y_1} + = 1 - \frac{n_2'-n_1'}{Y_2-Y_1}. +$$ + +This makes the near-zero moral-hazard risk-sharing result numerically +representable even when the coarse mesh used in the fixed-point step is modest. + +The plotted curves should therefore be read as polished numerical +approximations to the same contract problem, not as a separate economic model. + +To reach a fixed point quickly, all three economies are solved by +**Howard policy iteration** (also called modified policy iteration). + +Each outer iteration takes one greedy Bellman step, which re-optimizes the +contract, and then holds that contract fixed while iterating the value a fixed +number of times. + +Because the Bellman update is linear in the continuation values once the +contract is fixed, these evaluation sweeps are cheap, and the method converges +in a few dozen outer steps rather than the many hundreds that plain value +function iteration would need at this discount factor. ### Parameters @@ -687,7 +839,8 @@ In addition to what's in Anaconda, this lecture will need the following library: !pip install jax ``` -The computation uses JAX to vectorize the Bellman updates. +The computation uses JAX to vectorize the Bellman updates and SciPy for the +cubic splines and local policy-polishing problems. We will use the following imports: @@ -699,6 +852,8 @@ config.update("jax_enable_x64", True) import jax import jax.numpy as jnp import matplotlib.pyplot as plt +from scipy.interpolate import CubicSpline +from scipy.optimize import minimize ``` We store the parameters in a `NamedTuple`, with defaults calibrated to Argentina @@ -790,24 +945,24 @@ I_search_grid = np.linspace(0.0, 1.0, N_I_search) I_search_grid_j = jnp.asarray(I_search_grid) # Mesh of candidate continuation pairs (n_1', n_2') for the MH step -N_policy = 70 -n1p_candidates = np.linspace(n_lo, - min(Y1 * 1.1, n_hi - 1e-4), - N_policy) -n2p_candidates = np.linspace(n_lo, - min(Y2 * 1.05, n_hi - 1e-4), - N_policy) +N_policy = 90 +n1p_candidates = np.linspace(n_lo, n_hi, N_policy) +n2p_candidates = np.linspace(n_lo, n_hi, N_policy) n1p_mesh, n2p_mesh = np.meshgrid(n1p_candidates, n2p_candidates, indexing='ij') n1p_flat_j = jnp.asarray(n1p_mesh.ravel()) n2p_flat_j = jnp.asarray(n2p_mesh.ravel()) ``` -The grid sizes above are deliberately modest so the lecture can execute quickly. +The net-worth grid matches the paper's 100 nodes on $[0.2, 1.2]$. + +The policy mesh is deliberately modest so the lecture can execute quickly, and +it spans the full value-function domain to avoid artificial upper-bound +corners. -For a closer replication of the paper's spline computation, increase `N_n`, -`N_policy`, `N_I_search`, and the `CONTRACT_MAX_ITER` value used by the -contract solvers below. +For a more accurate but slower run, increase `N_policy` and `N_I_search`, +lower `POLISH_TOL`, and tighten the `CONTRACT_TOL` used by the contract solvers +below. ### Autarky value function @@ -955,10 +1110,18 @@ endogenous borrowing limits instead. For LE, investment is observable, so the planner chooses it directly. +Each Bellman step returns both the improved value and the greedy contract, and a +shared routine `policy_eval_jax` then performs the Howard policy-evaluation +sweeps that hold that contract fixed. + +In the two limited-enforcement economies, the endogenous borrowing limits are +refreshed once per outer iteration with a damped update. + ```{code-cell} ipython3 BIG_LOAN_CAP = 1e6 -CONTRACT_TOL = 1e-5 -CONTRACT_MAX_ITER = 10_000 +CONTRACT_TOL = 1e-6 +CONTRACT_MAX_ITER = 1_000 +HOWARD_EVAL_STEPS = 80 def contract_initial_upper(β_val, loan_upper): @@ -1064,7 +1227,7 @@ def mh_bellman_step_jax(v, v_aut_arr, I_aut_arr, nbar1, nbar2, jnp.take_along_axis(b_all, idx[:, None], axis=1)[:, 0]) v_new = jnp.where(use_fallback, v_aut_arr, best_val) - return v_new, pol_n1p, pol_n2p, pol_I, pol_b + return v_new, pol_n1p, pol_n2p, pol_I, pol_b, use_fallback @jax.jit @@ -1141,7 +1304,29 @@ def le_bellman_step_jax(v, v_aut_arr, I_aut_arr, nbar1, nbar2, jnp.take_along_axis(b, idx[:, None], axis=1)[:, 0]) v_new = jnp.where(use_fallback, v_aut_arr, best_val) - return v_new, pol_n1p, pol_n2p, pol_I, pol_b + return v_new, pol_n1p, pol_n2p, pol_I, pol_b, use_fallback + + +@jax.jit +def policy_eval_jax(v, v_aut_arr, pol_n1p, pol_n2p, pol_I, pol_b, + use_fallback, β_val): + """Howard policy evaluation: iterate the value under a fixed policy. + + Once the greedy contract (b, n_1', n_2', I) is fixed, the Bellman update + is linear in the continuation values, so the period return R = u(c) and + the high-output weight λ(I) can be precomputed and the cheap evaluation + sweep applied many times before re-optimizing. + """ + R = u(n_grid_j + pol_b - θ * pol_I) + l = λ(pol_I) + + def eval_step(_, v): + v1 = jnp.interp(pol_n1p, n_grid_j, v) + v2 = jnp.interp(pol_n2p, n_grid_j, v) + v_pol = R + β_val * ((1.0 - l) * v1 + l * v2) + return jnp.where(use_fallback, v_aut_arr, v_pol) + + return jax.lax.fori_loop(0, HOWARD_EVAL_STEPS, eval_step, v) def update_nbars(v_arr, nbars, v_default, relaxation=0.5): @@ -1153,49 +1338,59 @@ def update_nbars(v_arr, nbars, v_default, relaxation=0.5): def mh_vfi(v_aut, β_val=None, β_c_val=None, tol=CONTRACT_TOL, max_iter=CONTRACT_MAX_ITER, - relaxation=0.6, limited_enforcement=False, loan_cap=M, + limited_enforcement=False, loan_cap=M, verbose=False, return_limits=False): - """Value function iteration for MH and MH+LE.""" + """Howard policy iteration for MH and MH+LE.""" if β_val is None: β_val = β if β_c_val is None: β_c_val = β_c _, I_aut_arr = autarky_policy(v_aut, β_val=β_val) + I_aut_j = jnp.asarray(I_aut_arr) + v_aut_j = jnp.asarray(v_aut) v_default = default_values(v_aut, β_val=β_val) nbars = np.array([n_lo, n_lo]) loan_upper = loan_cap if loan_cap < BIG_LOAN_CAP / 2 else Y2 - n_lo - v = contract_initial_upper(β_val, loan_upper) + v = jnp.asarray(contract_initial_upper(β_val, loan_upper)) label = 'MH+LE' if limited_enforcement else 'MH' for it in range(max_iter): - v_raw, pol_n1p, pol_n2p, pol_I, pol_b = mh_bellman_step_jax( - jnp.asarray(v), jnp.asarray(v_aut), jnp.asarray(I_aut_arr), - nbars[0], nbars[1], loan_cap, β_val, β_c_val) - v_raw = np.asarray(v_raw) - v_new = (1 - relaxation) * v + relaxation * v_raw + # Policy improvement: one greedy Bellman step. + (v_greedy, pol_n1p, pol_n2p, pol_I, pol_b, + use_fb) = mh_bellman_step_jax( + v, v_aut_j, I_aut_j, nbars[0], nbars[1], + loan_cap, β_val, β_c_val) + # Policy evaluation: iterate the value under the fixed policy. + v_new = policy_eval_jax(v_greedy, v_aut_j, pol_n1p, pol_n2p, + pol_I, pol_b, use_fb, β_val) limit_diff = 0.0 if limited_enforcement: - nbars_new = update_nbars(v_new, nbars, v_default) + nbars_new = update_nbars(np.asarray(v_new), nbars, v_default) limit_diff = np.max(np.abs(nbars_new - nbars)) nbars = nbars_new - diff = max(np.max(np.abs(v_new - v)), limit_diff) + diff = max(float(jnp.max(jnp.abs(v_new - v))), limit_diff) v = v_new - if verbose and ((it + 1) % 20 == 0 or diff < tol): - print(f" iter {it+1:3d}, diff = {diff:.5f}, " + if verbose and ((it + 1) % 5 == 0 or diff < tol): + print(f" iter {it+1:3d}, diff = {diff:.2e}, " f"nbars = {nbars}") if diff < tol: break + v = np.asarray(v) + if diff >= tol: + raise RuntimeError( + f"{label} HPI failed to converge after {max_iter} iterations " + f"(diff = {diff:.3e})" + ) if verbose: - status = 'converged' if diff < tol else 'stopped' print( - f"{label} VFI {status} after {it + 1} iterations: " + f"{label} HPI converged after {it + 1} iterations: " f"diff = {diff:.3e}, nbars = {np.round(nbars, 4)}" ) - _, pol_n1p, pol_n2p, pol_I, pol_b = mh_bellman_step_jax( - jnp.asarray(v), jnp.asarray(v_aut), jnp.asarray(I_aut_arr), + _, pol_n1p, pol_n2p, pol_I, pol_b, _ = mh_bellman_step_jax( + jnp.asarray(v), v_aut_j, I_aut_j, nbars[0], nbars[1], loan_cap, β_val, β_c_val) result = (v, np.asarray(pol_n1p), np.asarray(pol_n2p), @@ -1207,44 +1402,53 @@ def mh_vfi(v_aut, β_val=None, β_c_val=None, tol=CONTRACT_TOL, def le_vfi(v_aut, β_val=None, β_c_val=None, tol=CONTRACT_TOL, max_iter=CONTRACT_MAX_ITER, - relaxation=0.6, verbose=False, return_limits=False): - """Value function iteration for the LE-only economy.""" + verbose=False, return_limits=False): + """Howard policy iteration for the LE-only economy.""" if β_val is None: β_val = β if β_c_val is None: β_c_val = β_c _, I_aut_arr = autarky_policy(v_aut, β_val=β_val) + I_aut_j = jnp.asarray(I_aut_arr) + v_aut_j = jnp.asarray(v_aut) v_default = default_values(v_aut, β_val=β_val) nbars = np.array([n_lo, n_lo]) - v = contract_initial_upper(β_val, Y2 - n_lo) + v = jnp.asarray(contract_initial_upper(β_val, Y2 - n_lo)) for it in range(max_iter): - v_raw, pol_n1p, pol_n2p, pol_I, pol_b = le_bellman_step_jax( - jnp.asarray(v), jnp.asarray(v_aut), jnp.asarray(I_aut_arr), - nbars[0], nbars[1], β_val, β_c_val) - v_raw = np.asarray(v_raw) - v_new = (1 - relaxation) * v + relaxation * v_raw - nbars_new = update_nbars(v_new, nbars, v_default) + # Policy improvement: one greedy Bellman step. + (v_greedy, pol_n1p, pol_n2p, pol_I, pol_b, + use_fb) = le_bellman_step_jax( + v, v_aut_j, I_aut_j, nbars[0], nbars[1], β_val, β_c_val) + # Policy evaluation: iterate the value under the fixed policy. + v_new = policy_eval_jax(v_greedy, v_aut_j, pol_n1p, pol_n2p, + pol_I, pol_b, use_fb, β_val) + nbars_new = update_nbars(np.asarray(v_new), nbars, v_default) limit_diff = np.max(np.abs(nbars_new - nbars)) nbars = nbars_new - diff = max(np.max(np.abs(v_new - v)), limit_diff) + diff = max(float(jnp.max(jnp.abs(v_new - v))), limit_diff) v = v_new - if verbose and ((it + 1) % 20 == 0 or diff < tol): - print(f" iter {it+1:3d}, diff = {diff:.5f}, " + if verbose and ((it + 1) % 5 == 0 or diff < tol): + print(f" iter {it+1:3d}, diff = {diff:.2e}, " f"nbars = {nbars}") if diff < tol: break + v = np.asarray(v) + if diff >= tol: + raise RuntimeError( + f"LE HPI failed to converge after {max_iter} iterations " + f"(diff = {diff:.3e})" + ) if verbose: - status = 'converged' if diff < tol else 'stopped' print( - f"LE VFI {status} after {it + 1} iterations: " + f"LE HPI converged after {it + 1} iterations: " f"diff = {diff:.3e}, nbars = {np.round(nbars, 4)}" ) - _, pol_n1p, pol_n2p, pol_I, pol_b = le_bellman_step_jax( - jnp.asarray(v), jnp.asarray(v_aut), jnp.asarray(I_aut_arr), + _, pol_n1p, pol_n2p, pol_I, pol_b, _ = le_bellman_step_jax( + jnp.asarray(v), v_aut_j, I_aut_j, nbars[0], nbars[1], β_val, β_c_val) result = (v, np.asarray(pol_n1p), np.asarray(pol_n2p), @@ -1270,18 +1474,291 @@ v_mh, pol_n1p, pol_n2p, pol_I, pol_b = mh_vfi(v_aut, verbose=True) The helper functions below convert policies into repayments, risk-sharing indices, capital-outflow schedules and implied interest rates. +Before constructing the figures, the raw finite-mesh policies are polished by +continuous local optimization. + +This step is controlled by `POLISH_POLICIES` and can be turned off when exact +grid policies are desired. + ```{code-cell} ipython3 def λ_np(I): """NumPy version of λ for plotting and simulation.""" return np.minimum(np.asarray(I)**ν, 1.0) +def λ_prime_np(I): + """Derivative of λ(I)=I^ν on the interior.""" + return ν * np.maximum(np.asarray(I), 1e-12)**(ν - 1.0) + + +def u_np(c): + """NumPy CRRA utility.""" + c = np.maximum(np.asarray(c), 1e-12) + return c**(1.0 - γ) / (1.0 - γ) + + +def u_prime_np(c): + """NumPy marginal utility.""" + return np.maximum(np.asarray(c), 1e-12)**(-γ) + + +POLISH_POLICIES = True +POLISH_TOL = 1e-9 +PLOT_GRID = np.linspace(n_lo, n_hi, 400) +RSI_BOUNDS = (-0.25, 1.00) +Y_gap = Y2 - Y1 + + +def continuation_from_rsi(n1p, rsi): + """Recover n_2' from n_1' and the risk-sharing index.""" + return n1p + (1.0 - rsi) * Y_gap + + +def rsi_from_continuations(n1p, n2p): + """Risk-sharing index implied by continuation net worths.""" + return 1.0 - (n2p - n1p) / Y_gap + + +def value_spline(v_arr): + """Cubic spline approximation to a converged value function.""" + return CubicSpline(n_grid, np.asarray(v_arr), bc_type='natural') + + +def eval_v(vs, n): + """Evaluate a value spline on the supported domain.""" + return float(vs(np.clip(n, n_lo, n_hi))) + + +def bisect_mh_investment(f, lo=1e-7, hi=1.0 - 1e-7, max_iter=70): + """Solve the scalar MH first-order condition robustly.""" + flo = f(lo) + fhi = f(hi) + if not np.isfinite(flo) or not np.isfinite(fhi): + return None + if flo >= 0.0: + return lo + if fhi <= 0.0: + return hi + + for _ in range(max_iter): + mid = 0.5 * (lo + hi) + fmid = f(mid) + if not np.isfinite(fmid): + hi = mid + elif fmid > 0.0: + hi = mid + else: + lo = mid + return 0.5 * (lo + hi) + + +def mh_contract_value(n, n1p, rsi, vs, nbar1, nbar2, + loan_cap, β_val=β, β_c_val=β_c): + """Best MH contract value for a candidate (n_1', RSI).""" + n2p = continuation_from_rsi(n1p, rsi) + if not (nbar1 <= n1p <= n_hi and nbar2 <= n2p <= n_hi): + return None + + v1 = eval_v(vs, n1p) + v2 = eval_v(vs, n2p) + Δv = v2 - v1 + if Δv <= 1e-10: + return None + + d1 = Y1 - n1p + d2 = Y2 - n2p + + def lender_value(I): + l = λ_np(I) + return β_c_val * ((1.0 - l) * d1 + l * d2) + + candidates = [] + + def add_candidate(b_fun, participation_check): + def foc(I): + c = n + b_fun(I) - θ * I + if c <= 1e-10: + return np.inf + return θ * u_prime_np(c) - β_val * λ_prime_np(I) * Δv + + I_star = bisect_mh_investment(foc) + if I_star is None: + return + b_star = b_fun(I_star) + c_star = n + b_star - θ * I_star + if c_star <= 1e-10 or b_star < -1e-8: + return + if not participation_check(I_star, b_star): + return + l_star = λ_np(I_star) + val = u_np(c_star) + β_val * ((1.0 - l_star) * v1 + l_star * v2) + candidates.append((float(val), n1p, n2p, float(I_star), float(b_star))) + + add_candidate( + lender_value, + lambda I, b: b <= loan_cap + 1e-8 + ) + + if loan_cap < BIG_LOAN_CAP / 2: + add_candidate( + lambda I: loan_cap, + lambda I, b: lender_value(I) >= b - 1e-8 + ) + + if not candidates: + return None + return max(candidates, key=lambda x: x[0]) + + +def le_contract_value(n, n1p, rsi, I, vs, nbar1, nbar2, + β_val=β, β_c_val=β_c): + """LE contract value for a candidate (n_1', RSI, I).""" + n2p = continuation_from_rsi(n1p, rsi) + if not (nbar1 <= n1p <= n_hi and nbar2 <= n2p <= n_hi): + return None + + d1 = Y1 - n1p + d2 = Y2 - n2p + l = λ_np(I) + b = β_c_val * ((1.0 - l) * d1 + l * d2) + c = n + b - θ * I + if c <= 1e-10 or b < -1e-8: + return None + + v1 = eval_v(vs, n1p) + v2 = eval_v(vs, n2p) + val = u_np(c) + β_val * ((1.0 - l) * v1 + l * v2) + return float(val), n1p, n2p, float(I), float(b) + + +def polish_mh_state(n, raw_n1p, raw_n2p, vs, nbar1, nbar2, loan_cap): + """Local continuous re-optimization of one MH policy point.""" + raw_rsi = rsi_from_continuations(raw_n1p, raw_n2p) + starts = [ + (raw_n1p, raw_rsi), + (raw_n1p, 0.0), + (raw_n1p, 0.005), + (max(nbar1, raw_n1p - 0.03), 0.0), + (min(n_hi, raw_n1p + 0.03), 0.0) + ] + + def objective(x): + out = mh_contract_value(n, x[0], x[1], vs, nbar1, nbar2, loan_cap) + return 1e8 if out is None else -out[0] + + best = None + for start in starts: + x0 = np.array([np.clip(start[0], nbar1, n_hi), + np.clip(start[1], *RSI_BOUNDS)]) + out0 = mh_contract_value(n, x0[0], x0[1], vs, nbar1, nbar2, loan_cap) + if out0 is not None and (best is None or out0[0] > best[0]): + best = out0 + res = minimize(objective, x0, method='Nelder-Mead', + options={'xatol': POLISH_TOL, 'fatol': POLISH_TOL, + 'maxiter': 500}) + x = np.array([np.clip(res.x[0], nbar1, n_hi), + np.clip(res.x[1], *RSI_BOUNDS)]) + out = mh_contract_value(n, x[0], x[1], vs, nbar1, nbar2, loan_cap) + if out is not None and (best is None or out[0] > best[0]): + best = out + + return best + + +def polish_le_state(n, raw_n1p, raw_n2p, raw_I, vs, nbar1, nbar2): + """Local continuous re-optimization of one LE policy point.""" + raw_rsi = rsi_from_continuations(raw_n1p, raw_n2p) + starts = [ + (raw_n1p, raw_rsi, raw_I), + (raw_n1p, 0.80, raw_I), + (raw_n1p, 1.00, raw_I), + (max(nbar1, raw_n1p - 0.03), 0.80, raw_I), + (min(n_hi, raw_n1p + 0.03), 0.80, raw_I) + ] + + def objective(x): + out = le_contract_value(n, x[0], x[1], x[2], vs, nbar1, nbar2) + return 1e8 if out is None else -out[0] + + best = None + for start in starts: + x0 = np.array([np.clip(start[0], nbar1, n_hi), + np.clip(start[1], *RSI_BOUNDS), + np.clip(start[2], 0.0, 1.0 - 1e-7)]) + out0 = le_contract_value(n, x0[0], x0[1], x0[2], vs, nbar1, nbar2) + if out0 is not None and (best is None or out0[0] > best[0]): + best = out0 + res = minimize(objective, x0, method='Nelder-Mead', + options={'xatol': POLISH_TOL, 'fatol': POLISH_TOL, + 'maxiter': 700}) + x = np.array([np.clip(res.x[0], nbar1, n_hi), + np.clip(res.x[1], *RSI_BOUNDS), + np.clip(res.x[2], 0.0, 1.0 - 1e-7)]) + out = le_contract_value(n, x[0], x[1], x[2], vs, nbar1, nbar2) + if out is not None and (best is None or out[0] > best[0]): + best = out + + return best + + +def polish_mh_policy(v_arr, n1p_arr, n2p_arr, I_arr, b_arr, + nbars=None, loan_cap=M): + """Polish all MH or MH+LE policy points with continuous local search.""" + vs = value_spline(v_arr) + nbar1, nbar2 = (n_lo, n_lo) if nbars is None else nbars + out_v, out_n1p, out_n2p, out_I, out_b = [], [], [], [], [] + failures = 0 + + for n, n1_raw, n2_raw, I_raw, b_raw, v_raw in zip( + n_grid, n1p_arr, n2p_arr, I_arr, b_arr, v_arr): + best = polish_mh_state(n, n1_raw, n2_raw, vs, + nbar1, nbar2, loan_cap) + if best is None: + failures += 1 + best = (v_raw, n1_raw, n2_raw, I_raw, b_raw) + val, n1p, n2p, I, b = best + out_v.append(val) + out_n1p.append(n1p) + out_n2p.append(n2p) + out_I.append(I) + out_b.append(b) + + if failures: + print(f"MH polish fallback points: {failures}") + return map(np.asarray, (out_v, out_n1p, out_n2p, out_I, out_b)) + + +def polish_le_policy(v_arr, n1p_arr, n2p_arr, I_arr, b_arr, nbars): + """Polish all LE policy points with continuous local search.""" + vs = value_spline(v_arr) + nbar1, nbar2 = nbars + out_v, out_n1p, out_n2p, out_I, out_b = [], [], [], [], [] + failures = 0 + + for n, n1_raw, n2_raw, I_raw, b_raw, v_raw in zip( + n_grid, n1p_arr, n2p_arr, I_arr, b_arr, v_arr): + best = polish_le_state(n, n1_raw, n2_raw, I_raw, vs, nbar1, nbar2) + if best is None: + failures += 1 + best = (v_raw, n1_raw, n2_raw, I_raw, b_raw) + val, n1p, n2p, I, b = best + out_v.append(val) + out_n1p.append(n1p) + out_n2p.append(n2p) + out_I.append(I) + out_b.append(b) + + if failures: + print(f"LE polish fallback points: {failures}") + return map(np.asarray, (out_v, out_n1p, out_n2p, out_I, out_b)) + + def make_policy(name, n1p, n2p, I, b, v, nbars=None): """Collect a regime's policy arrays and derived schedules.""" d1 = Y1 - n1p d2 = Y2 - n2p l = λ_np(I) - return { + policy = { 'name': name, 'n1p': n1p, 'n2p': n2p, @@ -1297,6 +1774,24 @@ def make_policy(name, n1p, n2p, I, b, v, nbars=None): 'ca1': Y1 - n_grid - b, 'ca2': Y2 - n_grid - b } + spline_keys = ['n1p', 'n2p', 'I', 'b', 'v', 'd1', 'd2', 'λ', + 'Enp', 'RSI', 'ca1', 'ca2'] + policy['splines'] = { + key: CubicSpline(n_grid, policy[key], bc_type='natural') + for key in spline_keys + } + return policy + + +if POLISH_POLICIES: + v_mh, pol_n1p, pol_n2p, pol_I, pol_b = polish_mh_policy( + v_mh, pol_n1p, pol_n2p, pol_I, pol_b, loan_cap=M) + v_mhle, pol_n1p_mhle, pol_n2p_mhle, pol_I_mhle, pol_b_mhle = ( + polish_mh_policy(v_mhle, pol_n1p_mhle, pol_n2p_mhle, + pol_I_mhle, pol_b_mhle, nbars_mhle, + loan_cap=BIG_LOAN_CAP)) + v_le, pol_n1p_le, pol_n2p_le, pol_I_le, pol_b_le = polish_le_policy( + v_le, pol_n1p_le, pol_n2p_le, pol_I_le, pol_b_le, nbars_le) policies = { @@ -1309,9 +1804,14 @@ policies = { def policy_at(policy, key, n): - """Interpolate a policy at net worth n, clipping to the grid support.""" + """Cubic-spline interpolation of a policy at net worth n.""" n_clip = np.clip(n, n_grid[0], n_grid[-1]) - return float(np.interp(n_clip, n_grid, policy[key])) + return float(policy['splines'][key](n_clip)) + + +def policy_curve(policy, key): + """Evaluate a policy on the dense grid used in the figures.""" + return policy['splines'][key](PLOT_GRID) def next_period_c(policy, n_next): @@ -1351,10 +1851,14 @@ def repeated_low_limit(policy, T=100): n_low_mh = repeated_low_limit(policies['MH']) +n_low_mhle = repeated_low_limit(policies['MH+LE']) n_low_le = repeated_low_limit(policies['LE']) print(f"Approximate low-state limit, MH: {n_low_mh:.4f}") +print(f"Approximate low-state limit, MH+LE: {n_low_mhle:.4f}") print(f"Approximate low-state limit, LE: {n_low_le:.4f}") +low_limits = {'MH': n_low_mh, 'MH+LE': n_low_mhle, 'LE': n_low_le} + def print_policy_diagnostics(policies): """Print compact diagnostics for the computed policy functions.""" @@ -1362,17 +1866,24 @@ def print_policy_diagnostics(policies): for name, policy in policies.items(): active = policy['λ'] > 0.01 rsi_active = policy['RSI'][active] + support_lo = max(0.38, low_limits[name] - 1e-8) + support = ((n_grid >= support_lo) & (n_grid <= 1.02) + & (policy['λ'] > 0.01) & (policy['λ'] < 0.99)) + rsi_support = policy['RSI'][support] n1_floor = np.sum(np.isclose(policy['n1p'], n_lo)) n2_floor = np.sum(np.isclose(policy['n2p'], n_lo)) nbars = policy['nbars'] nbars_text = "none" if nbars is None else np.array2string( np.round(nbars, 4)) + support_max_abs = (np.nan if rsi_support.size == 0 + else np.nanmax(np.abs(rsi_support))) print( f"{name:5s}: " f"λ=[{np.nanmin(policy['λ']):.3f}, {np.nanmax(policy['λ']):.3f}], " f"b=[{np.nanmin(policy['b']):.3f}, {np.nanmax(policy['b']):.3f}], " f"RSI_active_mean={np.nanmean(rsi_active):.4f}, " f"RSI_active_max={np.nanmax(rsi_active):.4f}, " + f"RSI_support_max_abs={support_max_abs:.4f}, " f"n1'=[{np.nanmin(policy['n1p']):.3f}, " f"{np.nanmax(policy['n1p']):.3f}], " f"n2'=[{np.nanmin(policy['n2p']):.3f}, " @@ -1396,17 +1907,20 @@ mystnb: --- fig, axes = plt.subplots(1, 2, figsize=(12, 4)) -axes[0].plot(n_grid, v_aut, lw=2, color='0.45', label='Autarky') +axes[0].plot(PLOT_GRID, CubicSpline(n_grid, v_aut, bc_type='natural')(PLOT_GRID), + lw=2, color='0.45', label='Autarky') for name, style in [('MH', '-'), ('MH+LE', '--'), ('LE', ':')]: - axes[0].plot(n_grid, policies[name]['v'], lw=2, ls=style, label=name) + axes[0].plot(PLOT_GRID, policy_curve(policies[name], 'v'), + lw=2, ls=style, label=name) axes[0].set_xlabel('net worth $n$') axes[0].set_ylabel('value') axes[0].legend() for name, style in [('MH', '-'), ('MH+LE', '--'), ('LE', ':')]: - active = policies[name]['λ'] > 0.01 - rsi_plot = np.where(active, policies[name]['RSI'], np.nan) - axes[1].plot(n_grid, rsi_plot, lw=2, ls=style, label=name) + λ_plot = policy_curve(policies[name], 'λ') + rsi_plot = np.where(λ_plot > 0.01, + policy_curve(policies[name], 'RSI'), np.nan) + axes[1].plot(PLOT_GRID, rsi_plot, lw=2, ls=style, label=name) axes[1].axhline(1.0, ls=':', color='k', lw=1, label='Full insurance') axes[1].axhline(0.0, ls='--', color='k', lw=1, @@ -1422,8 +1936,12 @@ plt.show() for name in ['MH', 'MH+LE', 'LE']: active = policies[name]['λ'] > 0.01 rsi_active = policies[name]['RSI'][active] + support_lo = max(0.38, low_limits[name] - 1e-8) + support = ((n_grid >= support_lo) & (n_grid <= 1.02) + & (policies[name]['λ'] > 0.01) & (policies[name]['λ'] < 0.99)) + rsi_support = policies[name]['RSI'][support] print(f"{name:5s}: mean RSI = {np.mean(rsi_active): .4f}, " - f"max RSI = {np.max(rsi_active): .4f}") + f"max RSI on support = {np.max(np.abs(rsi_support)): .4f}") ``` In {cite:t}`Tsyrennikov2013`, the moral-hazard economy has essentially @@ -1432,11 +1950,16 @@ state non-contingent repayment: the maximal risk-sharing index is below 0.01. In the limited-enforcement economy, by contrast, the same index is about 0.80 on average, so the contract offers a significant amount of insurance. -The compact grid computation should be read against that benchmark. +The polished computation should be read against that benchmark. + +The diagnostic to watch is not the exact maximum at grid endpoints or at +near-certain investment corners, but whether the repayment schedule +$\{d_1(n), d_2(n)\}$ is nearly state non-contingent on the economically relevant +support under moral hazard and much more state contingent under limited +enforcement. -The diagnostic to watch is not the exact maximum at grid endpoints, but whether -the repayment schedule $\{d_1(n), d_2(n)\}$ is nearly state non-contingent under -moral hazard and much more state contingent under limited enforcement. +Any remaining endpoint irregularities should be interpreted as numerical +approximation artifacts rather than features of the contract in the paper. This is the paper's central policy result: under moral hazard nearly all the risk is assumed by the risk-averse borrower, and insurance comes mainly through @@ -1465,48 +1988,65 @@ for a in ax: a.axvline(n_low_le, color='0.55', lw=1, ls=':') a.set_xlim(0.38, 1.02) -ax[0].plot(n_grid, policies['MH']['λ'], lw=2, label='MH') -ax[0].plot(n_grid, policies['MH+LE']['λ'], lw=2, ls='--', label='MH+LE') -ax[0].plot(n_grid, policies['LE']['λ'], lw=2, ls=':', label='LE') +ax[0].plot(PLOT_GRID, policy_curve(policies['MH'], 'λ'), lw=2, label='MH') +ax[0].plot(PLOT_GRID, policy_curve(policies['MH+LE'], 'λ'), + lw=2, ls='--', label='MH+LE') +ax[0].plot(PLOT_GRID, policy_curve(policies['LE'], 'λ'), + lw=2, ls=':', label='LE') ax[0].set_ylabel(r'$\lambda(I)$') ax[0].set_title('A. investment') ax[0].legend(fontsize=9) for name, style in [('MH', '-'), ('MH+LE', '--'), ('LE', ':')]: - ax[1].plot(n_grid, policies[name]['Enp'], lw=2, ls=style, label=name) -ax[1].plot(n_grid, n_grid, color='k', lw=1, ls=':', label='45-degree') + ax[1].plot(PLOT_GRID, policy_curve(policies[name], 'Enp'), + lw=2, ls=style, label=name) +ax[1].plot(PLOT_GRID, PLOT_GRID, color='k', lw=1, ls=':', + label='45-degree') ax[1].set_ylabel(r"$E[n']$") ax[1].set_title('B. expected future net worth') ax[1].legend(fontsize=9) -ax[2].plot(n_grid, policies['MH']['b'], lw=2, label=r'$b_{MH}$') -ax[2].plot(n_grid, policies['MH']['d1'], lw=2, ls='--', label=r'$d_{1,MH}$') -ax[2].plot(n_grid, policies['MH']['d2'], lw=2, ls=':', label=r'$d_{2,MH}$') +ax[2].plot(PLOT_GRID, policy_curve(policies['MH'], 'b'), + lw=2, label=r'$b_{MH}$') +ax[2].plot(PLOT_GRID, policy_curve(policies['MH'], 'd1'), + lw=2, ls='--', label=r'$d_{1,MH}$') +ax[2].plot(PLOT_GRID, policy_curve(policies['MH'], 'd2'), + lw=2, ls=':', label=r'$d_{2,MH}$') ax[2].set_ylabel('loan and repayment') ax[2].set_title('C. MH contract') ax[2].legend(fontsize=9) -ax[3].plot(n_grid, policies['LE']['b'], lw=2, label=r'$b_{LE}$') -ax[3].plot(n_grid, policies['LE']['d1'], lw=2, ls='--', label=r'$d_{1,LE}$') -ax[3].plot(n_grid, policies['LE']['d2'], lw=2, ls=':', label=r'$d_{2,LE}$') +ax[3].plot(PLOT_GRID, policy_curve(policies['LE'], 'b'), + lw=2, label=r'$b_{LE}$') +ax[3].plot(PLOT_GRID, policy_curve(policies['LE'], 'd1'), + lw=2, ls='--', label=r'$d_{1,LE}$') +ax[3].plot(PLOT_GRID, policy_curve(policies['LE'], 'd2'), + lw=2, ls=':', label=r'$d_{2,LE}$') ax[3].set_ylabel('loan and repayment') ax[3].set_title('D. LE contract') ax[3].legend(fontsize=9) -ax[4].plot(n_grid, policies['MH']['ca1'], lw=2, label=r'$ca_{1,MH}$') -ax[4].plot(n_grid, policies['MH']['ca2'], lw=2, ls='--', label=r'$ca_{2,MH}$') -ax[4].plot(n_grid, policies['LE']['ca1'], lw=2, ls=':', label=r'$ca_{1,LE}$') -ax[4].plot(n_grid, policies['LE']['ca2'], lw=2, ls='-.', label=r'$ca_{2,LE}$') +ax[4].plot(PLOT_GRID, policy_curve(policies['MH'], 'ca1'), + lw=2, label=r'$ca_{1,MH}$') +ax[4].plot(PLOT_GRID, policy_curve(policies['MH'], 'ca2'), + lw=2, ls='--', label=r'$ca_{2,MH}$') +ax[4].plot(PLOT_GRID, policy_curve(policies['LE'], 'ca1'), + lw=2, ls=':', label=r'$ca_{1,LE}$') +ax[4].plot(PLOT_GRID, policy_curve(policies['LE'], 'ca2'), + lw=2, ls='-.', label=r'$ca_{2,LE}$') ax[4].axhline(0, color='k', lw=0.8) ax[4].set_xlabel('net worth $n$') ax[4].set_ylabel('capital outflows') ax[4].set_title('E. capital outflows') ax[4].legend(fontsize=8) -ax[5].plot(n_grid, 10 * policies['MH']['RSI'], lw=2, label=r'$10\times$ MH') -ax[5].plot(n_grid, 10 * policies['MH+LE']['RSI'], lw=2, ls='--', +ax[5].plot(PLOT_GRID, 10 * policy_curve(policies['MH'], 'RSI'), + lw=2, label=r'$10\times$ MH') +ax[5].plot(PLOT_GRID, 10 * policy_curve(policies['MH+LE'], 'RSI'), + lw=2, ls='--', label=r'$10\times$ MH+LE') -ax[5].plot(n_grid, policies['LE']['RSI'], lw=2, ls=':', label='LE') +ax[5].plot(PLOT_GRID, policy_curve(policies['LE'], 'RSI'), + lw=2, ls=':', label='LE') ax[5].axhline(0, color='k', lw=0.8) ax[5].set_xlabel('net worth $n$') ax[5].set_ylabel('risk-sharing index') From 4bd303f996f1acc5f8b03df36afe1a2d53a03c6f Mon Sep 17 00:00:00 2001 From: HumphreyYang Date: Thu, 11 Jun 2026 16:43:04 +1000 Subject: [PATCH 17/25] updates --- .../subjective_beliefs_business_cycles.md | 264 ++++++++++++++---- lectures/tsyrennikov_2013.md | 224 +++++---------- 2 files changed, 280 insertions(+), 208 deletions(-) diff --git a/lectures/subjective_beliefs_business_cycles.md b/lectures/subjective_beliefs_business_cycles.md index 3c03d3ec..f711cbac 100644 --- a/lectures/subjective_beliefs_business_cycles.md +++ b/lectures/subjective_beliefs_business_cycles.md @@ -137,11 +137,14 @@ Consumer Expectations (SCE). This evidence supports the interpretation that the wedges reflect a common pessimism/optimism component rather than two unrelated forecast mistakes. -The following code simulates a stylized one-factor wedge process. +The following code simulates a stylized wedge process with the same sample +length, mean wedges, standard deviations, and first-principal-component share +reported in the paper. + +The point is not to recreate the raw Michigan series. -It matches the mean loadings implied by the calibration but abstracts from -measurement error and other idiosyncratic components, so the simulated wedges -are perfectly correlated. +Instead, the simulation separates the common pessimism factor from residual +survey noise, so the wedges are strongly related but not perfectly collinear. ```{code-cell} ipython3 # Parameters from Table 1 @@ -149,10 +152,15 @@ are perfectly correlated. ρ_θ = 0.714 # AR(1) persistence of θ σ_θ = 4.3 # innovation volatility -# Wedge loadings match the empirical means. +# Wedge loadings used later in the model illustrations. c_u = 0.52 / μ_θ c_π = 1.22 / μ_θ +# Empirical wedge moments from Section 2 and Table 2. +mean_u, mean_π = 0.52, 1.22 +std_u, std_π = 0.57, 0.97 +pc1_share_target = 0.786 + T = 152 # 38 years * 4 quarters # Simulate the belief shock. @@ -164,8 +172,36 @@ for t in range(1, T): + ρ_θ * θ[t-1] + σ_θ * rng.standard_normal()) -wedge_u = c_u * θ -wedge_π = c_π * θ +def standardize(x): + """Return a de-meaned series with unit standard deviation.""" + return (x - np.mean(x)) / np.std(x) + + +def orthogonal_noise(rng, *basis, T=T): + """Generate standardized noise orthogonal to the supplied basis series.""" + e = standardize(rng.standard_normal(T)) + for b in basis: + e = e - (e @ b) / (b @ b) * b + return standardize(e) + + +# For two standardized variables, PC1 share = (1 + corr) / 2. +corr_target = 2 * pc1_share_target - 1 +common_weight = np.sqrt(corr_target) +noise_weight = np.sqrt(1 - corr_target) + +common_factor = standardize(θ) +noise_u = orthogonal_noise(rng, common_factor) +noise_π = orthogonal_noise(rng, common_factor, noise_u) + +wedge_u = mean_u + std_u * (common_weight * common_factor + + noise_weight * noise_u) +wedge_π = mean_π + std_π * (common_weight * common_factor + + noise_weight * noise_π) + +wedge_std = np.column_stack([standardize(wedge_u), standardize(wedge_π)]) +eigvals = np.linalg.eigvalsh(np.cov(wedge_std, rowvar=False)) +pc1_share = eigvals[-1] / eigvals.sum() # Quarterly dates, 1982Q1-2019Q4 quarters = [datetime.date(1982 + (q // 4), 3 * (q % 4) + 1, 1) @@ -217,7 +253,8 @@ plt.colorbar(sc, ax=ax, label='quarter (dark = recent)') ax.set_xlabel('unemployment wedge (pp)') ax.set_ylabel('inflation wedge (pp)') corr = np.corrcoef(wedge_u, wedge_π)[0, 1] -ax.text(0.05, 0.93, f'correlation = {corr:.2f}', +ax.text(0.05, 0.90, + f'correlation = {corr:.2f}\nPC1 share = {pc1_share:.3f}', transform=ax.transAxes, fontsize=11) plt.tight_layout() plt.show() @@ -226,19 +263,20 @@ plt.show() The first figure plots the simulated unemployment wedge in the top panel and the simulated inflation wedge in the bottom panel. -The dashed horizontal lines show the sample means. +The dashed horizontal lines show the sample means, which match the values in +the paper. -Both wedges rise and fall together because each is a fixed loading on the same -belief shock $\theta_t$. +Both wedges have a common cyclical component driven by $\theta_t$, but they also +contain residual components that stand in for survey noise and other +idiosyncratic variation. The scatter plot makes this one-factor structure even clearer. Each point is one quarter, with the horizontal coordinate equal to the unemployment wedge and the vertical coordinate equal to the inflation wedge. -The points lie on an upward-sloping line because high-pessimism quarters have -large wedges for both variables, while low-pessimism quarters have small wedges -for both variables. +The points form an upward-sloping cloud rather than a line because the first +principal component accounts for most, but not all, of the variation. This is the one-factor structure that motivates the theoretical framework. @@ -712,13 +750,14 @@ The model is calibrated to quarterly U.S. data, 1982Q1–2019Q4. | Vacancy posting cost | $\kappa_v$ | 0.09 | | | Unemployment benefit flow | $D$ | 0.57 | | -### Pedagogical reduced-form representation +### A paper-calibrated linear surrogate The paper solves a structural New Keynesian model. For computation in this lecture, we use a small reduced-form vector -autoregression that preserves the main qualitative channels and keeps the code -transparent: +autoregression that is calibrated to reproduce the main benchmark moments in +Table 2 and the qualitative shape of the belief-shock impulse responses in +Figure 7: $$ @@ -732,8 +771,21 @@ $\epsilon_{t+1} \sim N(0, I_3)$ contains the three structural shocks. The coefficient matrices $A$ and $B$ are not the paper's structural solution. -They are chosen to give Figure 7-style impulse responses and Table 2-scale -unconditional volatilities. +The full paper obtains them from equilibrium conditions: households and firms +distort probabilities according to continuation values, and those distorted +beliefs feed back into consumption, price setting, vacancy posting, and wages. + +Here we compress those equilibrium channels into a few linear loadings. + +The loadings are chosen so that the Lyapunov moments for unemployment, +inflation, and output match the benchmark and no-belief-shock columns of +Table 2. + +This surrogate is useful for transparent computations, but it should not be +used to analyse the diagnostic variants such as "only $\theta_t$", no TFP +shocks, or rational firms. + +For those variants, we report the paper's structural results directly below. We index the five state variables with named constants, so that later code can refer to, say, the belief shock as `I_THETA` rather than a bare number. @@ -750,6 +802,12 @@ The belief shock $\theta_t$ and TFP $a_t$ follow AR(1) processes; the endogenous variables inherit their own persistence and load on $\theta_t$, $a_t$, and the monetary policy shock. +In the structural model, the effects of $\theta_t$ arise because pessimism +changes the subjective distribution of the fundamental shocks. + +In the surrogate, these effects are summarized by the coefficients +$\phi_{u\theta}$, $\phi_{\pi\theta}$, and $\phi_{y\theta}$. + ```{code-cell} ipython3 class NKModel(NamedTuple): A: np.ndarray # state transition matrix @@ -858,6 +916,42 @@ nk = create_nk_model() ## Quantitative results +### Benchmark fit in the paper + +The key quantitative comparison is Table 2 of {cite}`bhandari2025survey`. + +The rows below reproduce the moments most relevant for this lecture. + +All values are percentages or percentage points; inflation is annualised and +output is detrended. + +| Moment | Data | Paper benchmark | No $\theta_t$ | Only $\theta_t$ | +|---|---:|---:|---:|---:| +| Mean inflation wedge | 1.22 | 0.90 | 0.00 | 0.00 | +| Mean unemployment wedge | 0.52 | 0.55 | 0.00 | 0.00 | +| Volatility of inflation wedge | 0.97 | 0.73 | 0.00 | 0.00 | +| Volatility of unemployment wedge | 0.57 | 0.45 | 0.00 | 0.00 | +| Volatility of inflation | 1.37 | 1.16 | 0.99 | 0.00 | +| Volatility of output | 2.00 | 2.22 | 1.55 | 0.00 | +| Volatility of unemployment | 1.70 | 1.39 | 0.55 | 0.00 | +| Corr. inflation wedge, output | −0.30 | −0.67 | 0.00 | 0.00 | +| Corr. unemployment wedge, output | −0.49 | −0.67 | 0.00 | 0.00 | + +Two lessons are important. + +First, shutting down the belief shock returns the familiar unemployment +volatility puzzle: unemployment volatility falls from 1.39 in the benchmark to +0.55, far below the data value of 1.70. + +Second, the "only $\theta_t$" column is zero in the structural model. + +If TFP and monetary policy uncertainty are absent, there is no payoff-relevant +uncertainty for pessimistic agents to distort, so time variation in +$\theta_t$ alone does not move the economy. + +This is why the full paper emphasizes the interaction between belief shocks and +fundamental shocks, especially TFP shocks. + ### Impulse responses to the belief shock A positive innovation to $\theta_t$ makes households more pessimistic. @@ -875,6 +969,16 @@ mechanism works this way: 4. The belief wedges jump on impact, then decay with the persistence $\rho_\theta = 0.714$. +In the paper's Figure 7, a one-standard-deviation belief shock raises +unemployment by about one percentage point and lowers output by about one +percent. + +Inflation rises briefly and then falls, leaving a roughly zero cumulative +10-quarter response. + +The reduced-form impulse responses below are calibrated to reproduce these +signs and the Table 2 volatility scale. + ```{code-cell} ipython3 --- mystnb: @@ -928,15 +1032,24 @@ The impulse responses show that a belief shock: mirror the dynamics of $\theta_t$ itself --- consistent with the one-factor structure. +The structural model contains one additional object that the surrogate does not +plot: impulse responses under the **subjective** measure. + +After a positive $\theta_t$ innovation, pessimistic agents behave as if future +TFP shocks are worse, monetary policy shocks are tighter, and future +pessimism is more persistent than under the data-generating measure. + +That subjective correlation structure is what makes both unemployment and +inflation forecasts biased upward. + ### The unemployment volatility puzzle A long-standing challenge for New Keynesian models is that standard TFP and monetary policy shocks generate far too little unemployment volatility ({cite}`Shimer2005`). -With only TFP and monetary policy shocks, the model -produces unemployment volatility of roughly 0.55%, compared to about 1.70% -in the data. +In the paper's no-belief-shock economy, TFP and monetary policy shocks produce +unemployment volatility of only 0.55, compared to 1.70 in the data. Adding the belief shock substantially closes the gap: @@ -957,16 +1070,16 @@ scale = [100, 400, 100] # convert to pp (unemployment, annualised inflation, std_full_scaled = [std_full[i] * scale[j] for j, i in enumerate(idx)] std_no_θ_scaled = [std_no_θ[i] * scale[j] for j, i in enumerate(idx)] -# Reference values from Table 2 of the paper +# Data values from Table 2 of the paper data_std = [1.70, 1.37, 2.00] # unemployment, inflation, output x = np.arange(len(labels_vol)) width = 0.25 fig, ax = plt.subplots() -ax.bar(x - width, std_no_θ_scaled, width, label='Model (no belief shock)', +ax.bar(x - width, std_no_θ_scaled, width, label='No $\\theta_t$', color='steelblue', alpha=0.7) -ax.bar(x, std_full_scaled, width, label='Model (with belief shock)', +ax.bar(x, std_full_scaled, width, label='Benchmark', color='firebrick', alpha=0.7) ax.bar(x + width, data_std, width, label='Data', color='grey', alpha=0.7) @@ -979,14 +1092,16 @@ plt.tight_layout() plt.show() ``` -The bar chart compares three standard deviations: the model without belief -shocks, the model with belief shocks, and the data. +The bar chart compares three standard deviations: the paper's no-belief-shock +economy, the benchmark economy, and the data. The main message is visible in the unemployment bars. Without the belief shock, unemployment volatility is far below its empirical -counterpart, but adding the calibrated belief shock brings the model much -closer to the data. +counterpart. + +Adding the calibrated belief shock raises unemployment volatility from about +0.55 to about 1.39, moving the model much closer to the data value 1.70. The paper is also clear about a limitation of the benchmark model. @@ -1056,41 +1171,71 @@ pedagogical representation. ### Role of firms' beliefs -{cite}`bhandari2025survey` also study a variant in which **firms** hold -subjective beliefs. +In the benchmark model of {cite}`bhandari2025survey`, **firms** as well as +households hold subjective beliefs. + +The paper studies how the results change when firms instead have rational +beliefs. The key channel is through the price-setting equation. Price-setting firms that share the household's pessimism put extra probability weight on states with lower productivity and higher marginal costs. -If firms instead have rational beliefs, they see the household pessimism shock -mainly as contractionary demand, inflation falls, and the inflation wedge is too -small. +The rational-firms experiment turns off belief distortions in firms' +forward-looking equations while keeping household beliefs subjective and +recalibrating $\theta_t$ so that the mean and volatility of the unemployment +wedge remain comparable. + +If firms have rational beliefs, they see the household pessimism shock mainly +as contractionary demand. + +Inflation falls on impact, and the inflation wedge is too small. Firm beliefs therefore strengthen the comovement between the unemployment wedge and the inflation wedge, which is needed to match the data. -The sign of the inflation response to a belief shock is therefore a -diagnostic: positive responses to pessimistic shocks require firms (not just -households) to hold subjective beliefs. +The sign and size of the inflation response to a belief shock are therefore +diagnostic: the survey evidence is hard to match unless firms, not only +households, put extra subjective probability on high-marginal-cost states. ### Two diagnostic variants -The paper uses two variants to show how survey wedges restrict the model. +The paper uses diagnostic variants to show how survey wedges restrict the +model. **No TFP shocks** --- Without supply-side uncertainty, pessimistic agents worry mainly about demand-type shocks, so the model predicts a negative average inflation wedge and negative comovement between inflation and unemployment wedges, both of which are counterfactual. +In Table 2, the no-TFP variant has a mean inflation wedge of $-0.32$ and +inflation-wedge volatility of only $0.26$, even after the belief-shock process +is recalibrated to keep the unemployment wedge close to the benchmark. + **Rational firms** --- If households are pessimistic but firms have rational beliefs, unemployment still responds strongly to a belief shock. Inflation, however, falls on impact and the inflation wedge is much too small. -This variant shows why firms' subjective beliefs matter for matching the joint -behaviour of inflation and unemployment forecasts. +In Table 2, the rational-firms variant keeps the unemployment wedge close to +the benchmark, with mean $0.55$ and volatility $0.45$, but the inflation wedge +falls to mean $0.34$ and volatility $0.29$, compared with $0.90$ and $0.73$ in +the benchmark. + +The table below summarizes the two restrictions. + +| Moment | Benchmark | No TFP shocks | Rational firms | +|---|---:|---:|---:| +| Mean inflation wedge | 0.90 | −0.32 | 0.34 | +| Mean unemployment wedge | 0.55 | 0.54 | 0.55 | +| Volatility of inflation wedge | 0.73 | 0.26 | 0.29 | +| Volatility of unemployment wedge | 0.45 | 0.43 | 0.45 | +| Volatility of unemployment | 1.39 | 0.87 | 1.24 | + +These variants show why the benchmark needs both supply-side uncertainty and +firms' subjective beliefs to match the joint behaviour of inflation and +unemployment forecasts. ### Countercyclicality of wedges @@ -1196,6 +1341,29 @@ the inflation dynamics substantially. This separation is identified from the relative sizes of the unemployment and inflation wedges. +**Pessimism induced by TFP** --- The benchmark treats $\theta_t$ as an +exogenous AR(1) process. + +The paper also studies a specification in which negative TFP shocks raise +pessimism. + +This variant matches many unconditional moments: Table 2 reports an inflation +wedge mean of $0.85$, an unemployment wedge mean of $0.56$, and unemployment +volatility of $1.49$. + +Its weakness is dynamic: TFP shocks generate responses that are too large, and +the fit to the historical paths of unemployment and subjective forecasts is +worse than in the benchmark with an orthogonal belief shock. + +**Wage rigidity** --- Wage rigidity is important for amplification. + +When wages are flexible, unemployment volatility falls to $0.77$ and the +unemployment-wedge volatility falls to $0.13$ in Table 2. + +This is the Shimer-style labour-market amplification problem in another form: +without sluggish wages, belief shocks and TFP shocks move match values too +little. + **Beyond the first-order homoskedastic case** --- The approximation is designed to keep subjective-belief effects alive in a linear solution. @@ -1822,12 +1990,6 @@ Explain the economic intuition. ``` ```{code-cell} ipython3 ---- -mystnb: - figure: - caption: persistence and belief-wedge volatility - name: fig-sbbc-persistence-volatility ---- ρ_vals = np.linspace(0.3, 0.95, 30) wedge_stds = [] @@ -1839,6 +2001,7 @@ for ρ in ρ_vals: fig, ax = plt.subplots() ax.plot(ρ_vals, np.array(wedge_stds) * 100, color='steelblue', linewidth=2) +ax.set_title('Persistence and belief-wedge volatility') ax.set_xlabel('persistence $\\rho_\\theta$') ax.set_ylabel('standard deviation of belief wedge (pp)') plt.tight_layout() @@ -1940,12 +2103,6 @@ the rational-expectations value function. ``` ```{code-cell} ipython3 ---- -mystnb: - figure: - caption: value sensitivity and steady-state wedge - name: fig-sbbc-pessimism-riccati ---- μ_grid = np.linspace(0, 15, 100) Vx_vals = [] wedge_ss = [] @@ -1958,6 +2115,7 @@ for μ in μ_grid: wedge_ss.append(belief_wedge(m_temp, μ) * 100) # in pp fig, axes = plt.subplots(1, 2, figsize=(11, 4)) +fig.suptitle('Value sensitivity and steady-state wedge') axes[0].plot(μ_grid, Vx_vals, color='steelblue', linewidth=2) axes[0].axhline(Vx_re, color='grey', linestyle='--', @@ -1970,7 +2128,7 @@ axes[1].plot(μ_grid, np.array(wedge_ss), color='firebrick', linewidth=2) axes[1].set_xlabel('mean pessimism $\\mu_\\theta$') axes[1].set_ylabel('steady-state wedge (pp)') -plt.tight_layout() +plt.tight_layout(rect=[0, 0, 1, 0.94]) plt.show() ``` diff --git a/lectures/tsyrennikov_2013.md b/lectures/tsyrennikov_2013.md index 53cf7a7f..98d5c962 100644 --- a/lectures/tsyrennikov_2013.md +++ b/lectures/tsyrennikov_2013.md @@ -506,8 +506,6 @@ contract that is not incentive compatible, which proves the lemma. The subtle part of the argument is why the planner may freely raise $n_2'$. -Here is the same point without using a figure. - If $S < 0$, then the high-output continuation value is lower than the low-output continuation value: @@ -545,93 +543,6 @@ After this change the spread is $S=0$ instead of $S<0$. Hence any candidate optimum with a negative spread can be replaced by an equivalent candidate with a nonnegative spread. -That is why the relaxed problem can focus on $S \geq 0$, where the borrower's -payoff is concave in investment and the first-order condition is sufficient for -incentive compatibility. - -The figure below is only an optional visual check of this logic. - -```{code-cell} ipython3 ---- -mystnb: - figure: - caption: why a negative spread lets the planner move the high-output continuation - name: fig-tsy-foa-proof ---- -import numpy as np -import matplotlib.pyplot as plt - -# Illustrative numbers for the borrower's investment problem -β_, θ_, ν_, γ_ = 0.98, 0.105, 0.95, 2.0 -nb = 1.0 # current resources n + b -v1 = -1.0 # continuation value v(n_1') after low output - -def prob_high(I): # λ(I) = Pr(Y_2 | I) - return np.minimum(I ** ν_, 1.0) - -def W(I, S): # borrower's payoff from investing I given spread S - c = nb - θ_ * I - return c ** (1 - γ_) / (1 - γ_) + β_ * (v1 + prob_high(I) * S) - -I_vals = np.linspace(0.0, 0.8, 400) - -fig, axes = plt.subplots(1, 2, figsize=(12, 4)) - -# Left: borrower's payoff under the original (S<0) and modified (S=0) contract -for S, lab, col in [(-0.5, r'original, $S<0$', 'C3'), - (0.0, r'modified, $S=0$', 'C0')]: - axes[0].plot(I_vals, W(I_vals, S), color=col, lw=2, label=lab) -axes[0].plot(0.0, W(0.0, 0.0), 'ko', ms=7) -axes[0].annotate('both invest $\\hat I=0$:\nsame realized payoff', - xy=(0.0, W(0.0, 0.0)), xytext=(0.22, W(0.0, 0.0) - 0.12), - arrowprops=dict(arrowstyle='->')) -axes[0].set_xlabel(r'investment $\hat I$') -axes[0].set_ylabel(r'borrower payoff $W(\hat I)$') -axes[0].set_title('a negative spread makes the borrower invest nothing') -axes[0].legend() - -# Right: probability of the high state as a function of investment -axes[1].plot(I_vals, prob_high(I_vals), 'C0', lw=2) -axes[1].plot(0.0, 0.0, 'ko', ms=7) -axes[1].annotate("$\\hat I=0$: high state never occurs,\nso $n_2'$ is off-path", - xy=(0.0, 0.0), xytext=(0.16, 0.5), - arrowprops=dict(arrowstyle='->')) -axes[1].set_xlabel(r'investment $\hat I$') -axes[1].set_ylabel(r'$\lambda(\hat I)=\Pr(Y_2|\hat I)$') -axes[1].set_title(r'the high state has zero probability at $\hat I=0$') - -plt.tight_layout() -plt.show() -``` - -The left panel plots the borrower's payoff $W(\hat I)$ from investing $\hat I$, -under the original contract (red, $S < 0$) and the modified one (blue, $S = 0$). - -Both are decreasing, so under either contract the borrower invests $\hat I = 0$ -and obtains the same realized payoff: the two contracts are identical on the path -that actually occurs. - -The right panel says why the modification is costless --- at $\hat I = 0$ the -high-output state has probability $\lambda(0) = 0$, so it is never reached. - -The continuation value $n_2'$ attached to that state is therefore pure off-path -bookkeeping, and the planner can slide it up to $n_1'$ --- raising the spread to -$S = 0$ --- without changing anything the borrower or the lender ever -experiences. - -At an interior optimum {eq}`eq:tsyrennikov_relaxed_ic` holds with equality, -giving the first-order condition used in the computation, - -$$ -\theta\,u'(c) -= \beta\,\lambda'(I)\,\bigl[v(n_2') - v(n_1')\bigr], -$$ (eq:tsyrennikov_foa) - -where $n_j' = Y_j - d_j$ is next period's net worth after state $j$. - -A larger continuation-value spread $v(n_2') - v(n_1')$ means a larger reward -after high output and supports a higher investment level. - ## The Euler equation and implied interest rate To characterize the optimal contract, attach multipliers to the constraints of @@ -893,7 +804,7 @@ def create_model(β=0.980, β_c=0.990, γ=2.0, θ=0.105, ν=0.950, model = create_model() β, β_c, γ, θ, ν, δ, M, Y1, Y2 = (model.β, model.β_c, model.γ, model.θ, model.ν, model.δ, model.M, model.Y1, model.Y2) -Y = np.array([Y1, Y2]) +output_states = np.array([Y1, Y2]) print(f"Output states: Y1 = {Y1:.4f}, Y2 = {Y2:.4f}") print(f"β = {β}, β_c = {β_c}, γ = {γ}, θ = {θ}, ν = {ν}") @@ -933,21 +844,21 @@ candidate continuation pairs $(n_1', n_2')$ searched by the moral-hazard step. ```{code-cell} ipython3 # Net-worth grid -N_n = 100 +n_grid_size = 100 n_lo = 0.20 n_hi = 1.20 -n_grid = np.linspace(n_lo, n_hi, N_n) +n_grid = np.linspace(n_lo, n_hi, n_grid_size) n_grid_j = jnp.asarray(n_grid) # Investment search grid used by the autarky Bellman step -N_I_search = 350 -I_search_grid = np.linspace(0.0, 1.0, N_I_search) +investment_grid_size = 350 +I_search_grid = np.linspace(0.0, 1.0, investment_grid_size) I_search_grid_j = jnp.asarray(I_search_grid) # Mesh of candidate continuation pairs (n_1', n_2') for the MH step -N_policy = 90 -n1p_candidates = np.linspace(n_lo, n_hi, N_policy) -n2p_candidates = np.linspace(n_lo, n_hi, N_policy) +policy_grid_size = 90 +n1p_candidates = np.linspace(n_lo, n_hi, policy_grid_size) +n2p_candidates = np.linspace(n_lo, n_hi, policy_grid_size) n1p_mesh, n2p_mesh = np.meshgrid(n1p_candidates, n2p_candidates, indexing='ij') n1p_flat_j = jnp.asarray(n1p_mesh.ravel()) @@ -960,10 +871,6 @@ The policy mesh is deliberately modest so the lecture can execute quickly, and it spans the full value-function domain to avoid artificial upper-bound corners. -For a more accurate but slower run, increase `N_policy` and `N_I_search`, -lower `POLISH_TOL`, and tighten the `CONTRACT_TOL` used by the contract solvers -below. - ### Autarky value function We solve the autarky problem {eq}`eq:tsyrennikov_autarky` by value function @@ -1014,7 +921,7 @@ def autarky_vfi(β_val=None, tol=1e-8, max_iter=3000, verbose=False): if β_val is None: β_val = β - v = jnp.zeros(N_n) + v = jnp.zeros(n_grid_size) for it in range(max_iter): v_new, _ = autarky_step_jax(v, β_val) diff = float(jnp.max(jnp.abs(v_new - v))) @@ -1061,7 +968,7 @@ def default_values(v_aut_arr, β_val=None): Ev1 = np.interp(Y1, n_grid, v_aut_arr) Ev2 = np.interp(Y2, n_grid, v_aut_arr) vals = [] - for Yj in Y: + for Yj in output_states: I = I_search_grid c = δ * Yj - θ * I l = np.minimum(I**ν, 1.0) @@ -1118,16 +1025,16 @@ In the two limited-enforcement economies, the endogenous borrowing limits are refreshed once per outer iteration with a damped update. ```{code-cell} ipython3 -BIG_LOAN_CAP = 1e6 -CONTRACT_TOL = 1e-6 -CONTRACT_MAX_ITER = 1_000 -HOWARD_EVAL_STEPS = 80 +big_loan_cap = 1e6 +contract_tol = 1e-6 +contract_max_iter = 1_000 +howard_eval_steps = 80 def contract_initial_upper(β_val, loan_upper): """High initial value; starting too low can converge back to autarky.""" c_upper = n_hi + loan_upper - return np.full(N_n, float(u(c_upper)) / (1.0 - β_val)) + return np.full(n_grid_size, float(u(c_upper)) / (1.0 - β_val)) @jax.jit @@ -1188,7 +1095,7 @@ def mh_bellman_step_jax(v, v_aut_arr, I_aut_arr, nbar1, nbar2, b_lp = lender_value(I_lp) # Regime 2: the exogenous loan cap binds. This regime is inactive when - # loan_cap is set to BIG_LOAN_CAP. + # loan_cap is set to big_loan_cap. def c_cap(I): return n_grid_j[:, None] + loan_cap - θ * I @@ -1326,7 +1233,7 @@ def policy_eval_jax(v, v_aut_arr, pol_n1p, pol_n2p, pol_I, pol_b, v_pol = R + β_val * ((1.0 - l) * v1 + l * v2) return jnp.where(use_fallback, v_aut_arr, v_pol) - return jax.lax.fori_loop(0, HOWARD_EVAL_STEPS, eval_step, v) + return jax.lax.fori_loop(0, howard_eval_steps, eval_step, v) def update_nbars(v_arr, nbars, v_default, relaxation=0.5): @@ -1336,8 +1243,8 @@ def update_nbars(v_arr, nbars, v_default, relaxation=0.5): return (1 - relaxation) * nbars + relaxation * target -def mh_vfi(v_aut, β_val=None, β_c_val=None, tol=CONTRACT_TOL, - max_iter=CONTRACT_MAX_ITER, +def mh_vfi(v_aut, β_val=None, β_c_val=None, tol=contract_tol, + max_iter=contract_max_iter, limited_enforcement=False, loan_cap=M, verbose=False, return_limits=False): """Howard policy iteration for MH and MH+LE.""" @@ -1351,7 +1258,7 @@ def mh_vfi(v_aut, β_val=None, β_c_val=None, tol=CONTRACT_TOL, v_aut_j = jnp.asarray(v_aut) v_default = default_values(v_aut, β_val=β_val) nbars = np.array([n_lo, n_lo]) - loan_upper = loan_cap if loan_cap < BIG_LOAN_CAP / 2 else Y2 - n_lo + loan_upper = loan_cap if loan_cap < big_loan_cap / 2 else Y2 - n_lo v = jnp.asarray(contract_initial_upper(β_val, loan_upper)) label = 'MH+LE' if limited_enforcement else 'MH' @@ -1400,8 +1307,8 @@ def mh_vfi(v_aut, β_val=None, β_c_val=None, tol=CONTRACT_TOL, return result -def le_vfi(v_aut, β_val=None, β_c_val=None, tol=CONTRACT_TOL, - max_iter=CONTRACT_MAX_ITER, +def le_vfi(v_aut, β_val=None, β_c_val=None, tol=contract_tol, + max_iter=contract_max_iter, verbose=False, return_limits=False): """Howard policy iteration for the LE-only economy.""" if β_val is None: @@ -1462,7 +1369,7 @@ v_mh, pol_n1p, pol_n2p, pol_I, pol_b = mh_vfi(v_aut, verbose=True) (v_mhle, pol_n1p_mhle, pol_n2p_mhle, pol_I_mhle, pol_b_mhle, nbars_mhle) = mh_vfi(v_aut, limited_enforcement=True, - loan_cap=BIG_LOAN_CAP, verbose=True, + loan_cap=big_loan_cap, verbose=True, return_limits=True) (v_le, pol_n1p_le, pol_n2p_le, pol_I_le, pol_b_le, @@ -1477,9 +1384,16 @@ indices, capital-outflow schedules and implied interest rates. Before constructing the figures, the raw finite-mesh policies are polished by continuous local optimization. -This step is controlled by `POLISH_POLICIES` and can be turned off when exact +This step is controlled by `polish_policies` and can be turned off when exact grid policies are desired. +The small NumPy versions of the primitives below are used only in this +SciPy-based polishing step. + +The JAX versions defined earlier are used inside JIT-compiled Bellman updates, +while SciPy's optimizer works most cleanly with ordinary NumPy arrays and Python +floats. + ```{code-cell} ipython3 def λ_np(I): """NumPy version of λ for plotting and simulation.""" @@ -1502,21 +1416,21 @@ def u_prime_np(c): return np.maximum(np.asarray(c), 1e-12)**(-γ) -POLISH_POLICIES = True -POLISH_TOL = 1e-9 -PLOT_GRID = np.linspace(n_lo, n_hi, 400) -RSI_BOUNDS = (-0.25, 1.00) -Y_gap = Y2 - Y1 +polish_policies = True +polish_tol = 1e-9 +plot_grid = np.linspace(n_lo, n_hi, 400) +rsi_bounds = (-0.25, 1.00) +y_gap = Y2 - Y1 def continuation_from_rsi(n1p, rsi): """Recover n_2' from n_1' and the risk-sharing index.""" - return n1p + (1.0 - rsi) * Y_gap + return n1p + (1.0 - rsi) * y_gap def rsi_from_continuations(n1p, n2p): """Risk-sharing index implied by continuation net worths.""" - return 1.0 - (n2p - n1p) / Y_gap + return 1.0 - (n2p - n1p) / y_gap def value_spline(v_arr): @@ -1599,7 +1513,7 @@ def mh_contract_value(n, n1p, rsi, vs, nbar1, nbar2, lambda I, b: b <= loan_cap + 1e-8 ) - if loan_cap < BIG_LOAN_CAP / 2: + if loan_cap < big_loan_cap / 2: add_candidate( lambda I: loan_cap, lambda I, b: lender_value(I) >= b - 1e-8 @@ -1649,15 +1563,15 @@ def polish_mh_state(n, raw_n1p, raw_n2p, vs, nbar1, nbar2, loan_cap): best = None for start in starts: x0 = np.array([np.clip(start[0], nbar1, n_hi), - np.clip(start[1], *RSI_BOUNDS)]) + np.clip(start[1], *rsi_bounds)]) out0 = mh_contract_value(n, x0[0], x0[1], vs, nbar1, nbar2, loan_cap) if out0 is not None and (best is None or out0[0] > best[0]): best = out0 res = minimize(objective, x0, method='Nelder-Mead', - options={'xatol': POLISH_TOL, 'fatol': POLISH_TOL, + options={'xatol': polish_tol, 'fatol': polish_tol, 'maxiter': 500}) x = np.array([np.clip(res.x[0], nbar1, n_hi), - np.clip(res.x[1], *RSI_BOUNDS)]) + np.clip(res.x[1], *rsi_bounds)]) out = mh_contract_value(n, x[0], x[1], vs, nbar1, nbar2, loan_cap) if out is not None and (best is None or out[0] > best[0]): best = out @@ -1683,16 +1597,16 @@ def polish_le_state(n, raw_n1p, raw_n2p, raw_I, vs, nbar1, nbar2): best = None for start in starts: x0 = np.array([np.clip(start[0], nbar1, n_hi), - np.clip(start[1], *RSI_BOUNDS), + np.clip(start[1], *rsi_bounds), np.clip(start[2], 0.0, 1.0 - 1e-7)]) out0 = le_contract_value(n, x0[0], x0[1], x0[2], vs, nbar1, nbar2) if out0 is not None and (best is None or out0[0] > best[0]): best = out0 res = minimize(objective, x0, method='Nelder-Mead', - options={'xatol': POLISH_TOL, 'fatol': POLISH_TOL, + options={'xatol': polish_tol, 'fatol': polish_tol, 'maxiter': 700}) x = np.array([np.clip(res.x[0], nbar1, n_hi), - np.clip(res.x[1], *RSI_BOUNDS), + np.clip(res.x[1], *rsi_bounds), np.clip(res.x[2], 0.0, 1.0 - 1e-7)]) out = le_contract_value(n, x[0], x[1], x[2], vs, nbar1, nbar2) if out is not None and (best is None or out[0] > best[0]): @@ -1783,13 +1697,13 @@ def make_policy(name, n1p, n2p, I, b, v, nbars=None): return policy -if POLISH_POLICIES: +if polish_policies: v_mh, pol_n1p, pol_n2p, pol_I, pol_b = polish_mh_policy( v_mh, pol_n1p, pol_n2p, pol_I, pol_b, loan_cap=M) v_mhle, pol_n1p_mhle, pol_n2p_mhle, pol_I_mhle, pol_b_mhle = ( polish_mh_policy(v_mhle, pol_n1p_mhle, pol_n2p_mhle, pol_I_mhle, pol_b_mhle, nbars_mhle, - loan_cap=BIG_LOAN_CAP)) + loan_cap=big_loan_cap)) v_le, pol_n1p_le, pol_n2p_le, pol_I_le, pol_b_le = polish_le_policy( v_le, pol_n1p_le, pol_n2p_le, pol_I_le, pol_b_le, nbars_le) @@ -1811,7 +1725,7 @@ def policy_at(policy, key, n): def policy_curve(policy, key): """Evaluate a policy on the dense grid used in the figures.""" - return policy['splines'][key](PLOT_GRID) + return policy['splines'][key](plot_grid) def next_period_c(policy, n_next): @@ -1907,10 +1821,10 @@ mystnb: --- fig, axes = plt.subplots(1, 2, figsize=(12, 4)) -axes[0].plot(PLOT_GRID, CubicSpline(n_grid, v_aut, bc_type='natural')(PLOT_GRID), +axes[0].plot(plot_grid, CubicSpline(n_grid, v_aut, bc_type='natural')(plot_grid), lw=2, color='0.45', label='Autarky') for name, style in [('MH', '-'), ('MH+LE', '--'), ('LE', ':')]: - axes[0].plot(PLOT_GRID, policy_curve(policies[name], 'v'), + axes[0].plot(plot_grid, policy_curve(policies[name], 'v'), lw=2, ls=style, label=name) axes[0].set_xlabel('net worth $n$') axes[0].set_ylabel('value') @@ -1920,7 +1834,7 @@ for name, style in [('MH', '-'), ('MH+LE', '--'), ('LE', ':')]: λ_plot = policy_curve(policies[name], 'λ') rsi_plot = np.where(λ_plot > 0.01, policy_curve(policies[name], 'RSI'), np.nan) - axes[1].plot(PLOT_GRID, rsi_plot, lw=2, ls=style, label=name) + axes[1].plot(plot_grid, rsi_plot, lw=2, ls=style, label=name) axes[1].axhline(1.0, ls=':', color='k', lw=1, label='Full insurance') axes[1].axhline(0.0, ls='--', color='k', lw=1, @@ -1988,51 +1902,51 @@ for a in ax: a.axvline(n_low_le, color='0.55', lw=1, ls=':') a.set_xlim(0.38, 1.02) -ax[0].plot(PLOT_GRID, policy_curve(policies['MH'], 'λ'), lw=2, label='MH') -ax[0].plot(PLOT_GRID, policy_curve(policies['MH+LE'], 'λ'), +ax[0].plot(plot_grid, policy_curve(policies['MH'], 'λ'), lw=2, label='MH') +ax[0].plot(plot_grid, policy_curve(policies['MH+LE'], 'λ'), lw=2, ls='--', label='MH+LE') -ax[0].plot(PLOT_GRID, policy_curve(policies['LE'], 'λ'), +ax[0].plot(plot_grid, policy_curve(policies['LE'], 'λ'), lw=2, ls=':', label='LE') ax[0].set_ylabel(r'$\lambda(I)$') ax[0].set_title('A. investment') ax[0].legend(fontsize=9) for name, style in [('MH', '-'), ('MH+LE', '--'), ('LE', ':')]: - ax[1].plot(PLOT_GRID, policy_curve(policies[name], 'Enp'), + ax[1].plot(plot_grid, policy_curve(policies[name], 'Enp'), lw=2, ls=style, label=name) -ax[1].plot(PLOT_GRID, PLOT_GRID, color='k', lw=1, ls=':', +ax[1].plot(plot_grid, plot_grid, color='k', lw=1, ls=':', label='45-degree') ax[1].set_ylabel(r"$E[n']$") ax[1].set_title('B. expected future net worth') ax[1].legend(fontsize=9) -ax[2].plot(PLOT_GRID, policy_curve(policies['MH'], 'b'), +ax[2].plot(plot_grid, policy_curve(policies['MH'], 'b'), lw=2, label=r'$b_{MH}$') -ax[2].plot(PLOT_GRID, policy_curve(policies['MH'], 'd1'), +ax[2].plot(plot_grid, policy_curve(policies['MH'], 'd1'), lw=2, ls='--', label=r'$d_{1,MH}$') -ax[2].plot(PLOT_GRID, policy_curve(policies['MH'], 'd2'), +ax[2].plot(plot_grid, policy_curve(policies['MH'], 'd2'), lw=2, ls=':', label=r'$d_{2,MH}$') ax[2].set_ylabel('loan and repayment') ax[2].set_title('C. MH contract') ax[2].legend(fontsize=9) -ax[3].plot(PLOT_GRID, policy_curve(policies['LE'], 'b'), +ax[3].plot(plot_grid, policy_curve(policies['LE'], 'b'), lw=2, label=r'$b_{LE}$') -ax[3].plot(PLOT_GRID, policy_curve(policies['LE'], 'd1'), +ax[3].plot(plot_grid, policy_curve(policies['LE'], 'd1'), lw=2, ls='--', label=r'$d_{1,LE}$') -ax[3].plot(PLOT_GRID, policy_curve(policies['LE'], 'd2'), +ax[3].plot(plot_grid, policy_curve(policies['LE'], 'd2'), lw=2, ls=':', label=r'$d_{2,LE}$') ax[3].set_ylabel('loan and repayment') ax[3].set_title('D. LE contract') ax[3].legend(fontsize=9) -ax[4].plot(PLOT_GRID, policy_curve(policies['MH'], 'ca1'), +ax[4].plot(plot_grid, policy_curve(policies['MH'], 'ca1'), lw=2, label=r'$ca_{1,MH}$') -ax[4].plot(PLOT_GRID, policy_curve(policies['MH'], 'ca2'), +ax[4].plot(plot_grid, policy_curve(policies['MH'], 'ca2'), lw=2, ls='--', label=r'$ca_{2,MH}$') -ax[4].plot(PLOT_GRID, policy_curve(policies['LE'], 'ca1'), +ax[4].plot(plot_grid, policy_curve(policies['LE'], 'ca1'), lw=2, ls=':', label=r'$ca_{1,LE}$') -ax[4].plot(PLOT_GRID, policy_curve(policies['LE'], 'ca2'), +ax[4].plot(plot_grid, policy_curve(policies['LE'], 'ca2'), lw=2, ls='-.', label=r'$ca_{2,LE}$') ax[4].axhline(0, color='k', lw=0.8) ax[4].set_xlabel('net worth $n$') @@ -2040,12 +1954,12 @@ ax[4].set_ylabel('capital outflows') ax[4].set_title('E. capital outflows') ax[4].legend(fontsize=8) -ax[5].plot(PLOT_GRID, 10 * policy_curve(policies['MH'], 'RSI'), +ax[5].plot(plot_grid, 10 * policy_curve(policies['MH'], 'RSI'), lw=2, label=r'$10\times$ MH') -ax[5].plot(PLOT_GRID, 10 * policy_curve(policies['MH+LE'], 'RSI'), +ax[5].plot(plot_grid, 10 * policy_curve(policies['MH+LE'], 'RSI'), lw=2, ls='--', label=r'$10\times$ MH+LE') -ax[5].plot(PLOT_GRID, policy_curve(policies['LE'], 'RSI'), +ax[5].plot(plot_grid, policy_curve(policies['LE'], 'RSI'), lw=2, ls=':', label='LE') ax[5].axhline(0, color='k', lw=0.8) ax[5].set_xlabel('net worth $n$') From bcaf69b74a5587bd5e4bc678b8e8fcb5cf985f4e Mon Sep 17 00:00:00 2001 From: HumphreyYang Date: Thu, 11 Jun 2026 16:43:10 +1000 Subject: [PATCH 18/25] updates --- .../subjective_beliefs_business_cycles.md | 229 ++++++++++++------ 1 file changed, 149 insertions(+), 80 deletions(-) diff --git a/lectures/subjective_beliefs_business_cycles.md b/lectures/subjective_beliefs_business_cycles.md index f711cbac..c6c9bedf 100644 --- a/lectures/subjective_beliefs_business_cycles.md +++ b/lectures/subjective_beliefs_business_cycles.md @@ -56,6 +56,26 @@ In this lecture, we will cover: * Why a calibrated belief shock helps resolve the unemployment volatility puzzle. +The lecture is self-contained in the following sense. + +All empirical moments, calibration values, and model objects used in the code +are reported below, so no external data files are needed. + +We use the paper for motivation and for several benchmark numbers, but the +computations in this lecture are generated from the equations and parameter +values stated here. + +Some notation and units will be used throughout: + +* `pp` means percentage points. +* Inflation is reported at an annualized rate when explicitly marked + "ann."; otherwise it is a quarterly rate. +* $u_t$, $\pi_t$, and $y_t$ denote unemployment, inflation, and the output gap. +* $a_t$ is total factor productivity (TFP), and $r_t$ is a monetary-policy + disturbance. +* $\theta_t$ is the belief-shock or pessimism state: larger $\theta_t$ means + agents put more subjective probability on low-continuation-value states. + We start with the following imports ```{code-cell} ipython3 @@ -90,8 +110,15 @@ data-generating forecast. For unemployment and inflation, this sign convention corresponds to an upward forecast bias. -The empirical objects in {cite}`bhandari2025survey` are mostly -one-year-ahead, or four-quarter, wedges. +The empirical objects in {cite}`bhandari2025survey` are mostly one-year-ahead, +or four-quarter, wedges: + +$$ + +\Delta_t^{(4)}(z) += \tilde E_t[z_{t+4} - z_t] - E_t[z_{t+4} - z_t]. + +$$ We introduce the one-period wedge because it is the cleanest way to explain the theory; the appendix below shows the multi-period version. @@ -105,9 +132,18 @@ Professional Forecasters (SPF) forecasts used as an important robustness check. In the structural model, the same object is interpreted as the difference between subjective and data-generating expectations. -The raw Michigan unemployment question is categorical, so the paper converts it -into a quantitative forecast using the Carlson--Parkin procedure as adapted by -{cite}`MankiwReisWolfers2003`. +Thus the lecture uses one object in two related ways: empirically, a belief +wedge is a survey forecast minus a statistical benchmark forecast; in the +model, it is a subjective expectation minus an objective expectation. + +The raw Michigan unemployment question is categorical, so Bhandari et al. +convert it into a quantitative forecast using the Carlson--Parkin procedure as +adapted by {cite}`MankiwReisWolfers2003`. + +We do not need those raw survey responses below. + +Instead, the empirical section works with the already-constructed wedge +moments listed next. ### Empirical facts @@ -139,7 +175,7 @@ pessimism/optimism component rather than two unrelated forecast mistakes. The following code simulates a stylized wedge process with the same sample length, mean wedges, standard deviations, and first-principal-component share -reported in the paper. +reported above. The point is not to recreate the raw Michigan series. @@ -147,7 +183,7 @@ Instead, the simulation separates the common pessimism factor from residual survey noise, so the wedges are strongly related but not perfectly collinear. ```{code-cell} ipython3 -# Parameters from Table 1 +# Baseline belief-shock parameters used throughout the lecture. μ_θ = 5.64 # mean of belief-shock parameter θ ρ_θ = 0.714 # AR(1) persistence of θ σ_θ = 4.3 # innovation volatility @@ -156,7 +192,7 @@ survey noise, so the wedges are strongly related but not perfectly collinear. c_u = 0.52 / μ_θ c_π = 1.22 / μ_θ -# Empirical wedge moments from Section 2 and Table 2. +# Empirical wedge moments used to discipline the simulation. mean_u, mean_π = 0.52, 1.22 std_u, std_π = 0.57, 0.97 pc1_share_target = 0.786 @@ -263,8 +299,8 @@ plt.show() The first figure plots the simulated unemployment wedge in the top panel and the simulated inflation wedge in the bottom panel. -The dashed horizontal lines show the sample means, which match the values in -the paper. +The dashed horizontal lines show the sample means, which match the empirical +values reported above. Both wedges have a common cyclical component driven by $\theta_t$, but they also contain residual components that stand in for survey noise and other @@ -750,14 +786,14 @@ The model is calibrated to quarterly U.S. data, 1982Q1–2019Q4. | Vacancy posting cost | $\kappa_v$ | 0.09 | | | Unemployment benefit flow | $D$ | 0.57 | | -### A paper-calibrated linear surrogate +### A self-contained linear surrogate The paper solves a structural New Keynesian model. For computation in this lecture, we use a small reduced-form vector autoregression that is calibrated to reproduce the main benchmark moments in -Table 2 and the qualitative shape of the belief-shock impulse responses in -Figure 7: +the moment table reported below and the qualitative shape of the +belief-shock impulse responses: $$ @@ -769,23 +805,25 @@ where $s_t = (u_t, \pi_t, y_t, \theta_t, a_t)'$ collects unemployment, inflation, output, the belief shock, and TFP, and $\epsilon_{t+1} \sim N(0, I_3)$ contains the three structural shocks. -The coefficient matrices $A$ and $B$ are not the paper's structural solution. +The coefficient matrices $A$ and $B$ are not the full structural solution. -The full paper obtains them from equilibrium conditions: households and firms -distort probabilities according to continuation values, and those distorted -beliefs feed back into consumption, price setting, vacancy posting, and wages. +A full structural solution would obtain them from equilibrium conditions: +households and firms distort probabilities according to continuation values, +and those distorted beliefs feed back into consumption, price setting, vacancy +posting, and wages. Here we compress those equilibrium channels into a few linear loadings. The loadings are chosen so that the Lyapunov moments for unemployment, inflation, and output match the benchmark and no-belief-shock columns of -Table 2. +the moment table in the next section. This surrogate is useful for transparent computations, but it should not be used to analyse the diagnostic variants such as "only $\theta_t$", no TFP shocks, or rational firms. -For those variants, we report the paper's structural results directly below. +For those variants, the relevant structural moments are reported directly +below. We index the five state variables with named constants, so that later code can refer to, say, the belief shock as `I_THETA` rather than a bare number. @@ -796,7 +834,7 @@ I_U, I_PI, I_Y, I_THETA, I_A = 0, 1, 2, 3, 4 ``` The factory `create_nk_model` builds the transition matrix $A$ and the shock -loadings $B$ from the exogenous-process parameters in Table 1. +loadings $B$ from the calibration values stated above. The belief shock $\theta_t$ and TFP $a_t$ follow AR(1) processes; the endogenous variables inherit their own persistence and load on $\theta_t$, @@ -818,7 +856,7 @@ class NKModel(NamedTuple): def create_nk_model(): """Build the pedagogical reduced-form NK model (state and shock matrices).""" - # Exogenous-process parameters from Table 1 + # Exogenous-process parameters from the calibration table above. ρ_θ, σ_θ = 0.714, 4.3 ρ_a, σ_a = 0.840, 0.00568 @@ -916,11 +954,10 @@ nk = create_nk_model() ## Quantitative results -### Benchmark fit in the paper - -The key quantitative comparison is Table 2 of {cite}`bhandari2025survey`. +### Benchmark moments and model targets -The rows below reproduce the moments most relevant for this lecture. +The table below collects the empirical moments and model moments most relevant +for this lecture. All values are percentages or percentage points; inflation is annualised and output is detrended. @@ -949,9 +986,21 @@ If TFP and monetary policy uncertainty are absent, there is no payoff-relevant uncertainty for pessimistic agents to distort, so time variation in $\theta_t$ alone does not move the economy. -This is why the full paper emphasizes the interaction between belief shocks and +This is why the benchmark emphasizes the interaction between belief shocks and fundamental shocks, especially TFP shocks. +This point is also why the reduced-form surrogate should be read carefully. + +In the structural model, $\theta_t$ changes how agents weight fundamental +shocks. + +In the surrogate, that interaction is compressed into direct loadings from +$\theta_t$ to unemployment, inflation, and output. + +Thus the surrogate is useful for the benchmark and no-belief-shock comparisons, +but the diagnostic columns in the table are structural results rather than +additional simulations of the surrogate. + ### Impulse responses to the belief shock A positive innovation to $\theta_t$ makes households more pessimistic. @@ -969,7 +1018,7 @@ mechanism works this way: 4. The belief wedges jump on impact, then decay with the persistence $\rho_\theta = 0.714$. -In the paper's Figure 7, a one-standard-deviation belief shock raises +In the structural benchmark, a one-standard-deviation belief shock raises unemployment by about one percentage point and lowers output by about one percent. @@ -977,7 +1026,7 @@ Inflation rises briefly and then falls, leaving a roughly zero cumulative 10-quarter response. The reduced-form impulse responses below are calibrated to reproduce these -signs and the Table 2 volatility scale. +signs and the volatility scale in the moment table above. ```{code-cell} ipython3 --- @@ -1048,7 +1097,7 @@ A long-standing challenge for New Keynesian models is that standard TFP and monetary policy shocks generate far too little unemployment volatility ({cite}`Shimer2005`). -In the paper's no-belief-shock economy, TFP and monetary policy shocks produce +In the no-belief-shock economy, TFP and monetary policy shocks produce unemployment volatility of only 0.55, compared to 1.70 in the data. Adding the belief shock substantially closes the gap: @@ -1070,7 +1119,7 @@ scale = [100, 400, 100] # convert to pp (unemployment, annualised inflation, std_full_scaled = [std_full[i] * scale[j] for j, i in enumerate(idx)] std_no_θ_scaled = [std_no_θ[i] * scale[j] for j, i in enumerate(idx)] -# Data values from Table 2 of the paper +# Data values from the benchmark moment table above. data_std = [1.70, 1.37, 2.00] # unemployment, inflation, output x = np.arange(len(labels_vol)) @@ -1092,8 +1141,8 @@ plt.tight_layout() plt.show() ``` -The bar chart compares three standard deviations: the paper's no-belief-shock -economy, the benchmark economy, and the data. +The bar chart compares three standard deviations: the no-belief-shock economy, +the benchmark economy, and the data. The main message is visible in the unemployment bars. @@ -1103,7 +1152,7 @@ counterpart. Adding the calibrated belief shock raises unemployment volatility from about 0.55 to about 1.39, moving the model much closer to the data value 1.70. -The paper is also clear about a limitation of the benchmark model. +One limitation of the benchmark model is its inflation cyclicality. Inflation is nearly acyclical in the data but countercyclical in the model, suggesting that omitted wage or price markup shocks may be important for @@ -1209,19 +1258,19 @@ mainly about demand-type shocks, so the model predicts a negative average inflation wedge and negative comovement between inflation and unemployment wedges, both of which are counterfactual. -In Table 2, the no-TFP variant has a mean inflation wedge of $-0.32$ and -inflation-wedge volatility of only $0.26$, even after the belief-shock process -is recalibrated to keep the unemployment wedge close to the benchmark. +The no-TFP variant has a mean inflation wedge of $-0.32$ and inflation-wedge +volatility of only $0.26$, even after the belief-shock process is recalibrated +to keep the unemployment wedge close to the benchmark. **Rational firms** --- If households are pessimistic but firms have rational beliefs, unemployment still responds strongly to a belief shock. Inflation, however, falls on impact and the inflation wedge is much too small. -In Table 2, the rational-firms variant keeps the unemployment wedge close to -the benchmark, with mean $0.55$ and volatility $0.45$, but the inflation wedge -falls to mean $0.34$ and volatility $0.29$, compared with $0.90$ and $0.73$ in -the benchmark. +The rational-firms variant keeps the unemployment wedge close to the benchmark, +with mean $0.55$ and volatility $0.45$, but the inflation wedge falls to mean +$0.34$ and volatility $0.29$, compared with $0.90$ and $0.73$ in the +benchmark. The table below summarizes the two restrictions. @@ -1344,12 +1393,11 @@ the relative sizes of the unemployment and inflation wedges. **Pessimism induced by TFP** --- The benchmark treats $\theta_t$ as an exogenous AR(1) process. -The paper also studies a specification in which negative TFP shocks raise -pessimism. +Another specification makes negative TFP shocks raise pessimism. -This variant matches many unconditional moments: Table 2 reports an inflation -wedge mean of $0.85$, an unemployment wedge mean of $0.56$, and unemployment -volatility of $1.49$. +This variant matches many unconditional moments: the inflation wedge mean is +$0.85$, the unemployment wedge mean is $0.56$, and unemployment volatility is +$1.49$. Its weakness is dynamic: TFP shocks generate responses that are too large, and the fit to the historical paths of unemployment and subjective forecasts is @@ -1357,8 +1405,8 @@ worse than in the benchmark with an orthogonal belief shock. **Wage rigidity** --- Wage rigidity is important for amplification. -When wages are flexible, unemployment volatility falls to $0.77$ and the -unemployment-wedge volatility falls to $0.13$ in Table 2. +When wages are flexible, unemployment volatility falls to $0.77$ and +unemployment-wedge volatility falls to $0.13$. This is the Shimer-style labour-market amplification problem in another form: without sluggish wages, belief shocks and TFP shocks move match values too @@ -1373,31 +1421,33 @@ move because the dispersion of continuation values changes. We do not pursue those extensions here. **Idiosyncratic risk** --- The benchmark model takes fluctuations in $\theta_t$ as -exogenous, but the paper also derives them endogenously. +exogenous, but they can also be endogenized. In a variant where households face uninsurable idiosyncratic risk, a rise in that risk makes adverse states more likely from each household's viewpoint, so pessimism and the belief wedges increase without any exogenous shock to $\theta_t$. -The paper offers suggestive support for this channel: the belief wedges -comove with the {cite}`Schmidt2016` index of idiosyncratic labour-income -skewness, which proxies for the risk of large losses such as job loss. +The supporting empirical idea is that belief wedges comove with the +{cite}`Schmidt2016` index of idiosyncratic labour-income skewness, which +proxies for the risk of large losses such as job loss. ## Appendix: the series expansion method -This appendix follows the Online Appendix of {cite}`bhandari2025survey` -and fills in the computational and theoretical details underlying the +This appendix gives the computational and theoretical details underlying the linearisation presented in the main lecture. +The formulas follow {cite}`bhandari2025survey`, but the notation needed for the +calculations below is introduced here. + ### Multi-period belief wedges The main text focused on the one-period belief wedge $\Delta_t^{(1)}(z)$. -The paper also uses $\tau$-period-ahead wedges +Longer-horizon survey forecasts require $\tau$-period-ahead wedges $\Delta_t^{(\tau)}(z) = \tilde E_t[z_{t+\tau}] - E_t[z_{t+\tau}]$, -which are needed to match survey respondents' longer-horizon forecasts. +so we now derive their linear representation. Under linear dynamics @@ -1422,7 +1472,7 @@ $$ so that $E_t[x_{t+\tau} - x_t] = G_x^{(\tau)} x_{1t} + G_0^{(\tau)}$. Under the **subjective** measure, the mean of $w_{t+1}$ is shifted to -$\nu_t = H + HF x_{1t}$ (equation OA.1 of the appendix). +$\nu_t = H + HF x_{1t}$. For the stationary model the relevant identifications are @@ -1460,8 +1510,7 @@ with the forecast horizon. ```{code-cell} ipython3 def compute_tau_wedge_loadings(ψ_x, ψ_w, H, H_bar, F, τ_max=20): """ - Compute tau-period belief wedge loadings using the recursions from - Online Appendix OA.1 of Bhandari, Borovicka, Ho (2025). + Compute tau-period belief wedge loadings. For simplicity we work with the scalar stationary case (all quantities are scalars or 1-d arrays). @@ -1587,7 +1636,7 @@ $$ To preserve a nontrivial role for beliefs at first order, the penalty parameter is **jointly scaled** with $\mathsf{q}$: the effective -penalisation in the perturbed recursion (OA.8) is +penalisation in the perturbed recursion is $\mathsf{q}/[\bar\theta(\bar x + x_{1t})]$, which shrinks together with shock volatility. @@ -1596,7 +1645,7 @@ deterministic steady state does not collapse to the rational-expectations solution. Guessing $V_{1t} = V_x x_{1t} + V_q$ and matching coefficients yields -the **Riccati equation for $V_x$** (equation OA.20 of the appendix): +the **Riccati equation for $V_x$**: $$ @@ -1630,12 +1679,11 @@ $$ #### Shock distribution under subjective beliefs -Substituting the first-order expansion into the distortion formula -(OA.10) shows that the leading term $m_{0,t+1}$ is a lognormal change of -measure. +Substituting the first-order expansion into the distortion formula shows that +the leading term $m_{0,t+1}$ is a lognormal change of measure. With Gaussian shocks, this is equivalent to shifting the innovation mean -(equation OA.12): +as follows: $$ @@ -1702,8 +1750,7 @@ $$ $$ -These equations (OA.17–OA.21) are solved jointly with the Riccati -equation for $V_x$. +These equations are solved jointly with the Riccati equation for $V_x$. Compared with the standard Blanchard–Kahn solution, the only modification is the additive term $-\mathbb{E}$ that shifts the @@ -1712,8 +1759,7 @@ rational-expectations solution. #### The AR(1) belief shock as a special case -In the paper's application $\theta_t$ is itself an exogenous AR(1) -process (equation OA.22): +Now suppose $\theta_t$ is itself an exogenous AR(1) process: $$ @@ -1737,13 +1783,12 @@ $$ The new coefficient $\psi_{xf}$ measures how a unit change in the belief shock $f_{1t}$ feeds into the endogenous state variables. -It is determined -by the backward-induction algorithm (equations OA.31–OA.34), which iterates -from a distant terminal date $T$ (where belief distortions vanish) back to -the present. +It is determined by a backward-induction algorithm that iterates from a +distant terminal date $T$ (where belief distortions vanish) back to the +present. The continuation value in the $f$-direction satisfies a separate recursion -for $V_f$ (equation OA.29), and the belief distortion matrix becomes +for $V_f$, and the belief distortion matrix becomes $$ @@ -1758,13 +1803,13 @@ $$ The algorithm therefore decomposes cleanly into two stages: -1. **Stage 1 (rational-expectations block)**: solve (OA.24) and (OA.26) for - $\psi_x$, $\psi_w$ using the standard Blanchard–Kahn method --- these are +1. **Stage 1 (rational-expectations block)**: solve for + $\psi_x$, $\psi_w$ using the standard Blanchard–Kahn method; these are *unaffected* by the belief shock. 2. **Stage 2 (belief distortion block)**: given $\psi_x, \psi_w, V_x$, - iterate (OA.31–OA.34) backward to convergence to find $\psi_{xf}$, - $V_f$, and $\mathbb{E}$. + iterate backward to convergence to find $\psi_{xf}$, $V_f$, and + $\mathbb{E}$. This separation is a major practical advantage: existing rational-expectations solvers can be used for Stage 1 with only a wrapper for Stage 2. @@ -1815,7 +1860,7 @@ print("The steady-state wedge for x: Δ = ψ_w * ν_bar =", ### Sequence problem and dynamic consistency The recursive formulation used throughout the lecture emerges from the -following sequence problem (Online Appendix OA.3). +following sequence problem. Define the discounted entropy functional @@ -1892,6 +1937,30 @@ print("The entropy penalty grows quadratically in θ,") print("constraining the agent from distorting beliefs too heavily.") ``` +## Summary + +The lecture has built the mechanism from the survey object to the model object. + +A belief wedge is the difference between a subjective forecast and an objective +forecast. + +In the data, the unemployment and inflation wedges are positive on average, +countercyclical, and well described by one common factor. + +Multiplier preferences generate exactly this kind of common factor: a higher +$\theta_t$ makes agents overweight states with low continuation value. + +With Gaussian shocks, the optimal change of measure is especially simple: it +shifts the mean of the innovation by +$-\theta_t (V_x \psi_w)'$. + +This mean shift implies belief wedges that are proportional to $\theta_t$ and +to the covariance between shocks and continuation values. + +In the New Keynesian application, the same belief shock raises unemployment, +creates comoving unemployment and inflation forecast wedges, and helps close +the unemployment volatility gap left by TFP and monetary-policy shocks alone. + ## Exercises ```{exercise-start} @@ -2067,8 +2136,8 @@ for i, label in zip([I_U, I_PI, I_Y], var_labels): print(f"{label:<16}", *[f"{s:>19.1f}%" for s in shares]) ``` -The belief shock accounts for the majority of unemployment variance, as -reported in the paper. +The belief shock accounts for the majority of unemployment variance in this +calibrated surrogate. Technology shocks drive most of the output variance (through their high persistence and direct effect on productivity). From ea732d4154ef38f28cb2952ef52bc16c4118a5e7 Mon Sep 17 00:00:00 2001 From: HumphreyYang Date: Thu, 11 Jun 2026 19:08:55 +1000 Subject: [PATCH 19/25] updates --- lectures/atkeson_1991.md | 37 +-- lectures/repeat_mh.md | 27 +-- .../subjective_beliefs_business_cycles.md | 198 ++++++++-------- lectures/tsyrennikov_2013.md | 220 +++++++++--------- 4 files changed, 239 insertions(+), 243 deletions(-) diff --git a/lectures/atkeson_1991.md b/lectures/atkeson_1991.md index eaa4a92f..2b716574 100644 --- a/lectures/atkeson_1991.md +++ b/lectures/atkeson_1991.md @@ -49,8 +49,8 @@ $\mathcal{Y} = \{Y_1,\ldots,Y_N\}$ with $0 < Y_1 < \cdots < Y_N$. The technology is built from two fixed probability distributions on $\mathcal{Y}$, which we call $g_0$ and $g_1$. -We think of $g_1$ as the distribution of output when the borrower's effort is -least productive and $g_0$ as the distribution when it is most productive. +We think of $g_1$ as the output distribution when investment is lowest and +$g_0$ as the output distribution when investment is highest. Accordingly $g_0$ places relatively more weight on high outputs than $g_1$ does. @@ -89,7 +89,7 @@ the borrower invested little. ### Agents and preferences -**The borrower** is an infinitely-lived, risk-averse agent with normalised +**The borrower** is an infinitely-lived, risk-averse agent with normalized discounted utility $$ @@ -129,7 +129,7 @@ in full in feasibility condition {eq}`eq:atkeson_feasibility`. $\mathbb{E}_0^{\sigma}$ is the expectation over output histories that this plan induces, evaluated at date $0$. -The factor $(1 - \beta)$ normalises lifetime utility to per-period units, so +The factor $(1 - \beta)$ normalizes lifetime utility to per-period units, so $v$ is comparable to a one-period payoff. **Lenders** are a sequence of short-lived, risk-neutral agents, one born each @@ -379,7 +379,7 @@ $\mathcal V(Q) \subseteq \mathcal B(\mathcal V)(Q)$ for all $Q$. Together, {prf:ref}`atkeson_self_generation` and {prf:ref}`atkeson_factorization` imply -$\mathcal V = \mathcal B(\mathcal V)$, characterising the utility +$\mathcal V = \mathcal B(\mathcal V)$, characterizing the utility possibility correspondence as the fixed point of $\mathcal B$. ### Program P* @@ -508,11 +508,8 @@ after output $Y_j'$, $\eta$ is the multiplier on the relaxed investment-incentive condition, and $\xi_j$ enforces consistency between $v_j$ and the frontier value $\bar v(Q_j')$. -In the paper's numbered notation, $\mu_3(Y_j')$ corresponds to $\mu_j$, -and $\mu_4$ corresponds to $\eta$. - -The numbers are just labels for constraints in the Lagrangian -{eq}`eq:atkeson_relaxed_lagrangian`. +In the numbered notation of {cite:t}`Atkeson1991`, $\mu_3(Y_j')$ corresponds +to $\mu_j$ and $\mu_4$ corresponds to $\eta$. The first-order condition with respect to $v_j$ is, up to the common positive scale factor $\beta g_j(I)$, @@ -717,12 +714,12 @@ v_{\text{aut}}(Q) = $$ Note that the continuation values depend only on $Y_L$ and $Y_H$, not on the -current $Q$, because next period's state is simply the realised output. +current $Q$, because next period's state is simply the realized output. ```{code-cell} ipython3 @jax.jit def autarky_operator_jax(V, β_val, g_high_val, g_low_val): - """One vectorised Bellman step for the autarky problem.""" + """One vectorized Bellman step for the autarky problem.""" V_Y = jnp.interp(Y_j, Q_grid_j, V) g_I = g_of_I_jax(I_grid_j, g_high_val, g_low_val) EV_I = g_I @ V_Y @@ -991,7 +988,7 @@ ax.plot(Q_grid, V_pareto, lw=2, ls='--', label=r'Program P* value $\bar v(Q)$') ax.set_xlabel(r'state $Q$ (output net of repayment)') -ax.set_ylabel('normalised utility') +ax.set_ylabel('normalized utility') ax.legend() plt.tight_layout() plt.show() @@ -1497,12 +1494,6 @@ parameters fixed). Here is one solution: ```{code-cell} ipython3 ---- -mystnb: - figure: - caption: continuation state across patience levels - name: fig-atk-patience ---- fig, ax = plt.subplots() for β_val, ls, color, tag in [ @@ -1524,6 +1515,7 @@ for β_val, ls, color, tag in [ ax.set_xlabel(r'state $Q$') ax.set_ylabel(r"$Q'_L$ (continuation state after low output)") +ax.set_title('continuation state across patience levels') ax.legend() plt.tight_layout() plt.show() @@ -1588,12 +1580,6 @@ weaker signal of investment. Here is one solution: ```{code-cell} ipython3 ---- -mystnb: - figure: - caption: net outflow under weak signal - name: fig-atk-signal ---- # Weak-signal specification g_high_ws = np.array([0.40, 0.60]) g_low_ws = np.array([0.60, 0.40]) @@ -1624,6 +1610,7 @@ ax.plot(Q_grid, net_L_ws, ax.axhline(0, color='k', lw=0.8, ls=':') ax.set_xlabel(r'state $Q$') ax.set_ylabel('net capital outflow') +ax.set_title('net outflow under weak signal') ax.legend() plt.tight_layout() plt.show() diff --git a/lectures/repeat_mh.md b/lectures/repeat_mh.md index 8e327bdd..a0cfc1a4 100644 --- a/lectures/repeat_mh.md +++ b/lectures/repeat_mh.md @@ -15,9 +15,8 @@ kernelspec: ## Overview -This lecture computes information-constrained optima in the -Phelan-Townsend repeated moral-hazard environment -{cite}`Phelan_Townsend_91`. +This lecture computes information-constrained optima in the repeated +moral-hazard environment of {cite:t}`Phelan_Townsend_91`. The environment is a continuum-agent economy with unobserved effort. @@ -28,8 +27,8 @@ discounted social surplus. The key recursive idea comes from {cite:t}`Spear_Srivastava_87`: an agent's promised continuation utility is a sufficient state variable. -Phelan and Townsend combine that idea with lotteries, finite grids, and -linear programming to compute full-information, static +{cite:t}`Phelan_Townsend_91` combine that idea with lotteries, finite grids, +and linear programming to compute full-information, static unobserved-action, and repeated unobserved-action allocations. The lecture proceeds from the recursive formulation to the computational @@ -342,7 +341,7 @@ $$ \quad \forall\, a,\, \hat a \in A. $$ -### Parameterisation +### Parameterization The baseline utility specification is @@ -1103,7 +1102,7 @@ def solve_multi_period_economy(A=None, We use the same parameters as for the static economy, plus a discount factor $\beta = 0.8$ and grids of $N = N_m = 100$ points. -We initialise the value function iteration with the one-period +We initialize the value function iteration with the one-period (static) solution, scaled to discounted-sum units. ```{code-cell} ipython3 @@ -1769,12 +1768,6 @@ $$ ``` ```{code-cell} ipython3 ---- -mystnb: - figure: - caption: agency cost function - name: fig-rmh-agency-cost ---- δ_W = s_W_full - s_W_unobs plt.figure() @@ -1783,6 +1776,7 @@ plt.xlabel("w") plt.ylabel(r"$\delta(w) = s^{FI}(w) - s^{UA}(w)$") plt.xlim([1.0, 5.0]) plt.ylim(bottom=0.0) +plt.title("agency cost function") plt.show() max_i = np.nanargmax(δ_W) @@ -1836,12 +1830,6 @@ in which output is less informative about effort than in the baseline $P$. ``` ```{code-cell} ipython3 ---- -mystnb: - figure: - caption: surplus and effort under flatter probabilities - name: fig-rmh-flat-p ---- P_flat = np.array([[0.70, 0.30], [0.55, 0.45], [0.45, 0.55], @@ -1869,6 +1857,7 @@ axes[1].set_xlim([1.0, 5.0]) axes[1].set_ylim([0.0, 0.8]) axes[1].legend() +fig.suptitle("surplus and effort under flatter probabilities") plt.tight_layout() plt.show() ``` diff --git a/lectures/subjective_beliefs_business_cycles.md b/lectures/subjective_beliefs_business_cycles.md index c6c9bedf..771143b0 100644 --- a/lectures/subjective_beliefs_business_cycles.md +++ b/lectures/subjective_beliefs_business_cycles.md @@ -19,14 +19,14 @@ kernelspec: ## Overview -This lecture presents key ideas from {cite}`bhandari2025survey`, who study -whether household survey data on macroeconomic expectations can discipline -business cycle models. +This lecture studies whether household survey data on macroeconomic +expectations can discipline business cycle models, following +{cite:t}`bhandari2025survey`. -Their central finding is that household forecasts of unemployment and inflation +A central finding is that household forecasts of unemployment and inflation exhibit **systematic upward biases** relative to rational forecasts. -These biases -- which the authors call *belief wedges* -- are: +These biases, called *belief wedges*, are: * **Persistent and countercyclical**: they are larger during recessions. * **Positively correlated**: optimism/pessimism about unemployment and inflation @@ -34,17 +34,17 @@ These biases -- which the authors call *belief wedges* -- are: * **One-factor in structure**: a single latent state accounts for most variation across wedges. -The paper represents this evidence through the lens of +We interpret this evidence using **robust preferences** ({cite:t}`HansenSargent2001`; {cite:t}`HansenSargent2008`). -The robust preference serves as a model of pessimism and optimism: +Robust preferences model pessimism and optimism: agents act as if they overweight states that deliver low continuation values (pessimism) and underweight those that deliver high continuation values (optimism). When calibrated to the Michigan Survey of Consumers (1982Q1-2019Q4), this mechanism yields a time-varying *belief shock* -that substantially reduces the well-known **unemployment volatility puzzle** --- +that substantially reduces the **unemployment volatility puzzle** --- the fact that standard New Keynesian models with only technology and monetary policy shocks generate far too little unemployment volatility. @@ -52,7 +52,7 @@ In this lecture, we will cover: * How to define and measure belief wedges from household survey data. * How robust preferences generate time-varying subjective beliefs. -* How belief distortions propagate through a linearised DSGE model. +* How belief distortions propagate through a linearized DSGE model. * Why a calibrated belief shock helps resolve the unemployment volatility puzzle. @@ -61,8 +61,8 @@ The lecture is self-contained in the following sense. All empirical moments, calibration values, and model objects used in the code are reported below, so no external data files are needed. -We use the paper for motivation and for several benchmark numbers, but the -computations in this lecture are generated from the equations and parameter +Benchmark numbers from {cite:t}`bhandari2025survey` are quoted for comparison, +but every computation below is generated from the equations and parameter values stated here. Some notation and units will be used throughout: @@ -110,8 +110,8 @@ data-generating forecast. For unemployment and inflation, this sign convention corresponds to an upward forecast bias. -The empirical objects in {cite}`bhandari2025survey` are mostly one-year-ahead, -or four-quarter, wedges: +The empirical objects below are mostly one-year-ahead, or four-quarter, +wedges: $$ @@ -136,9 +136,9 @@ Thus the lecture uses one object in two related ways: empirically, a belief wedge is a survey forecast minus a statistical benchmark forecast; in the model, it is a subjective expectation minus an objective expectation. -The raw Michigan unemployment question is categorical, so Bhandari et al. +The raw Michigan unemployment question is categorical, so {cite:t}`bhandari2025survey` convert it into a quantitative forecast using the Carlson--Parkin procedure as -adapted by {cite}`MankiwReisWolfers2003`. +adapted by {cite:t}`MankiwReisWolfers2003`. We do not need those raw survey responses below. @@ -147,7 +147,7 @@ moments listed next. ### Empirical facts -Using data from 1982Q1 to 2019Q4, the authors document: +Using data from 1982Q1 to 2019Q4, {cite:t}`bhandari2025survey` document: | Statistic | Unemployment wedge | Inflation wedge | |---|---|---| @@ -345,14 +345,14 @@ keeps the distortion from being too extreme. The scalar $\theta_t$ controls the direction and strength of the belief tilt. -The minimisation problem above corresponds to $\theta_t > 0$: larger +The minimization problem above corresponds to $\theta_t > 0$: larger $\theta_t$ means more pessimism. -The paper also allows optimism ($\theta_t < 0$), in which case the analogous -inner problem is a maximisation that tilts probability toward +Optimism corresponds to $\theta_t < 0$, in which case the inner problem +becomes a maximization that tilts probability toward high-continuation-value states. -The inner minimisation has a closed-form solution: +The inner minimization has a closed-form solution: $$ @@ -488,7 +488,7 @@ class BeliefModel(NamedTuple): μ_θ: float # mean of the belief-shock parameter θ ρ_θ: float # AR(1) persistence of θ σ_θ: float # volatility of the θ innovation - Vx: float # slope of the linearised continuation value + Vx: float # slope of the linearized continuation value def create_belief_model(β=0.994, ρ_x=0.85, σ_x=0.005, @@ -603,7 +603,7 @@ believes bad shocks are more likely than they actually are. ### The perturbation method -For quantitative analysis, {cite}`bhandari2025survey` extend the standard +For quantitative analysis, {cite:t}`bhandari2025survey` extend the standard first-order perturbation method to accommodate time-varying belief distortions. Let the state vector be $x_t \in \mathbb{R}^n$ with **objective** law of @@ -619,7 +619,7 @@ $$ Write the local scalar belief factor as $\vartheta_t = \bar\theta(\bar{x} + x_{1t})$. -Under the optimal belief distortion the shocks are re-centred: +Under the optimal belief distortion the shocks are re-centered: $$ @@ -633,8 +633,7 @@ and $\bar{x}$ is the non-stochastic steady state. This perturbation preserves nontrivial first-order effects of belief distortions. -The resulting **belief wedge** for any variable $z$ with model-consistent -expected value $\bar{z}' x$ is +The resulting **belief wedge** for any variable $z_t = \bar{z}' x_t$ is $$ @@ -665,12 +664,12 @@ An important consequence of the formula for $\Delta_t^{(1)}(z)$ is that the *time variation* in all belief wedges is driven by the **single scalar** belief factor $\vartheta_t$. -The cross-sectional loadings $\bar{z}'(\psi_w\psi_w')V_x'$ are +The cross-sectional loadings $-\bar{z}'(\psi_w\psi_w')V_x'$ are fixed by the model's structural parameters. This theoretical prediction matches the empirical finding that one principal component explains 78.6% -of the joint variation in household forecast errors. +of the joint variation in the unemployment and inflation wedges. ```{code-cell} ipython3 --- @@ -730,8 +729,8 @@ an almost exact positive relation. ### Model description -{cite}`bhandari2025survey` embed the belief-distortion mechanism in a -New Keynesian model with a **search-and-matching** labour market +Following {cite:t}`bhandari2025survey`, we embed the belief-distortion +mechanism in a New Keynesian model with a **search-and-matching** labor market ({cite}`Shimer2005`; {cite}`ChristianoEichenbaumTrabandt2016`). The key @@ -744,7 +743,7 @@ Employed members earn the wage, unemployed members receive a benefit flow $D$, and the household applies robust preferences (indexed by $\theta_t$) when forming subjective forecasts. -**Firms** --- Labour-market firms post vacancies and match with searching workers, +**Firms** --- Labor-market firms post vacancies and match with searching workers, while monopolistic intermediate-goods producers reset prices subject to Calvo frictions (parameter $\chi_p$), generating a New Keynesian Phillips curve. @@ -770,6 +769,7 @@ The model is calibrated to quarterly U.S. data, 1982Q1–2019Q4. | Price stickiness | $\chi_p$ | 0.75 | Calvo parameter | | Wage rigidity | $\chi_w$ | 0.925 | Partial adjustment | | Steady-state markup | $\lambda$ | 1.2 | | +| Policy-rule intercept | $\bar\pi$ | 0.01 | Inflation target | | Policy-rule smoothing | $\rho_r$ | 0.84 | | | Taylor-rule inflation loading | $r_\pi$ | 1.60 | | | Taylor-rule output loading | $r_y$ | 0.028 | | @@ -788,9 +788,10 @@ The model is calibrated to quarterly U.S. data, 1982Q1–2019Q4. ### A self-contained linear surrogate -The paper solves a structural New Keynesian model. +Solving the full structural model requires the series expansion method +described in the appendix. -For computation in this lecture, we use a small reduced-form vector +For computation in the main text, we use a small reduced-form vector autoregression that is calibrated to reproduce the main benchmark moments in the moment table reported below and the qualitative shape of the belief-shock impulse responses: @@ -959,10 +960,10 @@ nk = create_nk_model() The table below collects the empirical moments and model moments most relevant for this lecture. -All values are percentages or percentage points; inflation is annualised and +All values are percentages or percentage points; inflation is annualized and output is detrended. -| Moment | Data | Paper benchmark | No $\theta_t$ | Only $\theta_t$ | +| Moment | Data | Benchmark | No $\theta_t$ | Only $\theta_t$ | |---|---:|---:|---:|---:| | Mean inflation wedge | 1.22 | 0.90 | 0.00 | 0.00 | | Mean unemployment wedge | 0.52 | 0.55 | 0.00 | 0.00 | @@ -1074,15 +1075,15 @@ The second row plots the belief shock itself and the two implied survey wedges. The impulse responses show that a belief shock: * Raises unemployment persistently. -* Raises inflation on impact, with the response gradually decaying back to zero - in this reduced-form representation. +* Raises inflation temporarily, with the response gradually decaying back to + zero in this reduced-form representation. * Lowers output, so the shock looks like a pessimistic recessionary force. * Generates belief wedges for both unemployment and inflation that closely mirror the dynamics of $\theta_t$ itself --- consistent with the one-factor structure. The structural model contains one additional object that the surrogate does not -plot: impulse responses under the **subjective** measure. +capture: impulse responses under the **subjective** measure. After a positive $\theta_t$ innovation, pessimistic agents behave as if future TFP shocks are worse, monetary policy shocks are tighter, and future @@ -1114,7 +1115,7 @@ std_no_θ = unconditional_stds(nk, include_θ_shock=False) labels_vol = ['Unemployment', 'Inflation', 'Output'] idx = [I_U, I_PI, I_Y] -scale = [100, 400, 100] # convert to pp (unemployment, annualised inflation, %) +scale = [100, 400, 100] # convert to pp (unemployment, annualized inflation, %) std_full_scaled = [std_full[i] * scale[j] for j, i in enumerate(idx)] std_no_θ_scaled = [std_no_θ[i] * scale[j] for j, i in enumerate(idx)] @@ -1220,11 +1221,10 @@ pedagogical representation. ### Role of firms' beliefs -In the benchmark model of {cite}`bhandari2025survey`, **firms** as well as +In the benchmark model, **firms** as well as households hold subjective beliefs. -The paper studies how the results change when firms instead have rational -beliefs. +What changes when firms instead have rational beliefs? The key channel is through the price-setting equation. @@ -1237,7 +1237,7 @@ recalibrating $\theta_t$ so that the mean and volatility of the unemployment wedge remain comparable. If firms have rational beliefs, they see the household pessimism shock mainly -as contractionary demand. +as a contraction in demand. Inflation falls on impact, and the inflation wedge is too small. @@ -1250,7 +1250,7 @@ households, put extra subjective probability on high-marginal-cost states. ### Two diagnostic variants -The paper uses diagnostic variants to show how survey wedges restrict the +Two diagnostic variants show how survey wedges restrict the model. **No TFP shocks** --- Without supply-side uncertainty, pessimistic agents worry @@ -1283,7 +1283,7 @@ The table below summarizes the two restrictions. | Volatility of unemployment | 1.39 | 0.87 | 1.24 | These variants show why the benchmark needs both supply-side uncertainty and -firms' subjective beliefs to match the joint behaviour of inflation and +firms' subjective beliefs to match the joint behavior of inflation and unemployment forecasts. ### Countercyclicality of wedges @@ -1350,16 +1350,14 @@ predicted by the model and documented in the survey data. ### Further empirical checks -{cite}`bhandari2025survey` also verify the mechanism with reduced-form -evidence. +Reduced-form evidence provides additional support for the mechanism. -They estimate local projections of macroeconomic variables and survey forecasts -on innovations to the first principal component of the belief wedges. +In local projections of macroeconomic variables and survey forecasts on +innovations to the first principal component of the belief wedges, a positive +innovation predicts higher unemployment, higher belief wedges, and an +inflation response that is briefly positive before turning mildly negative. -A positive innovation predicts higher unemployment, higher belief wedges, and -an inflation response that is briefly positive before turning mildly negative. - -They also run forecast-error regressions of the form +A second check comes from forecast-error regressions of the form $$ @@ -1374,17 +1372,16 @@ Under full-information rational expectations these errors should be mean zero and unforecastable. The survey data and the calibrated model both generate predictable forecast -errors, providing another check on the subjective-belief mechanism. +errors, consistent with the subjective-belief mechanism. ## Extensions -The paper explores several important extensions: +Several extensions of the benchmark model are worth noting: **Heterogeneous beliefs** --- A natural question is whether households and firms should hold the same subjective beliefs. -The paper shows that -allowing firms to be *rational* while households are pessimistic changes +Allowing firms to be *rational* while households are pessimistic changes the inflation dynamics substantially. This separation is identified from @@ -1408,7 +1405,7 @@ worse than in the benchmark with an orthogonal belief shock. When wages are flexible, unemployment volatility falls to $0.77$ and unemployment-wedge volatility falls to $0.13$. -This is the Shimer-style labour-market amplification problem in another form: +This is the Shimer-style labor-market amplification problem in another form: without sluggish wages, belief shocks and TFP shocks move match values too little. @@ -1429,15 +1426,15 @@ pessimism and the belief wedges increase without any exogenous shock to $\theta_t$. The supporting empirical idea is that belief wedges comove with the -{cite}`Schmidt2016` index of idiosyncratic labour-income skewness, which +{cite}`Schmidt2016` index of idiosyncratic labor-income skewness, which proxies for the risk of large losses such as job loss. ## Appendix: the series expansion method This appendix gives the computational and theoretical details underlying the -linearisation presented in the main lecture. +linearization presented in the main lecture. -The formulas follow {cite}`bhandari2025survey`, but the notation needed for the +The formulas follow {cite:t}`bhandari2025survey`, but the notation needed for the calculations below is introduced here. ### Multi-period belief wedges @@ -1458,21 +1455,23 @@ x_{t+1} = \psi_q + \psi_x x_t + \psi_w w_{t+1}, $$ -the $\tau$-period-ahead expectation under the data-generating measure -satisfies the recursion +the $\tau$-period-ahead expectation of the state deviation under the +data-generating measure is $$ -G_x^{(\tau)} = \psi_x G_x^{(\tau-1)} + \psi_x, +E_t[x_{1,t+\tau}] = G_x^{(\tau)} x_{1t} + G_0^{(\tau)}, \qquad -G_x^{(0)} = 0, +G_x^{(\tau)} = \psi_x G_x^{(\tau-1)}, +\quad +G_0^{(\tau)} = \psi_x G_0^{(\tau-1)} + \psi_q, $$ -so that $E_t[x_{t+\tau} - x_t] = G_x^{(\tau)} x_{1t} + G_0^{(\tau)}$. +with initial conditions $G_x^{(0)} = I$ and $G_0^{(0)} = 0$. Under the **subjective** measure, the mean of $w_{t+1}$ is shifted to -$\nu_t = H + HF x_{1t}$. +$\nu_t = \bar H + HF x_{1t}$. For the stationary model the relevant identifications are @@ -1486,24 +1485,32 @@ H = -(V_x \psi_w)', $$ -The subjective expectation then obeys a modified recursion +The shift is equivalent to replacing the transition matrices by their +subjective counterparts $$ -\tilde G_x^{(\tau)} = \psi_x \tilde G_x^{(\tau-1)} + \psi_x - + \bigl(\psi_w + \tilde G_x^{(\tau-1)}\psi_w\bigr) HF, +\tilde\psi_x = \psi_x + \psi_w H F, +\qquad +\tilde\psi_q = \psi_q + \psi_w \bar H, $$ -and the $\tau$-period belief wedge is +so the subjective loadings $\tilde G_x^{(\tau)}$ and $\tilde G_0^{(\tau)}$ +satisfy the same recursions with $\tilde\psi_x$ and $\tilde\psi_q$ in place +of $\psi_x$ and $\psi_q$. + +The $\tau$-period belief wedge is then $$ \Delta_t^{(\tau)} = \bigl(\tilde G_x^{(\tau)} - G_x^{(\tau)}\bigr) x_{1t} - + \tilde G_0^{(\tau)} - G_0^{(\tau)}. + + \tilde G_0^{(\tau)} - G_0^{(\tau)}, $$ +which reduces to the one-period wedge formula at $\tau = 1$. + The code below implements these recursions and shows how belief wedges grow with the forecast horizon. @@ -1521,8 +1528,11 @@ def compute_tau_wedge_loadings(ψ_x, ψ_w, H, H_bar, F, τ_max=20): wedge_slope : array (tau_max,) x1t loading of wedge (Gx_tilde - Gx) """ n = ψ_x.shape[0] - Gx = np.zeros((n, n)) - Gx_tild = np.zeros((n, n)) + ψ_x_tild = ψ_x + ψ_w @ (H @ F) # subjective transition matrix + ψ_q_tild = (ψ_w @ H_bar).ravel() # subjective intercept + + Gx = np.eye(n) + Gx_tild = np.eye(n) G0 = np.zeros(n) G0_tild = np.zeros(n) @@ -1530,16 +1540,10 @@ def compute_tau_wedge_loadings(ψ_x, ψ_w, H, H_bar, F, τ_max=20): wedge_slope = np.zeros((τ_max, n)) for τ in range(1, τ_max + 1): - new_Gx = (Gx + np.eye(n)) @ ψ_x - new_G0 = G0 + (Gx + np.eye(n)) @ ψ_w @ np.zeros(ψ_w.shape[1]) - - new_Gx_tild = ((Gx_tild + np.eye(n)) @ ψ_x - + (Gx_tild + np.eye(n)) @ ψ_w @ (H @ F)) - new_G0_tild = (G0_tild - + ((Gx_tild + np.eye(n)) @ ψ_w @ H_bar).ravel()) - - Gx, G0 = new_Gx, new_G0 - Gx_tild, G0_tild = new_Gx_tild, new_G0_tild + Gx = ψ_x @ Gx + Gx_tild = ψ_x_tild @ Gx_tild + G0 = ψ_x @ G0 # ψ_q = 0 under the objective measure + G0_tild = ψ_x_tild @ G0_tild + ψ_q_tild wedge_slope[τ - 1] = (Gx_tild - Gx)[0] wedge_const[τ - 1] = float((G0_tild - G0)[0]) @@ -1565,14 +1569,16 @@ H_bar_sc = -model.μ_θ * x_bar_sc * np.array([[model.Vx * model.σ_x]]) τ_max = 20 wc, ws = compute_tau_wedge_loadings(ψ_x_sc, ψ_w_sc, H_sc, H_bar_sc, F_sc, τ_max) -# Evaluate at a one-standard-deviation belief shock. +# State deviation that raises the belief factor by one +# unconditional standard deviation of the belief shock. θ_std = model.σ_θ / np.sqrt(1 - model.ρ_θ**2) +x_dev = θ_std / model.μ_θ fig, ax = plt.subplots() τ_grid = np.arange(1, τ_max + 1) ax.plot(τ_grid, wc * 100, color='steelblue', linewidth=2, label='Wedge at mean ($x_{1t}=0$)') -ax.plot(τ_grid, (wc + ws[:, 0] * θ_std) * 100, +ax.plot(τ_grid, (wc + ws[:, 0] * x_dev) * 100, color='firebrick', linewidth=2, linestyle='--', label='Wedge at $+1\\,\\sigma_\\theta$ deviation') ax.axhline(0, color='grey', linewidth=0.7, linestyle=':') @@ -1595,7 +1601,7 @@ affect not only the next shock but also the expected path of future states. ### The series expansion -{cite}`bhandari2025survey` solve the full general-equilibrium model +{cite:t}`bhandari2025survey` solve the full general-equilibrium model using a **series expansion** (perturbation) method ({cite}`BorovickaHansen2014`). @@ -1636,7 +1642,7 @@ $$ To preserve a nontrivial role for beliefs at first order, the penalty parameter is **jointly scaled** with $\mathsf{q}$: the effective -penalisation in the perturbed recursion is +penalization in the perturbed recursion is $\mathsf{q}/[\bar\theta(\bar x + x_{1t})]$, which shrinks together with shock volatility. @@ -2104,7 +2110,8 @@ Using the reduced-form NK model built by `create_nk_model`: (a) Compute the fraction of unemployment variance explained by each of the three shocks. (b) Show that the belief shock is the dominant driver of unemployment - fluctuations, while TFP is the dominant driver of output fluctuations. + fluctuations, while TFP shocks matter much more for inflation and + output than they do for unemployment. ```{exercise-end} ``` @@ -2136,14 +2143,17 @@ for i, label in zip([I_U, I_PI, I_Y], var_labels): print(f"{label:<16}", *[f"{s:>19.1f}%" for s in shares]) ``` -The belief shock accounts for the majority of unemployment variance in this -calibrated surrogate. +The belief shock accounts for the large majority of unemployment variance in +this calibrated surrogate. + +Technology shocks drive most of the inflation variance, and output variance +is split roughly evenly between the belief and TFP shocks. -Technology shocks drive most of the output variance -(through their high persistence and direct effect on productivity). +Monetary policy shocks play a negligible role for all three variables. -Monetary -policy shocks play a smaller role for both variables. +This pattern matches the variance decomposition of the structural model, in +which the belief shock dominates unemployment while technology shocks account +for most of the variation in inflation. ```{solution-end} ``` diff --git a/lectures/tsyrennikov_2013.md b/lectures/tsyrennikov_2013.md index 98d5c962..da649997 100644 --- a/lectures/tsyrennikov_2013.md +++ b/lectures/tsyrennikov_2013.md @@ -41,7 +41,7 @@ The mechanism is that moral hazard severely restricts the amount of As a result, the optimal repayment is nearly *non-contingent* on output. -This justifies why non-contingent debt is an optimal way to finance an emerging +This explains why non-contingent debt is an optimal way to finance an emerging economy. Moral hazard also gives the model a strong internal propagation mechanism: even @@ -53,11 +53,11 @@ Tsyrennikov is also explicit about the model's main weakness. The mechanism improves the behavior of consumption, output and spreads, but it does not fully match the observed current-account dynamics. -### The mechanism without figures +### The mechanism in brief -A compact way to read the model is as follows. +The borrower can use foreign funds for either current consumption or +investment. -The borrower can use foreign funds for either current consumption or investment. Investment is costly today, but it raises the probability of high output tomorrow. @@ -131,8 +131,7 @@ reverse. For comparison, Canada displays much smoother consumption and much weaker spread-output comovement. -The following reduced version of the paper's data table highlights the contrast. - +The following table, condensed from the paper, highlights the contrast. Here and in the moments table below, $E(\cdot)$ is a mean, $\sigma(\cdot)$ a standard deviation, and $\rho(\cdot,\cdot)$ a correlation, while $\rho(y)$ is the @@ -164,7 +163,7 @@ or weak institutions. ### Technology and preferences -The environment is a small open economy with an infinitely-lived borrower. +The environment is a small open economy with an infinitely lived borrower. The borrower starts each period with net worth $n$ (output net of debt repayment), borrows $b$ from a short-lived risk-neutral lender, invests $I$, @@ -203,9 +202,12 @@ distribution under higher investment first-order stochastically dominates that under lower investment, with diminishing returns. ```{note} -This is the same mixture technology, and uses the same labelling, as in +This is the same mixture technology, and uses the same labeling, as in {doc}`atkeson_1991`: the weight $\lambda(I)$ multiplies the favorable distribution $g_0$, so more investment makes high output more likely. + +{cite:t}`Tsyrennikov2013` swaps the labels: there the weight $\lambda(I)$ +multiplies $g_1$, which is the favorable distribution in the paper's notation. ``` Tsyrennikov restricts to two output states, so the favorable distribution puts @@ -247,8 +249,9 @@ so they lend at the international gross risk-free rate $1/\beta_c$. Each lender lives for two periods with endowment $M$, so the loan cannot exceed it: $b \leq M$. -The assumption $\beta \leq \beta_c$ reflects that the government of an emerging -economy may be more impatient than a typical international lender. +The assumption $\beta \leq \beta_c$ captures the idea that the government of an +emerging economy may have a shorter planning horizon than a typical +international lender. A contract between the two parties specifies the loan $b$ and the repayment $d_j$ that the borrower makes after each output state $Y_j$. @@ -264,8 +267,10 @@ The incentive-compatibility (IC) constraint {eq}`eq:tsyrennikov_ic` requires that the borrower finds the recommended investment $I$ privately optimal. **Limited enforcement**: the borrower can default, suffering a one-time -output penalty: if default occurs when output is $Y_j$, the borrower retains -only $\delta Y_j$ (with $\delta \in (0,1)$) and then lives in autarky. +output penalty. + +If default occurs when output is $Y_j$, the borrower retains only +$\delta Y_j$, with $\delta \in (0,1)$, and then lives in autarky forever. Let $v_{\text{aut}}^{\delta}(Y_j)$ denote the value after default in state $j$: @@ -280,9 +285,11 @@ v_{\text{aut}}^{\delta}(Y_j) \right\}. $$ (eq:tsyrennikov_default_value) -Here $v_{\text{aut}}$ is the borrower's no-credit autarky value, defined in the -next subsection; the superscript $\delta$ marks the one-period output loss -incurred on entering autarky. +Here $v_{\text{aut}}$ is the borrower's autarky value, defined in the next +subsection. + +The superscript $\delta$ marks the one-time output loss incurred on entering +autarky. The enforcement constraint requires @@ -323,7 +330,7 @@ $$ Net worth converges to a constant, so consumption and investment are eventually constant, and the risk-sharing index defined below equals one. -This benchmark implies uninhibited risk-sharing and a strongly procyclical +This benchmark implies full risk sharing and a strongly procyclical current account --- the opposite of what the data show. It also shows what moral hazard limits: the ability to make repayments @@ -333,8 +340,9 @@ strongly state contingent without weakening investment incentives. The state variable is net worth $n$. -The optimal long-term contract can be represented recursively with this -single state is established in {cite:t}`Atkeson1991`, and we take it as given. +{cite:t}`Atkeson1991` establishes that the optimal long-term contract can be +represented recursively with this single state, and we take this result as +given. Let @@ -353,8 +361,9 @@ $$ (eq:tsyrennikov_bellman) subject to the following constraints. -A feasibility constraint combines the budget constraint {eq}`eq:tsyrennikov_budget`, with -nonnegative consumption $c = n + b - \theta I \geq 0$ and investment $I \geq 0$. +Feasibility requires the budget constraint {eq}`eq:tsyrennikov_budget` with +nonnegative consumption $c = n + b - \theta I \geq 0$ and nonnegative +investment $I \geq 0$. Lender participation requires the loan not to exceed the discounted expected value of repayments, @@ -369,8 +378,8 @@ $b \leq M$. The contract must also satisfy incentive compatibility {eq}`eq:tsyrennikov_ic` and the enforcement constraint {eq}`eq:tsyrennikov_enforcement`. -The incentive constraint says that the borrower chooses the recommended -investment from the feasible set: +The incentive constraint says that the recommended investment must be the +borrower's own best choice from the feasible set: $$ I \in \arg\max_{0 \leq \hat I \leq n+b} @@ -464,7 +473,8 @@ condition, so {eq}`eq:tsyrennikov_ic` and {eq}`eq:tsyrennikov_relaxed_ic` --- th latter holding with equality at an interior optimum --- select the same investment. -Next, we need to show that *every optimal contract has $S \geq 0$, so the first-order condition is sufficient at the optimum.* +Next, we show that *every optimal contract can be replaced by an equivalent one +with $S \geq 0$.* Suppose an optimal contract had $S < 0$, that is $v(n_2') < v(n_1')$. @@ -609,9 +619,9 @@ the equation pushes $v'(n_1')$ above $v'(n)$. By concavity of $v$, the borrower's net worth then falls in the low state. -This is the **immiseration** property: moral hazard forces the borrower to bear -more risk than would be optimal with full information -(see, e.g., {cite:t}`ThomasWorrall1990`, {cite:t}`AtkesonLucas1992`). +This is the **immiseration** property: incentive provision drags the borrower's +net worth down over time, a force also present in the private-information +economies of {cite:t}`ThomasWorrall1990` and {cite:t}`AtkesonLucas1992`. To isolate this force, set $\beta = \beta_c$, $\phi = 0$ and $\xi_j = 0$. @@ -635,8 +645,8 @@ $v'(n) = \mathbb{E}\,v'(n_j') + \sum_j g(Y_j\mid I)\,\xi_j v'(n_j') \geq \mathbb{E}\,v'(n_j')$, since $\xi_j \geq 0$, which implies upward drift in continuation net worth under concavity. -The optimal contract can be reinterpreted as a government-borrower that, instead -of signing a contract, faces an **implied interest rate** schedule $R(n)$ on each +The optimal contract can be decentralized: instead of signing a contract, the +government-borrower faces an **implied interest rate** schedule $R(n)$ on each unit borrowed: $$ @@ -652,8 +662,8 @@ increasing $R$. ## Computation -We now implement a lightweight numerical illustration using the parameterisation -from {cite:t}`Tsyrennikov2013`. +We now implement a lightweight numerical illustration using the +parameterization from {cite:t}`Tsyrennikov2013`. The code solves three economies: @@ -686,12 +696,9 @@ amount and the endowment $M$. For MH+LE and LE, borrowing is limited endogenously by the borrower's default value. -The resulting policy functions are intended to show the economic mechanism and -to move the lecture figures closer to Figures 3 and 4 of -{cite:t}`Tsyrennikov2013`. - -They should not be read as a full replication of the paper's numerical -algorithm. +The resulting policy functions illustrate the economic mechanism and +approximate Figures 3 and 4 of {cite:t}`Tsyrennikov2013`, but they are not a +full replication of the paper's numerical algorithm. The paper solves the Bellman equation iteratively, approximates the value function by a cubic spline on $[0.2, 1.2]$ with 100 nodes, and stops when the @@ -702,8 +709,8 @@ borrowing limits with a damped rule that gives one-half weight to the previous limit and one-half weight to the new limit implied by the current value function. -The lecture code below uses the same recursive economic problem, but a more -transparent two-stage approximation. +The code below solves the same recursive problem with a simpler two-stage +approximation. First, it computes the fixed point quickly with JAX, Howard policy iteration, linear interpolation of the value function, and a finite mesh of continuation @@ -722,11 +729,8 @@ $$ = 1 - \frac{n_2'-n_1'}{Y_2-Y_1}. $$ -This makes the near-zero moral-hazard risk-sharing result numerically -representable even when the coarse mesh used in the fixed-point step is modest. - -The plotted curves should therefore be read as polished numerical -approximations to the same contract problem, not as a separate economic model. +This makes the near-zero risk-sharing index under moral hazard representable +even though the fixed-point step uses a coarse mesh. To reach a fixed point quickly, all three economies are solved by **Howard policy iteration** (also called modified policy iteration). @@ -770,6 +774,11 @@ from scipy.optimize import minimize We store the parameters in a `NamedTuple`, with defaults calibrated to Argentina as in {cite:t}`Tsyrennikov2013`. +In the paper's calibration, $\beta_c$ matches a world interest rate of 4%, +$\ln Y_j = \pm 0.054$ matches output volatility, $\theta$ normalizes mean +output to one, and $\delta$ and $M$ match average debt-to-output ratios of +0.420 and 0.410 in the full and MH-only models. + ```{code-cell} ipython3 class Model(NamedTuple): β: float # borrower discount factor @@ -1009,8 +1018,8 @@ b = \beta_c\,\mathbb E[d_j] b = M. $$ -The first is lender participation binding; the second is the lender endowment -constraint binding. +In the first regime lender participation binds; in the second the lender +endowment constraint binds. For MH+LE, we set the exogenous cap to a very large value and rely on the endogenous borrowing limits instead. @@ -1864,20 +1873,17 @@ state non-contingent repayment: the maximal risk-sharing index is below 0.01. In the limited-enforcement economy, by contrast, the same index is about 0.80 on average, so the contract offers a significant amount of insurance. -The polished computation should be read against that benchmark. +Our computed schedules show the same pattern: under moral hazard the repayment +schedule $\{d_1(n), d_2(n)\}$ is nearly state non-contingent on the relevant +range of net worth, while under limited enforcement it is much more state +contingent. -The diagnostic to watch is not the exact maximum at grid endpoints or at -near-certain investment corners, but whether the repayment schedule -$\{d_1(n), d_2(n)\}$ is nearly state non-contingent on the economically relevant -support under moral hazard and much more state contingent under limited -enforcement. +Small irregularities near the grid endpoints are numerical artifacts. -Any remaining endpoint irregularities should be interpreted as numerical -approximation artifacts rather than features of the contract in the paper. - -This is the paper's central policy result: under moral hazard nearly all the -risk is assumed by the risk-averse borrower, and insurance comes mainly through -access to borrowing rather than through state-contingent repayment. +This is the paper's central result about the optimal contract: under moral +hazard nearly all the risk is assumed by the risk-averse borrower, and +insurance comes mainly through access to borrowing rather than through +state-contingent repayment. ### Policy functions @@ -1988,6 +1994,9 @@ $E[Y_j-d_j(n)]$. The MH schedule lies below the LE schedule at high net worth, so net worth drifts down faster in the MH economy. +In the paper this slope difference is only 0.005, yet it is equivalent to +raising the borrower's discount rate by 2% per annum. + At low net worth, expected future net worth can decrease with current net worth because the endogenous improvement in the output distribution raises the probability of the large repayment. @@ -2004,6 +2013,8 @@ The LE investment schedule is also higher and less volatile: investment is observable, so the creditor can dictate more investment than the borrower would choose under moral hazard. +For this reason the LE economy's internal propagation mechanism is weak. + Panel E plots capital outflows, denoted $ca_j(n)$ in the paper. Current output matters because it determines the repayment due on the previous @@ -2110,7 +2121,7 @@ for name, crisis in crises.items(): f"{low_path_prob:.4f}") ``` -The simulation should be read in the same way as Figure 4 in the paper. +The simulation parallels Figure 4 of the paper. Starting from zero debt, a path of low-output realizations makes the MH economy steadily accumulate obligations. @@ -2119,52 +2130,57 @@ Debt/output and the current account move before the interest rate does. When the borrower nearly exhausts borrowing capacity, the interest rate jumps. -Thus the interest rate is a **late warning** about the economy's health, unlike -debt and the current account. +Thus the interest rate gives a **late warning** about the economy's health, +unlike debt and the current account. -Panel C shows the current account first increasing gradually, meaning that -capital inflows gradually shrink, and then moving sharply when borrowing -capacity is nearly exhausted. +The bottom-left panel shows the current account first increasing gradually, +meaning that capital inflows gradually shrink, and then moving sharply when +borrowing capacity is nearly exhausted. -Panel D shows the probability of the high-output outcome. +The bottom-right panel shows the probability of the high-output outcome. As the borrower's net worth deteriorates, investment falls and $\lambda(I)$ falls, making the low-output path more likely than it would be in the frictionless or LE economies. +In the paper's calibration, the crisis path is 7.55 times more likely in the +MH economy than in a frictionless economy, while in the LE economy it is about +as likely as in the frictionless economy (a factor of 1.01). + The paper reports the MH+LE economy as visually close to the MH economy, so the main comparison is between MH and LE. ### MH versus limited enforcement -A crucial result of {cite:t}`Tsyrennikov2013` is that limited enforcement, -alone or together with moral hazard, has nearly no effect on the model's -performance relative to moral hazard alone. +A crucial result of {cite:t}`Tsyrennikov2013` is that limited enforcement +contributes almost nothing: on its own it leaves the model close to the +frictionless benchmark, and added to moral hazard it barely changes the MH +results. The reason is visible in the Euler equations. Moral hazard and limited enforcement push the dynamics in opposite directions. Moral hazard requires the creditor to spread the continuation value of the -borrower across future states, which unloads risk onto the borrower and produces +borrower across future states, which shifts risk onto the borrower and produces immiseration. Limited enforcement without moral hazard pushes expected net worth upward until -the enforcement constraints become non-operative. +the enforcement constraints no longer bind. When both frictions are present, limited enforcement can **turn off** moral hazard near the borrowing limits: the borrowing limits already spread continuation values enough to provide incentives, so the incentive multiplier -collapses. +collapses to zero. ## Quantitative comparison The paper's simulation results show why the moral-hazard mechanism matters. -The table below reports a reduced version of the model moments. +The table below condenses the paper's table of simulated model moments. -The MH+LE economy is very close to the MH economy in the paper's simulations, -so the reduced table reports the MH column as the relevant moral-hazard +Because the MH+LE economy is very close to the MH economy in the paper's +simulations, the table reports only the MH column as the moral-hazard benchmark. | moment | data | MH, i.i.d. | LE, i.i.d. | MH, persistent | LE, persistent | @@ -2193,7 +2209,7 @@ $$ \Pr(Y_2 \mid I, Y_2) = p + \lambda(I)(1-p), $$ -using $p=0.50$ in the recalibration. +with $p = 0.50$, recalibrating $\theta = 0.102$ and $\nu = 0.90$. This persistent component improves the fit for consumption and output. @@ -2202,27 +2218,30 @@ The current-account problem remains. In the data, the trade balance is strongly countercyclical, while the model's trade balance is still procyclical in the reported simulations. -Tsyrennikov argues that stochastic growth or multiperiod capital would likely -be needed to address this shortcoming. +Because invested goods last only one period, the model behaves much like an +exchange economy, and Tsyrennikov shows that no exchange-economy model can +match the observed consumption and current-account moments at the same time. + +He argues that stochastic growth or multiperiod capital would likely be needed +to address this shortcoming. ## Empirical test {cite:t}`Tsyrennikov2013` proposes a test to distinguish moral hazard from limited enforcement. -After a low past output realization ($y_{t-1} = Y_1$), -the MH contract lowers net worth sharply, reducing future consumption -smoothing. +After a low past output realization ($y_{t-1} = Y_1$), the MH contract lowers +net worth sharply, leaving less room for consumption smoothing. -This prediction is: +The prediction is $$ \text{MH economy}: \quad \rho(c_t, y_t \mid y_{t-1} = Y_1) \;>\; \rho(c_t, y_t \mid y_{t-1} = Y_2), $$ -while the LE economy gives the opposite ordering (insurance is better after -low realizations). +while the LE economy implies the opposite ordering, because insurance is +better after low realizations. Using Argentine quarterly data (1993--2005), the observed correlations are 0.98 (after low output) vs. 0.91 (after high output) --- @@ -2259,9 +2278,9 @@ fraction of output retained after default. 2. For each $\delta$, compare the two enforcement thresholds. 3. Discuss: how does a milder default penalty, corresponding to a larger $\delta$, affect the tightness of the enforcement constraint and, via the - Euler equation, the interest rate spread? At $\delta = 1$ the LE constraint - becomes $v(n_j') \geq v_{\text{aut}}^{1}(Y_j)$; at $\delta \to 0$ it is - weak. + Euler equation, the interest rate spread? At $\delta = 1$ default carries no + output penalty, so the enforcement constraint is tightest; as + $\delta \to 0$ the penalty is harsh and the constraint rarely binds. ```{exercise-end} ``` @@ -2270,12 +2289,6 @@ fraction of output retained after default. ``` ```{code-cell} ipython3 ---- -mystnb: - figure: - caption: autarky value and enforcement thresholds - name: fig-tsy-default-penalty ---- fig, ax = plt.subplots() def default_values_for_delta(δ_val, v_aut_arr, β_val=None): @@ -2314,6 +2327,7 @@ for δ_val, color in [(0.50, 'C0'), (0.795, 'C1'), (0.95, 'C2')]: ax.set_xlabel('net worth $n$') ax.set_ylabel(r'$v_{\rm aut}(n)$') +ax.set_title('autarky value and enforcement thresholds') ax.legend(fontsize=9) plt.tight_layout() plt.show() @@ -2321,7 +2335,7 @@ plt.show() A larger $\delta$ means a milder default penalty. -It raises the enforcement thresholds, tightens the participation constraints, +It raises the enforcement thresholds, tightens the enforcement constraints, and reduces the scope for state-contingent repayment. In the full model, this can make limited enforcement bind before the @@ -2330,8 +2344,8 @@ moral-hazard constraint does. Near the borrowing limit, limited enforcement can already force enough continuation-value dispersion to reduce the incentive multiplier. -At $\delta \to 0$ the enforcement constraint is -weak and the model approaches pure moral hazard. +As $\delta \to 0$ the enforcement constraint rarely binds and the model +approaches pure moral hazard. ```{solution-end} ``` @@ -2360,12 +2374,6 @@ front-loading incentive that the lender can exploit. ``` ```{code-cell} ipython3 ---- -mystnb: - figure: - caption: continuation net worth across discount factors - name: fig-tsy-discount-wedge ---- fig, ax = plt.subplots() for β_val, ls, color in [ @@ -2385,6 +2393,7 @@ for β_val, ls, color in [ ax.plot(n_grid, n_grid, lw=1, ls=':', color='k', label='45° line') ax.set_xlabel('net worth $n$') ax.set_ylabel("$E[n']$") +ax.set_title('continuation net worth across discount factors') ax.legend() plt.tight_layout() plt.show() @@ -2393,12 +2402,13 @@ plt.show() The larger the discount wedge $\beta_c - \beta$, the faster net worth drifts toward the borrowing limit. -When $\beta = \beta_c$ moral hazard alone drives -immiseration, while impatience accelerates it further. +When $\beta = \beta_c$, moral hazard alone drives the downward drift; a +positive wedge adds a front-loading motive that accelerates it. -A small wedge, as calibrated by Tsyrennikov, is significant: it is equivalent -to increasing the borrower's discount rate by 2% per annum, even though the -assumed difference in quarterly rates is only 0.010. +Tsyrennikov notes that even small differences in discounting significantly +speed up convergence to the stationary distribution of net worth, which is why +the calibration keeps the wedge small ($\beta = 0.980$ against +$\beta_c = 0.990$). ```{solution-end} ``` From 7613ca5039b7553e967c5378895653d9ad18deed Mon Sep 17 00:00:00 2001 From: HumphreyYang Date: Thu, 11 Jun 2026 19:29:50 +1000 Subject: [PATCH 20/25] updates --- lectures/tsyrennikov_2013.md | 18 +++++++++++++++--- 1 file changed, 15 insertions(+), 3 deletions(-) diff --git a/lectures/tsyrennikov_2013.md b/lectures/tsyrennikov_2013.md index da649997..3254b816 100644 --- a/lectures/tsyrennikov_2013.md +++ b/lectures/tsyrennikov_2013.md @@ -1900,13 +1900,25 @@ mystnb: caption: policy functions in the MH, MH+LE and LE economies name: fig-tsy-policy-functions --- -fig, axes = plt.subplots(3, 2, figsize=(12, 10), sharex=True) +fig, axes = plt.subplots(3, 2, figsize=(10, 15), sharex=True) ax = axes.ravel() -for a in ax: +paper_ylims = [(0.0, 0.6), (0.4, 1.1), + (0.05, 0.5), (0.0, 0.6), + (-0.15, 0.2), (0.0, 1.1)] +paper_yticks = [np.arange(0.0, 0.61, 0.1), np.arange(0.4, 1.01, 0.1), + np.arange(0.05, 0.51, 0.05), np.arange(0.0, 0.61, 0.1), + np.arange(-0.15, 0.21, 0.05), np.arange(0.0, 1.01, 0.2)] +paper_xticks = np.arange(0.4, 1.01, 0.1) + +for a, ylim, yticks in zip(ax, paper_ylims, paper_yticks): + a.set_box_aspect(1) a.axvline(n_low_mh, color='0.25', lw=1, ls='--') a.axvline(n_low_le, color='0.55', lw=1, ls=':') - a.set_xlim(0.38, 1.02) + a.set_xlim(0.4, 1.0) + a.set_xticks(paper_xticks) + a.set_ylim(*ylim) + a.set_yticks(yticks) ax[0].plot(plot_grid, policy_curve(policies['MH'], 'λ'), lw=2, label='MH') ax[0].plot(plot_grid, policy_curve(policies['MH+LE'], 'λ'), From fbf04f85fc5d812d902f53656ed247bf6aa05d28 Mon Sep 17 00:00:00 2001 From: HumphreyYang Date: Thu, 11 Jun 2026 21:26:33 +1000 Subject: [PATCH 21/25] updates --- lectures/tsyrennikov_2013.md | 203 ++++++----------------------------- 1 file changed, 33 insertions(+), 170 deletions(-) diff --git a/lectures/tsyrennikov_2013.md b/lectures/tsyrennikov_2013.md index 3254b816..6d018d82 100644 --- a/lectures/tsyrennikov_2013.md +++ b/lectures/tsyrennikov_2013.md @@ -205,9 +205,6 @@ under lower investment, with diminishing returns. This is the same mixture technology, and uses the same labeling, as in {doc}`atkeson_1991`: the weight $\lambda(I)$ multiplies the favorable distribution $g_0$, so more investment makes high output more likely. - -{cite:t}`Tsyrennikov2013` swaps the labels: there the weight $\lambda(I)$ -multiplies $g_1$, which is the favorable distribution in the paper's notation. ``` Tsyrennikov restricts to two output states, so the favorable distribution puts @@ -733,17 +730,12 @@ This makes the near-zero risk-sharing index under moral hazard representable even though the fixed-point step uses a coarse mesh. To reach a fixed point quickly, all three economies are solved by -**Howard policy iteration** (also called modified policy iteration). +**Howard policy iteration**. Each outer iteration takes one greedy Bellman step, which re-optimizes the contract, and then holds that contract fixed while iterating the value a fixed number of times. -Because the Bellman update is linear in the continuation values once the -contract is fixed, these evaluation sweeps are cheap, and the method converges -in a few dozen outer steps rather than the many hundreds that plain value -function iteration would need at this discount factor. - ### Parameters In addition to what's in Anaconda, this lecture will need the following library: @@ -824,10 +816,6 @@ Next we define the model primitives. The probability of high output is $\lambda(I) = \min(I^\nu, 1)$, period utility `u` is CRRA, and `u_prime` is its derivative $u'(c) = c^{-\gamma}$. -We write each with `jnp`, so the same function runs inside the JIT-compiled -Bellman updates and on the plain arrays used in the plotting and simulation -code. - ```{code-cell} ipython3 def λ(I): """Probability of high output, λ(I) = min{I^ν, 1}.""" @@ -894,7 +882,7 @@ $v(Y_2)$. ```{code-cell} ipython3 @jax.jit -def autarky_step_jax(v, β_val): +def autarky_step(v, β_val): """One vectorized Bellman step for the autarky problem.""" # Continuation values: next-period net worth is the realized output Ev1 = jnp.interp(Y1, n_grid_j, v) @@ -918,7 +906,7 @@ def autarky_policy(v_arr, β_val=None): """Return the autarky value update and investment policy on n_grid.""" if β_val is None: β_val = β - v_new, I_pol = autarky_step_jax(jnp.asarray(v_arr), β_val) + v_new, I_pol = autarky_step(jnp.asarray(v_arr), β_val) return np.asarray(v_new), np.asarray(I_pol) ``` @@ -932,7 +920,7 @@ def autarky_vfi(β_val=None, tol=1e-8, max_iter=3000, verbose=False): v = jnp.zeros(n_grid_size) for it in range(max_iter): - v_new, _ = autarky_step_jax(v, β_val) + v_new, _ = autarky_step(v, β_val) diff = float(jnp.max(jnp.abs(v_new - v))) v = v_new if diff < tol: @@ -1027,7 +1015,7 @@ endogenous borrowing limits instead. For LE, investment is observable, so the planner chooses it directly. Each Bellman step returns both the improved value and the greedy contract, and a -shared routine `policy_eval_jax` then performs the Howard policy-evaluation +shared routine `policy_eval` then performs the Howard policy-evaluation sweeps that hold that contract fixed. In the two limited-enforcement economies, the endogenous borrowing limits are @@ -1047,7 +1035,7 @@ def contract_initial_upper(β_val, loan_upper): @jax.jit -def mh_bellman_step_jax(v, v_aut_arr, I_aut_arr, nbar1, nbar2, +def mh_bellman_step(v, v_aut_arr, I_aut_arr, nbar1, nbar2, loan_cap, β_val, β_c_val): """One Bellman step for MH, with optional LE bounds and loan cap.""" v1 = jnp.interp(n1p_flat_j, n_grid_j, v) @@ -1147,7 +1135,7 @@ def mh_bellman_step_jax(v, v_aut_arr, I_aut_arr, nbar1, nbar2, @jax.jit -def le_bellman_step_jax(v, v_aut_arr, I_aut_arr, nbar1, nbar2, +def le_bellman_step(v, v_aut_arr, I_aut_arr, nbar1, nbar2, β_val, β_c_val): """One Bellman step for the limited-enforcement-only economy.""" v1 = jnp.interp(n1p_flat_j, n_grid_j, v) @@ -1224,14 +1212,9 @@ def le_bellman_step_jax(v, v_aut_arr, I_aut_arr, nbar1, nbar2, @jax.jit -def policy_eval_jax(v, v_aut_arr, pol_n1p, pol_n2p, pol_I, pol_b, +def policy_eval(v, v_aut_arr, pol_n1p, pol_n2p, pol_I, pol_b, use_fallback, β_val): """Howard policy evaluation: iterate the value under a fixed policy. - - Once the greedy contract (b, n_1', n_2', I) is fixed, the Bellman update - is linear in the continuation values, so the period return R = u(c) and - the high-output weight λ(I) can be precomputed and the cheap evaluation - sweep applied many times before re-optimizing. """ R = u(n_grid_j + pol_b - θ * pol_I) l = λ(pol_I) @@ -1274,11 +1257,11 @@ def mh_vfi(v_aut, β_val=None, β_c_val=None, tol=contract_tol, for it in range(max_iter): # Policy improvement: one greedy Bellman step. (v_greedy, pol_n1p, pol_n2p, pol_I, pol_b, - use_fb) = mh_bellman_step_jax( + use_fb) = mh_bellman_step( v, v_aut_j, I_aut_j, nbars[0], nbars[1], loan_cap, β_val, β_c_val) # Policy evaluation: iterate the value under the fixed policy. - v_new = policy_eval_jax(v_greedy, v_aut_j, pol_n1p, pol_n2p, + v_new = policy_eval(v_greedy, v_aut_j, pol_n1p, pol_n2p, pol_I, pol_b, use_fb, β_val) limit_diff = 0.0 if limited_enforcement: @@ -1305,7 +1288,7 @@ def mh_vfi(v_aut, β_val=None, β_c_val=None, tol=contract_tol, f"diff = {diff:.3e}, nbars = {np.round(nbars, 4)}" ) - _, pol_n1p, pol_n2p, pol_I, pol_b, _ = mh_bellman_step_jax( + _, pol_n1p, pol_n2p, pol_I, pol_b, _ = mh_bellman_step( jnp.asarray(v), v_aut_j, I_aut_j, nbars[0], nbars[1], loan_cap, β_val, β_c_val) @@ -1333,12 +1316,13 @@ def le_vfi(v_aut, β_val=None, β_c_val=None, tol=contract_tol, v = jnp.asarray(contract_initial_upper(β_val, Y2 - n_lo)) for it in range(max_iter): - # Policy improvement: one greedy Bellman step. + # Policy improvement: one greedy Bellman step (v_greedy, pol_n1p, pol_n2p, pol_I, pol_b, - use_fb) = le_bellman_step_jax( + use_fb) = le_bellman_step( v, v_aut_j, I_aut_j, nbars[0], nbars[1], β_val, β_c_val) - # Policy evaluation: iterate the value under the fixed policy. - v_new = policy_eval_jax(v_greedy, v_aut_j, pol_n1p, pol_n2p, + + # Policy evaluation: iterate the value under the fixed policy + v_new = policy_eval(v_greedy, v_aut_j, pol_n1p, pol_n2p, pol_I, pol_b, use_fb, β_val) nbars_new = update_nbars(np.asarray(v_new), nbars, v_default) limit_diff = np.max(np.abs(nbars_new - nbars)) @@ -1363,7 +1347,7 @@ def le_vfi(v_aut, β_val=None, β_c_val=None, tol=contract_tol, f"diff = {diff:.3e}, nbars = {np.round(nbars, 4)}" ) - _, pol_n1p, pol_n2p, pol_I, pol_b, _ = le_bellman_step_jax( + _, pol_n1p, pol_n2p, pol_I, pol_b, _ = le_bellman_step( jnp.asarray(v), v_aut_j, I_aut_j, nbars[0], nbars[1], β_val, β_c_val) @@ -1781,46 +1765,12 @@ print(f"Approximate low-state limit, MH+LE: {n_low_mhle:.4f}") print(f"Approximate low-state limit, LE: {n_low_le:.4f}") low_limits = {'MH': n_low_mh, 'MH+LE': n_low_mhle, 'LE': n_low_le} - - -def print_policy_diagnostics(policies): - """Print compact diagnostics for the computed policy functions.""" - print("\nPolicy diagnostics:") - for name, policy in policies.items(): - active = policy['λ'] > 0.01 - rsi_active = policy['RSI'][active] - support_lo = max(0.38, low_limits[name] - 1e-8) - support = ((n_grid >= support_lo) & (n_grid <= 1.02) - & (policy['λ'] > 0.01) & (policy['λ'] < 0.99)) - rsi_support = policy['RSI'][support] - n1_floor = np.sum(np.isclose(policy['n1p'], n_lo)) - n2_floor = np.sum(np.isclose(policy['n2p'], n_lo)) - nbars = policy['nbars'] - nbars_text = "none" if nbars is None else np.array2string( - np.round(nbars, 4)) - support_max_abs = (np.nan if rsi_support.size == 0 - else np.nanmax(np.abs(rsi_support))) - print( - f"{name:5s}: " - f"λ=[{np.nanmin(policy['λ']):.3f}, {np.nanmax(policy['λ']):.3f}], " - f"b=[{np.nanmin(policy['b']):.3f}, {np.nanmax(policy['b']):.3f}], " - f"RSI_active_mean={np.nanmean(rsi_active):.4f}, " - f"RSI_active_max={np.nanmax(rsi_active):.4f}, " - f"RSI_support_max_abs={support_max_abs:.4f}, " - f"n1'=[{np.nanmin(policy['n1p']):.3f}, " - f"{np.nanmax(policy['n1p']):.3f}], " - f"n2'=[{np.nanmin(policy['n2p']):.3f}, " - f"{np.nanmax(policy['n2p']):.3f}], " - f"floor_hits=({n1_floor}, {n2_floor}), " - f"nbars={nbars_text}" - ) - - -print_policy_diagnostics(policies) ``` ### Value functions and insurance +Now let's plot the value functions and risk-sharing indices. + ```{code-cell} ipython3 --- mystnb: @@ -1878,7 +1828,8 @@ schedule $\{d_1(n), d_2(n)\}$ is nearly state non-contingent on the relevant range of net worth, while under limited enforcement it is much more state contingent. -Small irregularities near the grid endpoints are numerical artifacts. +Small irregularities near the low net worth and high net worth are mainly +numerical effects of the finite grid and the local optimization. This is the paper's central result about the optimal contract: under moral hazard nearly all the risk is assumed by the risk-averse borrower, and @@ -2040,7 +1991,7 @@ must reduce the borrower's net worth, which can increase capital outflows. Panel F is the risk-sharing index. -In the paper, this panel is the visual counterpart to the state +This panel is the visual counterpart to the state non-contingency result: RSI is close to zero in the MH economy and much larger in the LE economy. @@ -2155,13 +2106,6 @@ As the borrower's net worth deteriorates, investment falls and $\lambda(I)$ falls, making the low-output path more likely than it would be in the frictionless or LE economies. -In the paper's calibration, the crisis path is 7.55 times more likely in the -MH economy than in a frictionless economy, while in the LE economy it is about -as likely as in the frictionless economy (a factor of 1.01). - -The paper reports the MH+LE economy as visually close to the MH economy, so the -main comparison is between MH and LE. - ### MH versus limited enforcement A crucial result of {cite:t}`Tsyrennikov2013` is that limited enforcement @@ -2185,95 +2129,8 @@ hazard near the borrowing limits: the borrowing limits already spread continuation values enough to provide incentives, so the incentive multiplier collapses to zero. -## Quantitative comparison - -The paper's simulation results show why the moral-hazard mechanism matters. - -The table below condenses the paper's table of simulated model moments. - -Because the MH+LE economy is very close to the MH economy in the paper's -simulations, the table reports only the MH column as the moral-hazard -benchmark. - -| moment | data | MH, i.i.d. | LE, i.i.d. | MH, persistent | LE, persistent | -| --- | ---: | ---: | ---: | ---: | ---: | -| $E(r)$ | 8.18 | 3.84 | 4.27 | 3.26 | 4.01 | -| $\sigma(r)$ | 4.73 | 5.20 | 2.51 | 4.73 | 1.96 | -| $\sigma(c)/\sigma(y)$ | 1.11 | 0.72 | 0.24 | 0.84 | 0.40 | -| $\rho(c,y)$ | 0.97 | 0.82 | 0.62 | 0.96 | 0.67 | -| $\rho(r,y)$ | -0.58 | -0.68 | -0.81 | -0.74 | -0.42 | -| $\rho(tb,y)$ | -0.81 | 0.69 | 0.98 | 0.62 | 0.92 | -| $\rho(y)$ | 0.94 | 0.28 | 0.07 | 0.75 | 0.63 | - -Moral hazard raises consumption-output comovement and generates volatile, -countercyclical spreads. - -It also creates endogenous output persistence even when the exogenous shocks -are i.i.d. - -The persistent-shocks extension strengthens this result. - -The paper replaces the i.i.d. transition law with - -$$ -\Pr(Y_2 \mid I, Y_1) = \lambda(I)(1-p), -\qquad -\Pr(Y_2 \mid I, Y_2) = p + \lambda(I)(1-p), -$$ - -with $p = 0.50$, recalibrating $\theta = 0.102$ and $\nu = 0.90$. - -This persistent component improves the fit for consumption and output. - -The current-account problem remains. - -In the data, the trade balance is strongly countercyclical, while the model's -trade balance is still procyclical in the reported simulations. - -Because invested goods last only one period, the model behaves much like an -exchange economy, and Tsyrennikov shows that no exchange-economy model can -match the observed consumption and current-account moments at the same time. - -He argues that stochastic growth or multiperiod capital would likely be needed -to address this shortcoming. - -## Empirical test - -{cite:t}`Tsyrennikov2013` proposes a test to distinguish moral hazard from -limited enforcement. - -After a low past output realization ($y_{t-1} = Y_1$), the MH contract lowers -net worth sharply, leaving less room for consumption smoothing. - -The prediction is - -$$ -\text{MH economy}: \quad - \rho(c_t, y_t \mid y_{t-1} = Y_1) \;>\; \rho(c_t, y_t \mid y_{t-1} = Y_2), -$$ - -while the LE economy implies the opposite ordering, because insurance is -better after low realizations. - -Using Argentine quarterly data (1993--2005), the observed -correlations are 0.98 (after low output) vs. 0.91 (after high output) --- -*consistent with moral hazard*. - -The paper reports the following comparison for the persistent-shocks economy: - -| statistic | data | LE | MH | -| --- | ---: | ---: | ---: | -| $\rho(c_t,y_t \mid y_{t-1}=Y_1)$ | 0.98 | 0.61 | 0.97 | -| $\rho(c_t,y_t \mid y_{t-1}=Y_2)$ | 0.91 | 0.99 | 0.96 | - -The limited-enforcement model predicts stronger consumption-output correlation -after high past output. - -The data show the opposite ordering, which is closer to the moral-hazard -prediction. - -This test relies on the maintained assumption that state-contingent contracting -is feasible. +These are visible in the figures we showed above: the MH and MH+LE policies are very close to each other, while the LE policy is quite different, while +LE is closer to the frictionless benchmark. ## Exercises @@ -2281,7 +2138,7 @@ is feasible. :label: tsyrennikov_2013_ex1 ``` -**Effect of the default penalty.** The parameter $\delta \in (0,1)$ is the +*Effect of the default penalty.* The parameter $\delta \in (0,1)$ is the fraction of output retained after default. 1. Using $v_{\text{aut}}$, compute @@ -2300,6 +2157,8 @@ fraction of output retained after default. :class: dropdown ``` +Here is one solution: + ```{code-cell} ipython3 fig, ax = plt.subplots() @@ -2366,7 +2225,7 @@ approaches pure moral hazard. :label: tsyrennikov_2013_ex2 ``` -**Discounting wedge and impatience.** +*Discounting wedge and impatience.* 1. Re-solve the MH model for $\beta = \beta_c = 0.990$ (equal discounting --- no impatience wedge) and for $\beta = 0.950$ (larger wedge). @@ -2385,6 +2244,8 @@ front-loading incentive that the lender can exploit. :class: dropdown ``` +Here is one solution: + ```{code-cell} ipython3 fig, ax = plt.subplots() @@ -2429,7 +2290,7 @@ $\beta_c = 0.990$). :label: tsyrennikov_2013_ex3 ``` -**The envelope condition.** In deriving the Euler equation +*The envelope condition.* In deriving the Euler equation {eq}`eq:tsyrennikov_euler` we used the envelope result $$ @@ -2449,6 +2310,8 @@ which terms of $\mathcal{L}$ actually depend on $n$. :class: dropdown ``` +Here is one solution: + The state $n$ enters the Lagrangian {eq}`eq:tsyrennikov_lagrangian` only through current consumption $c = n + b - \theta I$, and only in two terms: the period utility $u(n+b-\theta I)$ and the incentive term $-\mu\theta\,u'(n+b-\theta I)$. From 25f2cb573408470b6c848ebcd44b4fb77f20fadf Mon Sep 17 00:00:00 2001 From: HumphreyYang Date: Thu, 11 Jun 2026 22:51:21 +1000 Subject: [PATCH 22/25] updates --- .../subjective_beliefs_business_cycles.md | 916 +++++++++++++++--- 1 file changed, 758 insertions(+), 158 deletions(-) diff --git a/lectures/subjective_beliefs_business_cycles.md b/lectures/subjective_beliefs_business_cycles.md index 771143b0..3d0d1537 100644 --- a/lectures/subjective_beliefs_business_cycles.md +++ b/lectures/subjective_beliefs_business_cycles.md @@ -37,10 +37,10 @@ These biases, called *belief wedges*, are: We interpret this evidence using **robust preferences** ({cite:t}`HansenSargent2001`; {cite:t}`HansenSargent2008`). -Robust preferences model pessimism and optimism: -agents act as if they overweight states that deliver low continuation values -(pessimism) and underweight those that deliver high continuation values -(optimism). +Robust preferences provide model-consistent notions of pessimism and optimism: +a pessimistic agent acts as if states that deliver low continuation values are +more likely than they really are, while an optimistic agent overweights states +that deliver high continuation values. When calibrated to the Michigan Survey of Consumers (1982Q1-2019Q4), this mechanism yields a time-varying *belief shock* @@ -51,7 +51,8 @@ policy shocks generate far too little unemployment volatility. In this lecture, we will cover: * How to define and measure belief wedges from household survey data. -* How robust preferences generate time-varying subjective beliefs. +* Why optimal pessimism is a mean shift of the shock distribution, with + size equal to $\theta_t$ times the shock's exposure to continuation values. * How belief distortions propagate through a linearized DSGE model. * Why a calibrated belief shock helps resolve the unemployment volatility puzzle. @@ -71,8 +72,8 @@ Some notation and units will be used throughout: * Inflation is reported at an annualized rate when explicitly marked "ann."; otherwise it is a quarterly rate. * $u_t$, $\pi_t$, and $y_t$ denote unemployment, inflation, and the output gap. -* $a_t$ is total factor productivity (TFP), and $r_t$ is a monetary-policy - disturbance. +* $a_t$ is total factor productivity (TFP), and $w_t^r$ is a monetary-policy + shock. * $\theta_t$ is the belief-shock or pessimism state: larger $\theta_t$ means agents put more subjective probability on low-continuation-value states. @@ -110,24 +111,24 @@ data-generating forecast. For unemployment and inflation, this sign convention corresponds to an upward forecast bias. -The empirical objects below are mostly one-year-ahead, or four-quarter, -wedges: +The Michigan Survey asks about outcomes one year ahead, so the empirical +objects below are four-quarter wedges $$ \Delta_t^{(4)}(z) -= \tilde E_t[z_{t+4} - z_t] - E_t[z_{t+4} - z_t]. += \tilde E_t[z_{t+4}] - E_t[z_{t+4}]. $$ -We introduce the one-period wedge because it is the cleanest way to explain the -theory; the appendix below shows the multi-period version. +We work mostly with the one-period wedge because it is the cleanest way to +explain the theory; the appendix derives the multi-period version. In the data, $\tilde{E}_t[\cdot]$ is measured from the Michigan Survey of Consumers. The benchmark $E_t[\cdot]$ is computed from a quarterly VAR, with Survey of -Professional Forecasters (SPF) forecasts used as an important robustness check. +Professional Forecasters (SPF) forecasts used as a robustness check. In the structural model, the same object is interpreted as the difference between subjective and data-generating expectations. @@ -155,27 +156,49 @@ Using data from 1982Q1 to 2019Q4, {cite:t}`bhandari2025survey` document: | Standard deviation | 0.57 pp | 0.97 pp | | Correlation with output gap | −0.49 | −0.30 | -Both wedges are **positive on average** (households are pessimistic) and -**countercyclical** (pessimism rises in recessions). +Both wedges are **positive on average**: households expect higher unemployment +and higher inflation than the rational forecast. -Moreover, the first -principal component of the joint wedge series explains **78.6%** of its -variation --- a striking one-factor structure. +Both wedges are **countercyclical**: they rise during every NBER recession in +the sample. + +These facts are robust: they survive replacing the VAR forecast with the SPF +forecast, alternative VAR specifications, and extending the sample back to +1960. + +A positive unemployment wedge is naturally read as pessimism, since +unemployment is high in bad times. + +The positive inflation wedge carries the same interpretation because +households regard high inflation as a feature of bad times: in the Bank of +England's Inflation Attitudes Survey, 50% to 80% of households consistently +answer that faster inflation would weaken the economy. + +The two wedges are positively correlated, with a correlation of 0.34, and the +first principal component of the wedge series explains **78.6%** of their +joint variation. + +(The principal component is computed from the raw wedges, so it reflects both +their correlation and the larger variance of the inflation wedge.) The same one-factor pattern appears in the cross section. -In the Michigan Survey, households with high inflation forecasts are also more -likely to expect unemployment to rise and to report worse economic conditions. +Michigan Survey households with high inflation forecasts are more likely to +expect unemployment to rise, to expect worse aggregate and personal financial +conditions, and to assign higher probability to losing their own jobs. -Similar patterns appear across demographic groups and in the FRBNY Survey of -Consumer Expectations (SCE). +Average wedges also line up positively across demographic groups, and although +more educated and higher-income households make smaller forecast errors, every +group overpredicts both variables. + +The FRBNY Survey of Consumer Expectations (SCE) shows the same patterns. This evidence supports the interpretation that the wedges reflect a common pessimism/optimism component rather than two unrelated forecast mistakes. -The following code simulates a stylized wedge process with the same sample -length, mean wedges, standard deviations, and first-principal-component share -reported above. +The following code simulates a stylized wedge process that matches the sample +length, the mean and standard deviation of each wedge, and the 0.34 +correlation between the wedges. The point is not to recreate the raw Michigan series. @@ -195,7 +218,7 @@ c_π = 1.22 / μ_θ # Empirical wedge moments used to discipline the simulation. mean_u, mean_π = 0.52, 1.22 std_u, std_π = 0.57, 0.97 -pc1_share_target = 0.786 +corr_wedges = 0.34 T = 152 # 38 years * 4 quarters @@ -221,10 +244,10 @@ def orthogonal_noise(rng, *basis, T=T): return standardize(e) -# For two standardized variables, PC1 share = (1 + corr) / 2. -corr_target = 2 * pc1_share_target - 1 -common_weight = np.sqrt(corr_target) -noise_weight = np.sqrt(1 - corr_target) +# Loading each wedge on the common factor with weight √corr makes the +# sample correlation between the two wedges equal corr_wedges exactly. +common_weight = np.sqrt(corr_wedges) +noise_weight = np.sqrt(1 - corr_wedges) common_factor = standardize(θ) noise_u = orthogonal_noise(rng, common_factor) @@ -235,8 +258,9 @@ wedge_u = mean_u + std_u * (common_weight * common_factor wedge_π = mean_π + std_π * (common_weight * common_factor + noise_weight * noise_π) -wedge_std = np.column_stack([standardize(wedge_u), standardize(wedge_π)]) -eigvals = np.linalg.eigvalsh(np.cov(wedge_std, rowvar=False)) +# Principal components of the raw (non-standardized) wedges, as in the paper. +wedges = np.column_stack([wedge_u, wedge_π]) +eigvals = np.linalg.eigvalsh(np.cov(wedges, rowvar=False)) pc1_share = eigvals[-1] / eigvals.sum() # Quarterly dates, 1982Q1-2019Q4 @@ -306,16 +330,19 @@ Both wedges have a common cyclical component driven by $\theta_t$, but they also contain residual components that stand in for survey noise and other idiosyncratic variation. -The scatter plot makes this one-factor structure even clearer. +The scatter plot makes the one-factor structure visible. Each point is one quarter, with the horizontal coordinate equal to the unemployment wedge and the vertical coordinate equal to the inflation wedge. -The points form an upward-sloping cloud rather than a line because the first -principal component accounts for most, but not all, of the variation. +The points form an upward-sloping cloud rather than a line: the common factor +drives both wedges, while survey noise keeps them from being collinear. + +With the means, standard deviations, and correlation matched, the first +principal component of the simulated wedges accounts for about 78.5% of their +joint variation, essentially the 78.6% share in the data. -This is the one-factor structure that motivates the -theoretical framework. +This is the one-factor structure that motivates the theoretical framework. ## A model of pessimism @@ -343,16 +370,48 @@ Here $m_{t+1}$ is a **likelihood ratio** (Radon–Nikodym derivative) that distorts the reference measure, and the last term is an entropy penalty that keeps the distortion from being too extreme. +The state vector $x_t \in \mathbb{R}^n$ follows a Markov law of motion + +$$ + +x_{t+1} = \psi(x_t, w_{t+1}), + +$$ + +where $w_{t+1} \sim N(0, I_k)$ is i.i.d. under the data-generating measure, +and the penalty parameter is linear in the state: + +$$ + +\theta_t = \bar\theta x_t, + +$$ + +for a $1 \times n$ vector of parameters $\bar\theta$. + The scalar $\theta_t$ controls the direction and strength of the belief tilt. The minimization problem above corresponds to $\theta_t > 0$: larger $\theta_t$ means more pessimism. -Optimism corresponds to $\theta_t < 0$, in which case the inner problem -becomes a maximization that tilts probability toward -high-continuation-value states. +Because $\theta_t$ is linear in the state, it can turn negative, in which case +the inner problem becomes a maximization that tilts probability toward +high-continuation-value states --- optimism. -The inner minimization has a closed-form solution: +The inner minimization has a closed-form solution. + +Minimizing $E_t[m_{t+1} V_{t+1}] + \frac{1}{\theta_t} E_t[m_{t+1} \log m_{t+1}]$ +state by state subject to $E_t[m_{t+1}] = 1$ gives the first-order condition + +$$ + +V_{t+1} + \frac{1}{\theta_t}\left(\log m_{t+1} + 1\right) + \lambda_t = 0, + +$$ + +where $\lambda_t$ is the multiplier on the constraint, so +$m_{t+1} \propto \exp(-\theta_t V_{t+1})$ and the constraint pins down the +normalization: $$ @@ -365,6 +424,37 @@ Since $m_{t+1}^*$ assigns higher weight to states where $V_{t+1}$ is low (bad outcomes), pessimistic agents effectively over-weight recessions in their probability assessments. +Substituting $m_{t+1}^*$ back into the recursion gives the equivalent +**risk-sensitive** form + +$$ + +V_t = u(x_t) - \frac{\beta}{\theta_t} +\log E_t\!\left[\exp(-\theta_t V_{t+1})\right], + +$$ + +which replaces the expected continuation value with a soft minimum: as +$\theta_t \to 0$ it reduces to $u(x_t) + \beta E_t[V_{t+1}]$, and as +$\theta_t \to \infty$ it approaches the worst case over states. + +In the robust-control literature, this distortion expresses fear of model +misspecification. + +{cite:t}`bhandari2025survey` instead take the recursion directly as a model of +pessimism and optimism, and let survey data discipline the process for +$\theta_t$. + +Survey data also resolve an identification problem. + +With log period utility, these preferences are mathematically equivalent to +Epstein--Zin preferences with time-varying risk aversion +$\gamma_t = \theta_t + 1$, so asset prices and macroeconomic aggregates alone +cannot distinguish time-varying pessimism from time-varying risk premia. + +Survey forecasts can: fluctuations in rational risk premia leave forecasts +unbiased, whereas subjective beliefs show up directly as belief wedges. + ### Connection to the belief wedge The belief wedge is the expected deviation between subjective and objective @@ -381,6 +471,10 @@ $$ $$ +The last equality holds because $E_t[m_{t+1}^*] = 1$, so +$E_t[m^*_{t+1} z_{t+1}] - E_t[z_{t+1}] += E_t[m^*_{t+1} z_{t+1}] - E_t[m^*_{t+1}]\,E_t[z_{t+1}]$. + So the belief wedge equals the covariance between the distorted likelihood ratio and the variable of interest. @@ -402,42 +496,87 @@ x_{t+1} = \rho_x x_t + \sigma_x w_{t+1}, \qquad w_{t+1} \sim N(0,1). $$ -With log utility, the continuation value is linear: $V_t = V_x x_t + V_q$. +With log utility, $u(x_t) = (1 - \beta)x_t$, and we guess that the +continuation value is linear: $V_t = V_x x_t + V_q$. +The Gaussian structure makes everything explicit. -Under the objective measure, $x_{t+1}$ is normal with mean $\rho_x x_t$ and -standard deviation $\sigma_x$. +Since $-\theta_t V_{t+1} = -\theta_t(V_x \rho_x x_t + V_q) - \theta_t V_x +\sigma_x w_{t+1}$ is linear in the standard normal shock, the moment +generating function $E[\exp(a w)] = \exp(a^2/2)$ evaluates the +risk-sensitive recursion in closed form: + +$$ -The distorted measure $m_{t+1}^*$ shifts the mean of $w_{t+1}$ to +-\frac{1}{\theta_t} \log E_t\!\left[\exp(-\theta_t V_{t+1})\right] += V_x \rho_x x_t + V_q - \frac{\theta_t}{2}\, V_x^2 \sigma_x^2. $$ -\nu_t \;=\; -\theta_t (V_x \sigma_x). +The expected continuation value is penalized by half the pessimism parameter +times the conditional variance of continuation values: pessimism acts like an +endogenous discount on risky continuation utilities. + +The same linearity pins down the optimal distortion. + +Writing $a_t = -\theta_t V_x \sigma_x$, the likelihood ratio becomes + +$$ + +m_{t+1}^* = \frac{\exp(a_t w_{t+1})}{E_t[\exp(a_t w_{t+1})]} += \exp\!\left(a_t w_{t+1} - \tfrac{1}{2} a_t^2\right), $$ -Hence, under the subjective measure, the innovation distribution becomes +which is exactly the density of $N(a_t, 1)$ divided by the density of +$N(0, 1)$. + +Pessimism is therefore a **mean shift**: under the subjective measure, $$ -w_{t+1} \;\sim\; N\!\left(\nu_t,\; 1\right). +w_{t+1} \;\sim\; N(\nu_t, 1), +\qquad +\nu_t = -\theta_t V_x \sigma_x. $$ -The belief wedge for the state variable $x$ is +The drift $\nu_t$ is the pessimism parameter times the exposure of the +continuation value to the shock --- the agent slants beliefs exactly in the +direction that hurts most, by an amount the entropy penalty limits. + +The belief wedge for the state follows immediately: $$ -\Delta_t^{(1)}(x) \;=\; \sigma_x \nu_t \;=\; -\theta_t V_x \sigma_x^2. +\Delta_t^{(1)}(x) = \tilde E_t[x_{t+1}] - E_t[x_{t+1}] += \sigma_x \nu_t = -\theta_t V_x \sigma_x^2. $$ -When $V_x > 0$ (good consumption state is good) and $\theta_t > 0$ +When $V_x > 0$ (high consumption states are good) and $\theta_t > 0$ (pessimism), the wedge is negative --- the agent *underestimates* -consumption growth. +future consumption. -For unemployment (enter with a negative sign in the -value function), the same pessimism generates a **positive** wedge. +For a variable that enters the value function with a negative sign, such as +unemployment, the same pessimism generates a **positive** wedge. + +Finally, substituting the closed-form recursion back into +$V_t = u(x_t) - \frac{\beta}{\theta_t}\log E_t[\exp(-\theta_t V_{t+1})]$ +and matching coefficients on $x_t$ --- using the scaling of $\theta_t$ +derived in the appendix, under which the penalty term contributes to the +slope with coefficient $\mu_\theta$ --- yields the **Riccati equation** + +$$ + +V_x = u_x + \beta \rho_x V_x - \frac{\beta}{2}\,\mu_\theta\, \sigma_x^2 V_x^2, +\qquad u_x = 1 - \beta. + +$$ + +The quadratic term is the price of pessimism: it lowers the marginal value of +the state relative to the rational-expectations value +$V_x^{RE} = u_x / (1 - \beta\rho_x)$. We now turn this illustration into code, building it up from small pieces. @@ -599,6 +738,100 @@ movement is the normalized subjective drift $\nu_t / \sigma_x$. An agent with $\theta_t > 0$ believes bad shocks are more likely than they actually are. +### Subjective dynamics + +The mean shift changes the law of motion that agents perceive. + +Substituting $w_{t+1} = \nu_t + \tilde w_{t+1}$, where +$\tilde w_{t+1} \sim N(0, 1)$ under the subjective measure, into the dynamics +of $x_t$ gives + +$$ + +x_{t+1} = -\theta_t V_x \sigma_x^2 + \rho_x x_t + \sigma_x \tilde w_{t+1}. + +$$ + +With $\theta_t = \bar\theta(\bar x + x_t)$, collecting terms shows that +subjective beliefs change both the intercept and the slope of the perceived +dynamics: + +$$ + +\tilde\rho_x = \rho_x - \bar\theta\, V_x \sigma_x^2, +\qquad +\tilde\psi_q = -\bar\theta \bar{x}\, V_x \sigma_x^2. + +$$ + +For a good state ($V_x > 0$), pessimism adds a negative drift; for a bad +state such as unemployment ($V_x < 0$), the same formula raises the +subjective persistence --- pessimists believe bad times last longer. + +The code below compares objective and subjective forecast paths of the +consumption state, starting from the steady state, holding the pessimism +level fixed along the forecast path. + +```{code-cell} ipython3 +def forecast_paths(model, θ, x0=0.0, τ_max=20): + """Objective and subjective forecast paths E_t[x_{t+τ}], Ẽ_t[x_{t+τ}].""" + ν = belief_drift(model, θ) # subjective mean of the shock + obj = np.empty(τ_max + 1) + subj = np.empty(τ_max + 1) + obj[0] = subj[0] = x0 + for τ in range(τ_max): + obj[τ+1] = model.ρ_x * obj[τ] + subj[τ+1] = model.ρ_x * subj[τ] + model.σ_x * ν + return obj, subj +``` + +```{code-cell} ipython3 +--- +mystnb: + figure: + caption: objective and subjective forecasts of consumption + name: fig-sbbc-subjective-forecasts +--- +τ_max = 20 +horizons = np.arange(τ_max + 1) + +θ_levels = [model.μ_θ, 2 * model.μ_θ] +labels_f = [f'subjective, θ = θ_bar', f'subjective, θ = 2θ_bar'] +colors_f = ['steelblue', 'firebrick'] + +fig, ax = plt.subplots() +obj, _ = forecast_paths(model, 0.0, τ_max=τ_max) +ax.plot(horizons, obj * 100, color='black', linewidth=2, + label='objective forecast') +for θ, lab, c in zip(θ_levels, labels_f, colors_f): + _, subj = forecast_paths(model, θ, τ_max=τ_max) + ax.plot(horizons, subj * 100, color=c, linewidth=2, + linestyle='--', label=lab) + +ax.axhline(0, color='grey', linewidth=0.7, linestyle=':') +ax.set_xlabel('forecast horizon $\\tau$ (quarters)') +ax.set_ylabel('forecast of $x_{t+\\tau}$ (%)') +ax.legend() +plt.tight_layout() +plt.show() +``` + +Starting at the steady state, the objective forecast of consumption is flat +at zero. + +The subjective forecasts drift persistently downward, and twice as fast when +pessimism is twice as high, because the constant drift +$\sigma_x \nu_t$ accumulates at the persistence $\rho_x$. + +On average the feared bad times never arrive, so subjective forecasts are +systematically wrong --- and that systematic error is exactly the belief +wedge measured in the surveys. + +This figure is the scalar version of a key picture in +{cite:t}`bhandari2025survey`: after a pessimism shock in the structural +model, households expect consumption to fall further and recover far more +slowly than it actually does. + ## Linear approximation with belief distortions ### The perturbation method @@ -616,32 +849,42 @@ w_{t+1} \sim N(0, I_k). $$ -Write the local scalar belief factor as -$\vartheta_t = \bar\theta(\bar{x} + x_{1t})$. +To first order, the belief factor $\theta_t = \bar\theta x_t$ equals +$\bar\theta(\bar{x} + x_{1t})$. Under the optimal belief distortion the shocks are re-centered: $$ -w_{t+1} \;\sim\; N\!\left(- \vartheta_t (V_x \psi_w)',\; I_k\right), +w_{t+1} \;\sim\; N\!\left(- \theta_t (V_x \psi_w)',\; I_k\right), $$ where $V_x$ is the row vector of first derivatives of the continuation value and $\bar{x}$ is the non-stochastic steady state. -This perturbation preserves nontrivial first-order effects of belief -distortions. +In a standard first-order perturbation, belief distortions would vanish: as +shock volatility shrinks, the entropy penalty makes the distortion second +order. + +{cite:t}`bhandari2025survey` avoid this by scaling $\theta_t$ jointly with the +shock volatility, which keeps the subjective model distinct from the +data-generating process in the linear solution; the appendix gives details. The resulting **belief wedge** for any variable $z_t = \bar{z}' x_t$ is $$ \Delta_t^{(1)}(z) -\;=\; -\vartheta_t\, \bar{z}' (\psi_w \psi_w') V_x'. +\;=\; -\theta_t\, \bar{z}' (\psi_w \psi_w') V_x'. $$ +Because the drift moves with $\theta_t$, subjective beliefs change both the +conditional mean and the persistence of the state: adverse states are more +persistent under the subjective measure than under the data-generating +measure. + ### Riccati equation for $V_x$ The key object is $V_x$, which solves @@ -662,11 +905,18 @@ penalty on beliefs and vanishes under rational expectations ($\bar\theta = 0$). An important consequence of the formula for $\Delta_t^{(1)}(z)$ is that the *time variation* in all belief wedges is driven by the **single scalar** belief -factor $\vartheta_t$. +factor $\theta_t \approx \bar\theta(\bar{x} + x_{1t})$. The cross-sectional loadings $-\bar{z}'(\psi_w\psi_w')V_x'$ are fixed by the model's structural parameters. +The loadings are not free parameters: they equal covariances of shocks with +the continuation value, objects that the equilibrium of the model determines. + +The only free parameters describing beliefs are the three governing the +$\theta_t$ process, so every additional surveyed variable adds an +overidentifying restriction on the model. + This theoretical prediction matches the empirical finding that one principal component explains 78.6% of the joint variation in the unemployment and inflation wedges. @@ -725,6 +975,10 @@ inflation wedges against each other. Since both are driven by the same scalar state, the simulated points trace out an almost exact positive relation. +In the data the relation is looser because survey responses contain +measurement error; a hidden-factor model that allows for such error recovers a +belief factor whose path is close to the first principal component. + ## A New Keynesian model with belief distortions ### Model description @@ -733,30 +987,267 @@ Following {cite:t}`bhandari2025survey`, we embed the belief-distortion mechanism in a New Keynesian model with a **search-and-matching** labor market ({cite}`Shimer2005`; {cite}`ChristianoEichenbaumTrabandt2016`). -The key -components are: +The economy is populated by a representative household with subjective +beliefs, competitive producers of a final good, monopolistic producers of +intermediate goods, and labor-market firms that hire workers through a +matching friction. + +In the benchmark, all agents share the household's subjective beliefs; +the rational-firms variant below relaxes this. -**Households** --- A representative household has log utility in consumption and -fully shares consumption risk across its employed and unemployed members. +#### Representative household -Employed members earn the wage, unemployed members receive a benefit flow $D$, -and the household applies robust preferences (indexed by $\theta_t$) when -forming subjective forecasts. +The household chooses consumption $C_t$ and bond holdings $B_{t+1}$ under +robust preferences: + +$$ -**Firms** --- Labor-market firms post vacancies and match with searching workers, -while monopolistic intermediate-goods producers reset prices subject to Calvo -frictions (parameter $\chi_p$), generating a New Keynesian Phillips curve. +V_t = \max_{C_t,\, B_{t+1}}\; +\min_{\substack{m_{t+1} > 0 \\ E_t[m_{t+1}] = 1}} +\log C_t + \beta E_t\!\left[m_{t+1} V_{t+1}\right] ++ \frac{\beta}{\theta_t} E_t\!\left[m_{t+1} \log m_{t+1}\right], -Wages adjust sluggishly through a partial-adjustment rule (parameter $\chi_w$) -following {cite}`Shimer2010`. +$$ + +where the penalty parameter follows the exogenous AR(1) **belief shock** + +$$ -**Monetary policy** --- A Taylor rule that reacts to inflation and the output gap. +\theta_t = (1 - \rho_\theta)\mu_\theta + \rho_\theta \theta_{t-1} ++ \sigma_\theta w_t^\theta. + +$$ + +This fits the framework above by appending $\theta_t$ to the state vector +$x_t$ and letting $\bar\theta$ select that component. + +The household consists of a unit mass of workers who perfectly share +consumption risk. + +A fraction $L_t$ is employed and earns the real wage $\xi_t$; the remaining +$1 - L_t$ are unemployed and receive benefits $D$ financed by lump-sum taxes. + +The nominal budget constraint is + +$$ -**Exogenous shocks** --- Three shocks drive the model: +P_t C_t + B_{t+1} \;\le\; (1 - L_t)\, P_t D + L_t P_t \xi_t ++ R_{t-1} B_t - T_t, + +$$ + +where $P_t$ is the price level, $B_{t+1}$ are one-period bonds purchased at +$t$ with gross return $R_t$, and $T_t$ collects lump-sum taxes net of profits. + +The process for $\theta_t$ is exogenous, but which states are *adverse* is +endogenous: equilibrium dynamics determine which states deliver low +continuation values $V_{t+1}$, and those are the states the household +overweights. + +#### Labour market + +At the end of each period, an employed worker keeps the job with probability +$\rho$, so $1 - \rho L_{t-1}$ workers search at the beginning of period $t$. + +Employment evolves as + +$$ + +L_t = \rho L_{t-1} + (1 - \rho L_{t-1})\, f_t, + +$$ + +where $f_t$ is the job-finding probability, and measured unemployment is +$u_t = 1 - L_t$. + +Labor-market firms post $v_t L_{t-1}$ vacancies at flow cost $\kappa_v$ each, +so labor market tightness is + +$$ -1. **Belief shock** $\theta_t$: an AR(1) capturing time-varying pessimism. -2. **TFP shock** $a_t$: standard technology shock. -3. **Monetary policy shock** $r_t$: i.i.d.\ deviation from the Taylor rule. +\zeta_t = \frac{v_t L_{t-1}}{1 - \rho L_{t-1}}. + +$$ + +A Cobb--Douglas matching function +$M_t = \mu\, (v_t L_{t-1})^{\nu} (1 - \rho L_{t-1})^{1-\nu}$ +combines vacancies and searchers, so + +$$ + +f_t = \mu \zeta_t^{\nu}, +\qquad +q_t = \frac{f_t}{\zeta_t}, + +$$ + +where $q_t$ is the probability of filling a vacancy. + +Workers value jobs using the household's subjective beliefs. + +Let $s_{t+1} = \beta C_t / C_{t+1}$ denote the household's stochastic discount +factor and $\tilde E_t[\cdot]$ the subjective expectation. + +The values of unemployment and employment satisfy + +$$ + +U_t = D + \tilde E_t\!\left[ +s_{t+1}\bigl(f_{t+1} J^w_{t+1} + (1 - f_{t+1}) U_{t+1}\bigr)\right] + +$$ + +and + +$$ + +J^w_t = \xi_t ++ \tilde E_t\!\left[s_{t+1}\bigl(\rho + (1-\rho) f_{t+1}\bigr) J^w_{t+1}\right] ++ \tilde E_t\!\left[s_{t+1} (1-\rho)(1 - f_{t+1})\, U_{t+1}\right], + +$$ + +where $\rho + (1-\rho)f_{t+1}$ combines keeping the current job with losing it +but immediately finding a new one. + +A firm that employs a worker sells the labor services at the competitive price +$\vartheta_t$ and values the match at + +$$ + +J_t = \vartheta_t - \xi_t + \rho\, \tilde E_t\!\left[s_{t+1} J_{t+1}\right], + +$$ + +and free entry drives the expected profit from posting a vacancy to zero: + +$$ + +J_t = \frac{\kappa_v}{q_t}. + +$$ + +Wages are set by Nash bargaining with rigid wages, following +{cite}`Shimer2010`. + +The firm and the worker bargain over a *target* wage $\xi_t^*$ that splits the +match surplus according to the bargaining weight $\eta$: + +$$ + +\eta \left(J_t + \xi_t - \xi_t^*\right) += (1 - \eta)\left(J^w_t - U_t + \xi_t^* - \xi_t\right). + +$$ + +The actual wage adjusts only partially toward the target, + +$$ + +\xi_t = \chi_w\, \xi_{t-1} + (1 - \chi_w)\, \xi_t^*, + +$$ + +with $\chi_w = 0$ corresponding to flexible wages. + +Vacancy posting and bargaining are forward-looking, so fluctuations in +$\theta_t$ directly move firms' incentives to create matches. + +This is a sharp contrast with a Walrasian spot labor market, where one-period +contracts leave no role for beliefs about future economic conditions. + +#### Production and price setting + +A competitive final-good sector aggregates a continuum of intermediate +varieties, + +$$ + +Y_t = \left[\int_0^1 (Y_{j,t})^{(\varepsilon-1)/\varepsilon}\, dj +\right]^{\varepsilon/(\varepsilon-1)}, +\qquad +Y_{j,t} = \left(\frac{P_t}{P_{j,t}}\right)^{\varepsilon} Y_t, + +$$ + +where $\varepsilon$ is the elasticity of substitution. + +Monopolistic intermediate producers buy labor services at price $\vartheta_t$ +and produce with the linear technology + +$$ + +Y_{j,t} = \exp(a_t)\, l_{j,t} - \phi, + +$$ + +where $a_t$ is log TFP and $\phi$ is a fixed cost. + +Each producer reoptimizes its price with probability $1 - \chi_p$ (Calvo), so +price setting is a dynamic problem and is distorted by the firm's subjective +beliefs. + +Goods and labor markets clear: + +$$ + +C_t + \frac{\kappa_v}{q_t}\, h_t L_{t-1} = Y_t, +\qquad +\int_0^1 l_{j,t}\, dj = L_t, + +$$ + +where $h_t = f_t (1 - \rho L_{t-1}) / L_{t-1}$ is the hiring rate and the +second term in the resource constraint is total vacancy-posting costs. + +#### Monetary policy and shocks + +The monetary authority follows a Taylor rule with smoothing, + +$$ + +\log \frac{R_t}{\bar R} += \rho_r \log \frac{R_{t-1}}{\bar R} ++ (1 - \rho_r)\left[ +r_\pi \log \frac{\pi_t}{\bar\pi} + r_y \log \frac{Y_t}{Y^*} +\right] ++ \sigma_r w_t^r, + +$$ + +where $\pi_t = P_t / P_{t-1}$ is gross inflation, $\bar\pi$ is the intercept, +and $Y^*$ is steady-state output. + +TFP follows + +$$ + +a_{t+1} = \rho_a a_t + \sigma_a w_{t+1}^a. + +$$ + +The three innovations are independent under the data-generating measure $P$: + +$$ + +(w_t^r,\, w_t^a,\, w_t^\theta)' \overset{iid}{\sim} N(0, I). + +$$ + +Under the subjective measure they are not independent: the optimal distortion +ties their joint distribution to the current level of $\theta_t$, and this +subjective correlation structure is the source of the belief wedges. + +#### Where beliefs enter + +Every forward-looking equilibrium condition uses the subjective expectation +$\tilde E_t$: the household's consumption Euler equation, the worker and firm +match values, free entry, the bargaining problem, and the Calvo price setters' +first-order conditions. + +In the linearized solution, each of these conditions is one row of the system +$0 = \tilde E_t[g(x_{t+1}, x_t, x_{t-1}, w_{t+1}, w_t)]$ described in the +appendix, where the belief distortion can be switched on or off row by row. ### Calibration @@ -769,7 +1260,7 @@ The model is calibrated to quarterly U.S. data, 1982Q1–2019Q4. | Price stickiness | $\chi_p$ | 0.75 | Calvo parameter | | Wage rigidity | $\chi_w$ | 0.925 | Partial adjustment | | Steady-state markup | $\lambda$ | 1.2 | | -| Policy-rule intercept | $\bar\pi$ | 0.01 | Inflation target | +| Policy-rule intercept | $\bar\pi$ | 0.01 | Targets 2% annual inflation | | Policy-rule smoothing | $\rho_r$ | 0.84 | | | Taylor-rule inflation loading | $r_\pi$ | 1.60 | | | Taylor-rule output loading | $r_y$ | 0.028 | | @@ -777,8 +1268,8 @@ The model is calibrated to quarterly U.S. data, 1982Q1–2019Q4. | Persistence of $\theta$ | $\rho_\theta$ | 0.714 | | | Volatility of $\theta$ shock | $\sigma_\theta$ | 4.3 | | | TFP persistence | $\rho_a$ | 0.840 | | -| TFP volatility | $100\sigma_a$ | 0.568% | | -| MP volatility | $100\sigma_r$ | 0.078% | | +| TFP volatility | $100\sigma_a$ | 0.568 | | +| MP volatility | $100\sigma_r$ | 0.078 | | | Job survival probability | $\rho$ | 0.89 | Separation rate $1-\rho=0.11$ | | Matching efficiency | $\mu$ | 0.67 | | | Matching-function curvature | $\nu$ | 0.72 | From {cite}`Shimer2005` | @@ -786,6 +1277,28 @@ The model is calibrated to quarterly U.S. data, 1982Q1–2019Q4. | Vacancy posting cost | $\kappa_v$ | 0.09 | | | Unemployment benefit flow | $D$ | 0.57 | | +The calibration has three blocks. + +Conventional steady-state targets pin down most parameters: a 1% annual real +return, a 20% markup with price spells of about three quarters, an 11% +quarterly job separation rate, a job-finding rate of 0.67, a flow value of +unemployment equal to 70% of wages, and steady-state labor market tightness +equal to one. + +The TFP process is estimated from measured TFP, and the wage rigidity and +monetary policy parameters are chosen so that responses of inflation and +unemployment to TFP and monetary policy shocks match VAR evidence. + +Matching these responses matters because the wedge formula above makes belief +distortions proportional to covariances of shocks with continuation values: +the model has to propagate fundamental shocks correctly before its belief +wedges can be meaningfully compared with the survey data. + +The belief-shock parameters are read off the survey wedges: +$\rho_\theta = 0.714$ is the autocorrelation of the first principal component +of the wedges, and $\mu_\theta$ and $\sigma_\theta$ fit the means and +volatilities of the two wedges. + ### A self-contained linear surrogate Solving the full structural model requires the series expansion method @@ -815,9 +1328,12 @@ posting, and wages. Here we compress those equilibrium channels into a few linear loadings. -The loadings are chosen so that the Lyapunov moments for unemployment, -inflation, and output match the benchmark and no-belief-shock columns of -the moment table in the next section. +The loadings are chosen so that the volatilities of unemployment, inflation, +and output, computed from the Lyapunov equation, match the benchmark and +no-belief-shock columns of the moment table in the next section. + +The volatility match is essentially exact; conditional objects, such as +impact magnitudes and correlations with output, are only qualitatively right. This surrogate is useful for transparent computations, but it should not be used to analyse the diagnostic variants such as "only $\theta_t$", no TFP @@ -974,6 +1490,8 @@ output is detrended. | Volatility of unemployment | 1.70 | 1.39 | 0.55 | 0.00 | | Corr. inflation wedge, output | −0.30 | −0.67 | 0.00 | 0.00 | | Corr. unemployment wedge, output | −0.49 | −0.67 | 0.00 | 0.00 | +| Corr. inflation, output | 0.10 | −0.82 | −0.85 | 0.00 | +| Corr. unemployment, output | −0.87 | −0.74 | −0.33 | 0.00 | Two lessons are important. @@ -1009,13 +1527,14 @@ A positive innovation to $\theta_t$ makes households more pessimistic. The mechanism works this way: -1. Pessimistic households expect worse future outcomes and reduce consumption - demand. -2. Firms' valuation of new matches falls, vacancy posting declines, output - falls, and unemployment rises. -3. Firms that share the pessimistic beliefs put extra probability on - low-productivity, high-marginal-cost states, weakening the disinflationary - force and sometimes raising inflation briefly on impact. +1. Households put more subjective probability on futures with low + productivity, tight monetary policy, and continued pessimism, so they cut + current consumption to smooth the bad times they anticipate. +2. Firms, who share these beliefs, mark down their valuation of new matches; + vacancy posting and the job-finding rate fall, output falls, and + unemployment rises. +3. Price setters expect lower future productivity and hence higher marginal + costs, so they resist cutting prices despite the fall in demand. 4. The belief wedges jump on impact, then decay with the persistence $\rho_\theta = 0.714$. @@ -1026,6 +1545,9 @@ percent. Inflation rises briefly and then falls, leaving a roughly zero cumulative 10-quarter response. +The contraction is sizable: the cumulative 10-quarter output loss is about +two-thirds of the loss following a one-standard-deviation drop in TFP. + The reduced-form impulse responses below are calibrated to reproduce these signs and the volatility scale in the moment table above. @@ -1089,6 +1611,13 @@ After a positive $\theta_t$ innovation, pessimistic agents behave as if future TFP shocks are worse, monetary policy shocks are tighter, and future pessimism is more persistent than under the data-generating measure. +Agents distort the TFP shock more than the monetary policy shock, because TFP +matters more for their continuation values. + +Under the subjective measure, households expect consumption to fall further +and recover far more slowly than it actually does, and they expect inflation +to remain persistently high. + That subjective correlation structure is what makes both unemployment and inflation forecasts biased upward. @@ -1153,11 +1682,33 @@ counterpart. Adding the calibrated belief shock raises unemployment volatility from about 0.55 to about 1.39, moving the model much closer to the data value 1.70. +The belief shock also improves the model's fit to the historical record. + +{cite:t}`bhandari2025survey` feed measured TFP innovations and the +belief-shock innovations extracted from the wedge data into the model and +compare the implied paths with the data. + +The correlations between model-implied and actual paths are 0.51 for +unemployment, 0.83 for the unemployment wedge, and 0.79 for the inflation +wedge. + +Shutting down fluctuations in $\theta_t$ makes the model overstate +unemployment during the late-1990s boom and understate it around the Great +Recession. + +Through the lens of the model, the late 1990s were a period of relative +optimism, and much of the 2008--09 rise in unemployment reflected an increase +in pessimism. + One limitation of the benchmark model is its inflation cyclicality. -Inflation is nearly acyclical in the data but countercyclical in the model, -suggesting that omitted wage or price markup shocks may be important for -unconditional inflation dynamics without overturning the belief-wedge mechanism. +Inflation is nearly acyclical in the data (correlation with output of 0.10) +but countercyclical in the model (−0.82). + +The missing ingredients are wage and price markup shocks, which account for +much of unconditional inflation variation in richer DSGE models but have +little explanatory power for output and unemployment, so adding them would +leave the belief-wedge mechanism intact. ### Impulse responses to TFP and monetary policy shocks @@ -1241,6 +1792,13 @@ as a contraction in demand. Inflation falls on impact, and the inflation wedge is too small. +Wages also fall by less. + +Under Nash bargaining, the wage splits the gap between the firm's subjective +valuation of the match and the worker's subjective value of unemployment; a +rational firm does not mark down its valuation when $\theta_t$ rises, so the +perceived surplus stays larger and the bargained wage declines less. + Firm beliefs therefore strengthen the comovement between the unemployment wedge and the inflation wedge, which is needed to match the data. @@ -1253,14 +1811,18 @@ households, put extra subjective probability on high-marginal-cost states. Two diagnostic variants show how survey wedges restrict the model. -**No TFP shocks** --- Without supply-side uncertainty, pessimistic agents worry -mainly about demand-type shocks, so the model predicts a negative average -inflation wedge and negative comovement between inflation and unemployment -wedges, both of which are counterfactual. +**No TFP shocks** --- Setting $\sigma_a = 0$ leaves only demand-type +disturbances: monetary policy and belief shocks, which lower economic activity +and inflation together. + +The states households fear then combine high unemployment with *low* +inflation, so pessimism produces a negative average inflation wedge and +negative comovement between the two wedges --- both counterfactual. -The no-TFP variant has a mean inflation wedge of $-0.32$ and inflation-wedge -volatility of only $0.26$, even after the belief-shock process is recalibrated -to keep the unemployment wedge close to the benchmark. +This failure persists even after the belief-shock process is recalibrated (to +$\mu_\theta = 150$ and $\sigma_\theta = 117.7$) to keep the unemployment wedge +close to the benchmark: the mean inflation wedge is $-0.32$, its volatility is +only $0.26$, and it becomes procyclical. **Rational firms** --- If households are pessimistic but firms have rational beliefs, unemployment still responds strongly to a belief shock. @@ -1281,6 +1843,7 @@ The table below summarizes the two restrictions. | Volatility of inflation wedge | 0.73 | 0.26 | 0.29 | | Volatility of unemployment wedge | 0.45 | 0.43 | 0.45 | | Volatility of unemployment | 1.39 | 0.87 | 1.24 | +| Corr. inflation wedge, output | −0.67 | 0.50 | −0.53 | These variants show why the benchmark needs both supply-side uncertainty and firms' subjective beliefs to match the joint behavior of inflation and @@ -1369,23 +1932,49 @@ $$ where $z_t$ is inflation or unemployment. Under full-information rational expectations these errors should be mean zero -and unforecastable. +and unforecastable: $b_0 = b_z = b_f = 0$. + +In the data, $b_0 < 0$ for both variables, restating the average upward biases. + +The slopes reveal two different updating patterns: inflation forecast errors +are predicted by the lagged forecast ($b_f = -0.50$), the signature of +sluggish updating, while unemployment forecast errors are predicted by the +current unemployment rate ($b_z = -0.40$), the signature of overreaction. + +The calibrated model reproduces the signs, magnitudes, and $R^2$ of all three +patterns. + +Models of information frictions and models of overreaction imply $b_0 = 0$, +and each matches only one of the two slope patterns; the belief-wedge model +matches everything because the household pessimistically distorts the *joint* +distribution of inflation and unemployment. + +A final check compares the belief factor with familiar sentiment measures. + +The first principal component of the wedges has correlations of $-0.65$ with +the Michigan Consumer Sentiment Index and $-0.72$ with the Conference Board +Consumer Confidence Index, so the belief wedges capture what sentiment indices +capture --- with the advantage that wedges quantify the forecast bias in +percentage points, which is what makes the model calibration possible. -The survey data and the calibrated model both generate predictable forecast -errors, consistent with the subjective-belief mechanism. +In contrast, the wedges are largely uncorrelated with dispersion in SPF +forecasts, so time-varying pessimism is distinct from forecaster disagreement. ## Extensions Several extensions of the benchmark model are worth noting: -**Heterogeneous beliefs** --- A natural question is whether households and -firms should hold the same subjective beliefs. +**Heterogeneous beliefs** --- The solution method allows the belief distortion +to be switched on or off equation by equation, so different agents can hold +different subjective beliefs. -Allowing firms to be *rational* while households are pessimistic changes -the inflation dynamics substantially. +The rational-firms variant above is one example, and the relative sizes of the +unemployment and inflation wedges identify whose beliefs are distorted. -This separation is identified from -the relative sizes of the unemployment and inflation wedges. +With incomplete markets, heterogeneous exposures of continuation values to +shocks would generate belief heterogeneity across households endogenously, +with implications for saving, portfolio choice, and the design of social +insurance. **Pessimism induced by TFP** --- The benchmark treats $\theta_t$ as an exogenous AR(1) process. @@ -1396,18 +1985,23 @@ This variant matches many unconditional moments: the inflation wedge mean is $0.85$, the unemployment wedge mean is $0.56$, and unemployment volatility is $1.49$. -Its weakness is dynamic: TFP shocks generate responses that are too large, and -the fit to the historical paths of unemployment and subjective forecasts is -worse than in the benchmark with an orthogonal belief shock. +Its weakness is dynamic: responses to TFP shocks become counterfactually large +relative to the VAR evidence, and the correlations of model-implied paths with +the data fall to 0.22 (unemployment), 0.20 (unemployment wedge), and 0.35 +(inflation wedge), compared with 0.51, 0.83, and 0.79 in the benchmark. + +{cite:t}`bhandari2025survey` read this as evidence that quantitatively +important movements in pessimism are orthogonal to productivity. **Wage rigidity** --- Wage rigidity is important for amplification. -When wages are flexible, unemployment volatility falls to $0.77$ and -unemployment-wedge volatility falls to $0.13$. +With flexible wages ($\chi_w = 0$), bargained wages absorb shocks, firm values +move less, and unemployment volatility falls from $1.39$ to $0.77$ --- the +Shimer-style amplification problem in another form. -This is the Shimer-style labor-market amplification problem in another form: -without sluggish wages, belief shocks and TFP shocks move match values too -little. +Lower macroeconomic volatility feeds back into beliefs: with less to fear, the +covariance between forecasted variables and continuation values shrinks, and +unemployment-wedge volatility falls from $0.45$ to $0.13$. **Beyond the first-order homoskedastic case** --- The approximation is designed to keep subjective-belief effects alive in a linear solution. @@ -1820,49 +2414,51 @@ The algorithm therefore decomposes cleanly into two stages: This separation is a major practical advantage: existing rational-expectations solvers can be used for Stage 1 with only a wrapper for Stage 2. +In the scalar endowment model, Stage 2 is simple because the value function is +the only forward-looking object. + +The code below carries out the two computable ingredients: it solves the +Riccati equation by iteration starting from the rational-expectations value, +as the paper does, and then reads off the subjective transition coefficients +from the re-centred shocks. + ```{code-cell} ipython3 -β = model.β -ρ_x = model.ρ_x -σ_x = model.σ_x -ρ_f = model.ρ_θ -σ_f = model.σ_θ -Vx = model.Vx - -# Stage 1 rational-expectations objects -ψ_x_s1 = ρ_x -ψ_w_s1 = σ_x - -# Simple log-utility derivative with respect to x_{t+1} -gx_plus = β * (1 - β) - -θ_f = 1.0 # f is θ in the partitioned state. - -# First-order scalar fixed point -denom = gx_plus * ψ_x_s1 - (1 - β) -E_const = (gx_plus * ψ_w_s1) * ψ_w_s1 * Vx * θ_f -penalty_const = (β * θ_f / 2.0) * (Vx * ψ_w_s1)**2 - -A_fp = np.array([ - [1 - β * ρ_f, -β * ρ_f * Vx], - [0.0, 1 + gx_plus * ρ_f / denom], -]) -b_fp = np.array([ - -penalty_const, - E_const / denom, -]) - -Vf, ψ_xf = np.linalg.solve(A_fp, b_fp) - -print("Stage 2 fixed point:") -print(f" Vf = {Vf:.6f}") -print(f" ψ_xf = {ψ_xf:.6f} (impact of belief shock on endogenous state)") -print() -print("Interpretation: a one-unit rise in f_t changes x by ψ_xf =", - f"{ψ_xf:.4f} per period.") -print("The steady-state wedge for x: Δ = ψ_w * ν_bar =", - f"{σ_x * (-model.μ_θ * (Vx * σ_x)):.4f}") +# Stage 1: rational-expectations objects of the scalar endowment model +ψ_x_re = model.ρ_x +ψ_w_re = model.σ_x +u_x = 1 - model.β + +# Stage 2 value block: iterate on the Riccati equation +Vx_iter = u_x / (1 - model.β * ψ_x_re) # start at the RE value +for it in range(1, 200): + Vx_new = (u_x + model.β * ψ_x_re * Vx_iter + - 0.5 * model.β * model.μ_θ * ψ_w_re**2 * Vx_iter**2) + if abs(Vx_new - Vx_iter) < 1e-15: + break + Vx_iter = Vx_new + +print(f"Riccati by iteration ({it} steps): Vx = {Vx_iter:.10f}") +print(f"Riccati by quadratic formula: Vx = {model.Vx:.10f}") + +# Subjective transition coefficients implied by the re-centred shocks +ψ_x_subj = ψ_x_re - model.μ_θ * Vx_iter * ψ_w_re**2 +ψ_q_subj = -model.μ_θ * 1.0 * Vx_iter * ψ_w_re**2 # x̄ normalized to 1 + +print(f"\nObjective persistence: ψ_x = {ψ_x_re:.8f}") +print(f"Subjective persistence: ψ̃_x = {ψ_x_subj:.8f}") +print(f"Subjective drift: ψ̃_q = {ψ_q_subj:.2e}") ``` +The two solution methods agree. + +For this consumption-type state ($V_x > 0$), the subjective persistence is +slightly *below* the objective one and the subjective drift is negative: the +pessimist expects good states to fade and bad outcomes to arrive. + +For an unemployment-type state ($V_x < 0$), both signs flip, so subjective +persistence exceeds objective persistence --- the formal statement of +"pessimists believe bad times last longer". + ### Sequence problem and dynamic consistency The recursive formulation used throughout the lecture emerges from the @@ -1967,6 +2563,10 @@ In the New Keynesian application, the same belief shock raises unemployment, creates comoving unemployment and inflation forecast wedges, and helps close the unemployment volatility gap left by TFP and monetary-policy shocks alone. +The survey wedges do double duty: they calibrate the belief-shock process, and +their joint behavior across variables --- means, comovement, cyclicality, and +forecast-error predictability --- over-identifies and thereby tests the model. + ## Exercises ```{exercise-start} From 054fefe8ae706cbcb20f9d3994acf9d9d32545b1 Mon Sep 17 00:00:00 2001 From: HumphreyYang Date: Fri, 12 Jun 2026 12:05:49 +1000 Subject: [PATCH 23/25] updates --- .../bbh_macro_quarterly.csv | 261 ++++ .../bbh_michigan_monthly.csv | 508 ++++++ .../subjective_beliefs_business_cycles.md | 1370 +++++++---------- 3 files changed, 1296 insertions(+), 843 deletions(-) create mode 100644 lectures/_static/lecture_specific/subjective_beliefs_business_cycles/bbh_macro_quarterly.csv create mode 100644 lectures/_static/lecture_specific/subjective_beliefs_business_cycles/bbh_michigan_monthly.csv diff --git a/lectures/_static/lecture_specific/subjective_beliefs_business_cycles/bbh_macro_quarterly.csv b/lectures/_static/lecture_specific/subjective_beliefs_business_cycles/bbh_macro_quarterly.csv new file mode 100644 index 00000000..ef779444 --- /dev/null +++ b/lectures/_static/lecture_specific/subjective_beliefs_business_cycles/bbh_macro_quarterly.csv @@ -0,0 +1,261 @@ +YYYYQ,GDP,GDPC1,GDPPOT,PCESV,GPDI,PIRIC,PRS85006023,CPIAUCSL,PCEND,UNRATE,CUMFNS,FEDFUNDS,CE16OV,CNP16OV +19551,413.073,2815.134,2757.5375,108.459,68.702,3.7812,116.228,26.7933,,4.7333,84.4645,1.3433,60814.6667,109130.3333 +19552,421.532,2860.942,2775.4869,109.634,72.688,3.8033,116.416,26.7567,,4.4,87.4043,1.5,61643.3333,109533.6667 +19553,430.221,2899.578,2793.0734,111.288,74.747,3.8236,116.436,26.7767,,4.1,87.483,1.94,62753.3333,109883.6667 +19554,437.092,2916.985,2811.3068,114.179,78.882,3.828,116.693,26.8567,,4.2333,88.5527,2.3567,63310.6667,110186.0 +19561,439.746,2905.656,2829.9084,115.94,78.303,3.8365,116.288,26.86,,4.0333,87.5453,2.4833,63560.6667,110483.3333 +19562,446.01,2929.666,2848.3139,117.723,77.02,3.8171,115.838,27.0367,,4.2,86.4917,2.6933,63765.0,110787.6667 +19563,451.191,2927.034,2867.5765,119.958,78.267,3.8304,115.873,27.3167,,4.1333,84.154,2.81,63950.3333,111113.3333 +19564,460.463,2975.209,2887.9829,122.272,77.145,3.8457,116.08,27.55,,4.1333,86.3631,2.9267,63893.6667,111431.0 +19571,469.779,2994.259,2909.9969,123.876,77.728,3.8527,115.685,27.7767,,3.9333,86.521,2.9333,64097.6667,111720.3333 +19572,472.025,2987.699,2933.4487,125.528,77.907,3.8553,114.913,28.0133,,4.1,84.5961,3.0,64076.0,112045.3333 +19573,479.49,3016.979,2957.9832,127.521,79.339,3.8151,114.636,28.2633,,4.2333,83.8886,3.2333,64206.6667,112430.6667 +19574,474.864,2985.775,2983.7255,129.85,71.045,3.7711,113.608,28.4,,4.9333,79.4514,3.2533,63879.0,112865.6667 +19581,467.54,2908.281,3009.6601,130.63,66.73,3.6815,113.415,28.7367,,6.3,74.0657,1.8633,62949.6667,113236.3333 +19582,471.978,2927.395,3036.5506,133.0,65.065,3.6384,113.546,28.93,,7.3667,72.406,0.94,62745.0,113532.0 +19583,485.841,2995.112,3063.9644,135.471,71.999,3.6083,114.08,28.9133,,7.3333,75.4322,1.3233,62979.3333,113846.3333 +19584,499.555,3065.141,3092.7924,137.049,80.001,3.5888,114.641,28.9433,,6.3667,78.1743,2.1633,63498.0,114283.3333 +19591,510.33,3123.978,3122.0075,139.726,83.166,3.5845,115.042,28.9933,126.0667,5.8333,81.3723,2.57,63939.6667,114714.3333 +19592,522.653,3194.429,3152.2324,142.888,89.381,3.5742,115.397,29.0433,127.1667,5.1,84.5589,3.0833,64772.0,115139.0 +19593,525.034,3196.683,3184.1616,146.201,83.606,3.5454,115.039,29.1933,128.2,5.2667,80.4988,3.5767,64875.0,115550.6667 +19594,528.6,3205.79,3216.3037,149.277,86.524,3.5154,114.763,29.37,129.5,5.6,80.0757,3.99,64927.3333,115918.0 +19601,542.648,3277.847,3249.2975,151.304,96.476,3.4992,114.518,29.3967,129.6,5.1333,84.4715,3.9333,65213.3333,116707.6667 +19602,541.08,3260.177,3282.0234,153.813,87.096,3.4719,114.572,29.5733,131.9667,5.2333,81.318,3.6967,66061.3333,117036.6667 +19603,545.604,3276.133,3314.9506,154.623,86.377,3.4464,114.685,29.59,131.5667,5.5333,78.8989,2.9367,66023.6667,117411.0 +19604,540.197,3234.087,3347.584,156.921,75.963,3.4216,114.205,29.78,132.4,6.2667,75.8523,2.2967,65839.6667,117824.3333 +19611,545.018,3255.914,3380.7233,158.878,78.378,3.4126,113.975,29.84,133.4,6.8,73.8284,2.0033,65738.0,118254.3333 +19612,555.545,3311.181,3413.0685,161.891,84.108,3.4219,113.933,29.83,134.2333,7.0,76.3543,1.7333,65605.3333,118636.0 +19613,567.664,3374.742,3446.7717,163.156,90.917,3.4115,114.099,29.9467,134.6,6.7667,78.4144,1.6833,65667.0,119000.6667 +19614,580.612,3440.924,3480.8275,166.742,92.931,3.3997,114.538,29.99,136.1667,6.2,80.6409,2.4,65966.6667,119189.6667 +19621,594.013,3502.298,3515.977,169.287,98.074,3.3763,114.248,30.1067,137.8333,5.6333,81.1862,2.4567,66379.6667,119378.6667 +19622,600.366,3533.947,3552.0383,172.666,96.706,3.3598,114.784,30.22,138.7667,5.5333,81.3356,2.6067,66576.6667,119819.3333 +19623,609.027,3577.362,3589.0123,174.806,98.16,3.349,114.733,30.3067,139.9667,5.5667,81.5601,2.8467,66881.0,120368.0 +19624,612.28,3589.128,3626.7058,177.594,94.968,3.3247,114.29,30.38,141.5,5.5333,81.6345,2.9233,66969.3333,121045.6667 +19631,621.672,3628.306,3664.8477,179.236,99.689,3.3128,114.418,30.4767,142.4667,5.7667,82.3156,2.9667,67149.0,121640.0 +19632,629.752,3669.02,3703.8465,181.728,101.65,3.3031,114.65,30.5333,142.8,5.7333,83.7831,2.9633,67635.3333,122166.6667 +19633,644.444,3749.681,3743.5307,185.526,104.612,3.274,114.343,30.72,145.1667,5.5,83.5563,3.33,67995.6667,122669.6667 +19634,653.938,3774.264,3784.049,189.031,107.189,3.2583,114.636,30.8033,145.3,5.5667,84.2037,3.4533,68258.0,123188.6667 +19641,669.822,3853.835,3824.6709,192.935,110.474,3.2211,115.857,30.93,148.6333,5.4667,84.4956,3.4633,68613.6667,123708.0 +19642,678.674,3895.793,3866.1988,196.604,110.518,3.2183,116.111,30.98,151.4667,5.2,85.5447,3.49,69401.6667,124203.0 +19643,692.031,3956.657,3908.2547,200.168,112.631,3.2,115.867,31.05,154.8667,5.0,86.057,3.4567,69480.0,124739.3333 +19644,697.319,3968.878,3950.3756,203.811,114.984,3.2021,116.019,31.1933,155.7667,4.9667,86.492,3.5767,69710.3333,125289.0 +19651,717.79,4064.915,3993.8515,207.035,126.542,3.1933,116.672,31.29,157.7667,4.9,88.866,3.9767,70187.6667,125814.0 +19652,730.191,4116.267,4036.6403,211.056,127.052,3.1683,116.44,31.49,160.7667,4.6667,89.3981,4.08,70897.3333,126324.6667 +19653,749.323,4207.782,4081.0984,215.035,131.237,3.139,116.14,31.5833,164.0667,4.3667,89.9023,4.0767,71369.3333,126745.0 +19654,771.857,4304.731,4126.1029,220.065,133.752,3.123,116.194,31.75,170.5333,4.1,89.9839,4.1667,71827.0,127169.3333 +19661,795.734,4409.518,4171.8648,223.73,144.2,3.0595,116.444,32.0467,174.0667,3.8667,91.1195,4.56,72173.3333,127511.3333 +19662,804.981,4424.581,4218.3113,228.23,143.501,3.0507,116.104,32.3367,177.2667,3.8333,91.5667,4.9133,72594.0,127868.6667 +19663,819.638,4462.053,4265.4766,232.198,143.194,3.0176,115.668,32.6167,179.7667,3.7667,91.2437,5.41,73088.0,128233.6667 +19664,833.302,4498.66,4312.4901,237.04,145.855,3.0124,115.136,32.8833,180.4,3.7,90.5906,5.5633,73656.6667,128617.0 +19671,844.17,4538.498,4360.2718,240.841,142.811,3.0039,114.527,32.9667,182.3667,3.8333,88.5845,4.8233,73572.0,129043.6667 +19672,848.983,4541.28,4408.3508,244.953,137.495,2.9987,113.789,33.1667,184.0333,3.8333,86.7784,3.99,74001.3333,129527.0 +19673,865.233,4584.246,4456.8385,250.241,142.835,2.9892,113.68,33.5,185.7667,3.8,85.9545,3.8933,74713.6667,130165.6667 +19674,881.439,4618.812,4506.4596,254.743,147.653,2.9885,113.627,33.8667,187.8667,3.9,87.3031,4.1733,75216.3333,130757.3333 +19681,909.387,4713.013,4556.6781,261.903,152.288,2.9699,113.24,34.2,193.5667,3.7333,87.3264,4.79,75102.6667,131267.0 +19682,934.344,4791.758,4606.9936,269.161,158.943,2.9503,113.448,34.5333,197.8,3.5667,87.2785,5.9833,75950.0,131712.3333 +19683,950.825,4828.892,4658.5699,275.736,155.683,2.9281,113.436,35.0,202.8333,3.5333,86.6724,5.9467,76100.6667,132250.0 +19684,968.03,4847.885,4710.9814,282.144,160.76,2.9333,112.889,35.4333,205.0,3.4,87.0991,5.9167,76498.6667,132880.0 +19691,993.337,4923.76,4761.7534,288.161,172.388,2.9202,112.809,35.8667,208.8333,3.4,87.6714,6.5667,77166.3333,133476.0 +19692,1009.02,4938.728,4810.6972,295.673,172.721,2.9033,112.65,36.4333,212.2333,3.4333,86.924,8.3267,77605.0,134020.3333 +19693,1029.956,4971.349,4857.3564,302.01,177.564,2.8861,112.416,36.9333,215.9667,3.5667,86.843,8.9833,78153.0,134595.0 +19694,1038.147,4947.104,4902.0822,309.998,171.573,2.8856,111.973,37.5,219.7333,3.5667,85.1773,8.94,78575.3333,135246.6667 +19701,1051.2,4939.759,4945.1206,317.59,168.113,2.8735,111.487,38.1,224.5,4.1667,81.8428,8.5733,78780.3333,135949.6667 +19702,1067.375,4946.77,4986.3386,324.016,171.455,2.8871,110.699,38.6333,226.5667,4.7667,80.446,7.8867,78635.6667,136676.6667 +19703,1086.059,4992.357,5026.2052,331.755,173.904,2.8627,110.177,39.0333,229.7,5.1667,79.1387,6.7067,78616.0,137456.0 +19704,1088.608,4938.857,5065.0472,338.361,166.754,2.8629,110.078,39.6,234.4667,5.8333,76.4018,5.5667,78643.0,138260.3333 +19711,1135.156,5072.996,5103.9317,345.307,189.495,2.8778,110.102,39.9333,235.8,5.9333,77.5462,3.8567,78717.3333,139033.6667 +19712,1156.271,5100.447,5143.6084,353.404,197.329,2.8727,110.062,40.3,238.8333,5.9,77.6773,4.5667,78961.0,139827.3333 +19713,1177.675,5142.422,5183.8731,361.732,202.058,2.8571,109.872,40.7,240.4,6.0333,77.4289,5.4767,79511.0,140602.6667 +19714,1190.297,5154.547,5224.7492,370.843,198.411,2.8476,110.266,41.0,243.6,5.9333,79.1621,4.75,80228.6667,141401.6667 +19721,1230.609,5249.337,5266.3945,381.204,212.968,2.8542,110.289,41.3333,247.0333,5.7667,81.8018,3.5467,81213.3333,143005.3333 +19722,1266.369,5368.485,5308.6412,388.996,226.798,2.8588,110.266,41.6,254.3,5.7,82.8565,4.3,81875.0,143758.6667 +19723,1290.566,5419.184,5351.5746,398.123,233.09,2.8571,110.179,41.9333,260.1,5.5667,83.3599,4.7433,82450.3333,144522.6667 +19724,1328.904,5509.926,5395.2241,409.029,239.715,2.8505,110.034,42.3667,268.0667,5.3667,85.7091,5.1467,83002.0,145215.0 +19731,1377.49,5646.286,5439.8896,417.739,254.313,2.8328,109.922,43.0333,275.4333,4.9333,87.6636,6.5367,83841.6667,145964.3333 +19732,1413.887,5707.755,5486.6967,427.847,268.196,2.8075,109.874,43.9333,281.4,4.9333,87.6192,7.8167,84797.3333,146719.6667 +19733,1433.838,5677.738,5534.0687,438.16,264.335,2.7838,109.878,44.8,289.9,4.8,87.3638,10.56,85330.3333,147478.3333 +19734,1476.289,5731.632,5582.5199,448.011,280.858,2.7333,109.557,45.9333,297.7,4.7667,88.1166,9.9967,86236.0,148226.0 +19741,1491.209,5682.353,5631.5986,456.362,268.361,2.6739,108.769,47.3,308.8667,5.1333,86.5609,9.3233,86709.3333,148986.6667 +19742,1530.056,5695.859,5680.9704,471.822,277.391,2.6617,108.454,48.5667,318.0,5.2,85.7211,11.25,86833.6667,149746.6667 +19743,1560.026,5642.025,5729.7526,485.284,271.013,2.6938,108.142,49.9333,327.6667,5.6333,84.7454,12.09,87079.0,150498.0 +19744,1599.679,5620.126,5777.8023,501.022,281.339,2.7302,107.415,51.4667,330.9,6.6,80.4802,9.3467,86588.3333,151253.0 +19751,1616.116,5551.713,5824.5431,517.197,244.306,2.7645,106.529,52.5667,336.2,8.2667,73.5116,6.3033,85356.6667,151987.3333 +19752,1651.853,5591.382,5870.2553,532.041,243.281,2.7969,106.394,53.2,344.8333,8.8667,71.9256,5.42,85331.6667,152707.6667 +19753,1709.82,5687.087,5915.5758,544.44,265.192,2.7711,106.594,54.2667,355.9333,8.4667,73.8461,6.16,86135.6667,153579.0 +19754,1761.831,5763.665,5960.948,563.172,276.236,2.7563,107.153,55.2667,359.6667,8.3,75.3242,5.4133,86497.0,154336.3333 +19761,1820.487,5893.276,6006.2049,579.174,304.638,2.7416,107.512,55.9,367.4333,7.7333,77.3502,4.8267,87685.6667,155075.0 +19762,1852.332,5936.515,6052.324,590.571,322.303,2.7448,106.766,56.4,373.1,7.5667,78.0969,5.1967,88591.0,155773.6667 +19763,1886.558,5969.089,6099.8266,608.353,328.307,2.7215,106.57,57.3,380.6,7.7333,78.6868,5.2833,89163.0,156526.6667 +19764,1934.273,6012.356,6148.0796,627.345,337.65,2.7058,106.34,58.1333,389.5667,7.7667,79.3082,4.8733,89570.3333,157222.0 +19771,1988.648,6083.391,6197.9564,647.814,360.313,2.6821,106.198,59.2,396.4333,7.5,80.6792,4.66,90359.3333,157910.6667 +19772,2055.909,6201.659,6249.0099,663.054,389.703,2.6556,106.383,60.2333,403.7,7.1333,82.8604,5.1567,91661.3333,158652.3333 +19773,2118.473,6313.559,6300.8389,683.003,414.134,2.6552,106.152,61.0667,409.9,6.9,83.1817,5.82,92409.0,159429.6667 +19774,2164.27,6313.697,6354.0847,700.494,422.299,2.6657,105.993,61.9667,423.6333,6.6667,83.2387,6.5133,93639.3333,160140.3333 +19781,2202.76,6333.848,6408.7339,724.811,434.799,2.6718,105.219,63.0333,431.0667,6.3333,82.637,6.7567,94552.6667,160828.6667 +19782,2331.633,6578.605,6464.4701,750.386,470.584,2.6608,106.187,64.4667,444.8,6.0,84.7155,7.2833,95835.3333,161525.3333 +19783,2395.053,6644.754,6521.7113,769.856,492.368,2.65,106.015,65.9667,455.6,6.0333,84.797,8.1,96397.0,162265.0 +19784,2476.949,6734.069,6580.0271,789.689,515.755,2.6302,105.889,67.5,469.4,5.9,85.7337,9.5833,97399.6667,163024.0 +19791,2526.61,6746.176,6639.9792,809.273,525.809,2.6027,105.409,69.2,484.5667,5.8667,85.3371,10.0733,98252.3333,163756.3333 +19792,2591.247,6753.389,6697.9387,835.468,539.293,2.5661,104.811,71.4,500.0667,5.7,84.3488,10.18,98371.0,164447.3333 +19793,2667.565,6803.558,6757.0171,858.589,545.621,2.5323,105.139,73.7,522.3333,5.8667,83.5143,10.9467,99040.6667,165199.6667 +19794,2723.883,6820.572,6807.4314,886.599,547.875,2.5076,105.098,76.0333,539.4,5.9667,82.9414,13.5767,99637.0,166054.6667 +19801,2789.842,6842.024,6853.9741,910.301,554.562,2.4907,104.531,79.0333,559.9333,6.3,82.551,15.0467,99862.3333,166762.3333 +19802,2797.352,6701.046,6896.0535,926.892,519.294,2.4827,103.661,81.7,565.9,7.3333,77.8801,12.6867,98953.3333,167415.6667 +19803,2856.483,6693.082,6933.63,961.909,495.071,2.4702,103.47,83.2333,576.5,7.6667,75.7289,9.8367,98899.0,168110.6667 +19804,2985.557,6817.903,6973.2988,1004.394,551.472,2.4535,103.969,85.5667,591.2333,7.4,78.5143,15.8533,99498.6667,168693.6667 +19811,3124.206,6951.495,7017.0829,1025.643,619.381,2.4378,104.088,87.9333,614.0667,7.4333,78.0007,16.57,100239.0,169279.0 +19812,3162.532,6899.98,7064.0682,1053.74,609.843,2.4493,103.669,89.7667,622.8667,7.4,77.9005,17.78,100800.6667,169837.3333 +19813,3260.609,6982.609,7113.6671,1077.047,652.297,2.4495,103.244,92.2667,629.1333,7.4,77.2097,17.5767,100482.0,170412.6667 +19814,3280.818,6906.529,7165.8463,1101.901,643.395,2.4584,103.309,93.7667,635.7,8.2333,74.6717,13.5867,100076.6667,170990.3333 +19821,3274.302,6799.233,7219.8608,1127.862,588.318,2.4599,102.341,94.6,639.8667,8.8333,72.4271,14.2267,99708.6667,171497.0 +19822,3331.972,6830.251,7275.1977,1151.697,593.621,2.4655,103.04,95.9667,638.7,9.4333,71.6318,14.5133,99745.0,172020.0 +19823,3366.322,6804.139,7331.6461,1183.846,592.954,2.4384,103.032,97.6333,649.7333,9.9,70.6419,11.0067,99543.3333,172521.6667 +19824,3402.561,6806.857,7389.1969,1224.459,549.242,2.4175,102.93,97.9333,656.7667,10.6667,68.9264,9.2867,99119.6667,173046.0 +19831,3473.413,6896.561,7446.7071,1258.678,565.52,2.3998,103.308,98.0,657.0333,10.3667,70.2942,8.6533,99143.0,173505.0 +19832,3578.848,7053.5,7505.6008,1286.806,613.783,2.3729,103.546,99.1333,673.0333,10.1333,72.2148,8.8033,99945.0,173957.3333 +19833,3689.179,7194.504,7566.2177,1329.097,652.269,2.3397,104.3,100.1,688.4333,9.3667,74.6901,9.46,101610.6667,174449.3333 +19834,3794.706,7344.597,7629.3462,1356.704,718.496,2.3249,104.359,101.1,696.5,8.5333,76.7528,9.43,102588.0,174950.3333 +19841,3908.054,7488.167,7701.1611,1380.27,790.872,2.2808,104.713,102.5333,706.4667,7.8667,78.805,9.6867,103664.0,175678.6667 +19842,4009.601,7617.547,7768.8625,1412.233,818.894,2.2612,104.869,103.5,722.4,7.4333,79.5818,10.5567,105040.0,176125.3333 +19843,4084.25,7690.985,7838.097,1446.882,838.852,2.2434,104.475,104.4,724.6,7.4333,79.686,11.39,105362.6667,176595.3333 +19844,4148.551,7754.117,7908.4099,1475.175,831.741,2.2305,104.354,105.3,732.7667,7.3,79.5398,9.2667,105944.3333,177132.3333 +19851,4230.168,7829.26,7978.9846,1525.616,809.865,2.2096,104.402,106.2667,742.3667,7.2333,78.8659,8.4767,106615.3333,177522.3333 +19852,4294.887,7898.194,8049.7445,1555.754,827.04,2.1916,104.515,107.2333,752.7667,7.3,78.3372,7.9233,106791.0,177946.3333 +19853,4386.773,8018.809,8120.3593,1597.213,822.157,2.1729,104.308,107.9,760.4667,7.2,77.775,7.9,107186.3333,178413.3333 +19854,4444.094,8078.415,8190.5228,1622.303,859.545,2.1624,104.14,109.0,773.3667,7.0333,77.7767,8.1033,108023.3333,178940.6667 +19861,4507.894,8153.829,8260.076,1652.771,863.457,2.1462,103.889,109.5667,779.2667,7.0333,78.387,7.8267,108734.6667,179825.3333 +19862,4545.34,8190.552,8329.2905,1676.713,855.237,2.159,103.282,109.0333,767.4333,7.1667,78.1791,6.92,109205.6667,180320.6667 +19863,4607.669,8268.935,8398.3217,1700.452,835.832,2.1617,102.993,109.7,771.0667,6.9667,78.3772,6.2067,109970.0,180835.6667 +19864,4657.627,8313.338,8467.2809,1732.668,842.063,2.1573,103.12,110.4667,779.0,6.8333,78.9446,6.2667,110492.0,181365.3333 +19871,4722.156,8375.274,8535.8303,1767.634,871.196,2.1443,103.675,111.8,797.4,6.6,79.5409,6.22,111206.0,182001.3333 +19872,4806.16,8465.63,8604.0969,1801.948,874.588,2.1283,103.509,113.0667,812.2667,6.2667,80.2908,6.65,112158.0,182526.6667 +19873,4884.555,8539.075,8672.098,1836.16,876.466,2.1134,103.456,114.2667,820.6667,6.0,81.1952,6.8433,112866.6667,183016.0 +19874,5007.994,8685.694,8740.3552,1874.186,946.459,2.1067,103.663,115.3333,826.8,5.8333,82.9278,6.9167,113526.6667,183467.0 +19881,5073.372,8730.569,8808.2422,1923.07,908.569,2.0948,103.05,116.2333,838.4,5.7,83.2343,6.6633,114093.3333,183967.3333 +19882,5190.036,8845.28,8876.3763,1964.954,934.525,2.0799,103.315,117.5667,853.5667,5.4667,83.9856,7.1567,114623.0,184389.3333 +19883,5282.835,8897.107,8944.3285,2020.733,942.009,2.0619,103.127,119.0,870.7667,5.4667,84.0521,7.9833,115232.6667,184840.3333 +19884,5399.509,9015.661,9012.2245,2062.056,962.748,2.0556,103.529,120.3,886.3333,5.3333,84.7809,8.47,115947.3333,185253.3333 +19891,5511.253,9107.314,9079.7836,2101.195,1005.487,2.0385,103.707,121.6667,902.5333,5.2,84.905,9.4433,116835.3333,185772.6667 +19892,5612.463,9176.827,9146.9441,2132.674,1001.047,2.0136,103.788,123.6333,927.7667,5.2333,83.8181,9.7267,117204.6667,186178.0 +19893,5695.365,9244.816,9213.6664,2167.474,996.46,2.0056,103.951,124.6,936.2667,5.2333,82.5197,9.0833,117493.6667,186602.3333 +19894,5747.237,9263.033,9280.1172,2210.469,995.809,1.9947,103.546,125.8667,951.3333,5.3667,81.9263,8.6133,117774.3333,187017.6667 +19901,5872.701,9364.259,9345.7291,2248.382,1010.838,1.9713,103.269,128.0333,974.1667,5.3,82.1761,8.25,119114.3333,188519.6667 +19902,5960.028,9398.243,9410.6983,2304.16,1014.72,1.9527,102.795,129.3,980.8333,5.3333,82.1695,8.2433,118995.3333,188916.3333 +19903,6015.116,9404.494,9474.2945,2350.024,1000.785,1.9298,102.522,131.5333,1003.1333,5.7,81.8775,8.16,118712.0,189352.6667 +19904,6004.733,9318.876,9536.6607,2368.207,947.453,1.9068,102.515,133.7667,1018.7667,6.1333,80.1083,7.7433,118361.0,189866.3333 +19911,6035.178,9275.276,9598.1338,2387.721,924.569,1.9109,102.235,134.7667,1014.1667,6.6,77.9846,6.4267,117782.3333,190271.6667 +19912,6126.862,9347.597,9658.7723,2429.918,926.541,1.9006,102.078,135.5667,1021.7333,6.8333,78.0903,5.8633,117729.3333,190655.6667 +19913,6205.937,9394.834,9719.0523,2464.541,947.476,1.8871,102.219,136.6,1024.4,6.8667,79.2156,5.6433,117660.0,191121.3333 +19914,6264.54,9427.581,9779.6871,2501.877,978.788,1.8657,102.177,137.7333,1020.6667,7.1,79.1974,4.8167,117678.6667,191650.6667 +19921,6363.102,9540.444,9840.9574,2566.552,956.817,1.8464,102.098,138.6667,1037.6667,7.3667,78.8379,4.0233,117958.3333,192074.6667 +19922,6470.763,9643.893,9903.2242,2607.453,1013.084,1.8336,102.341,139.7333,1047.2,7.6,79.862,3.77,118406.6667,192506.6667 +19923,6566.641,9739.185,9967.1893,2653.829,1024.162,1.821,102.285,140.8,1061.0333,7.6333,80.0819,3.2567,118753.0,193024.3333 +19924,6680.803,9840.753,10032.2124,2709.331,1057.962,1.8097,102.448,142.0333,1074.8,7.3667,80.1231,3.0367,118833.6667,193615.6667 +19931,6729.459,9857.185,10098.2893,2742.534,1083.829,1.7992,102.54,143.0667,1079.1333,7.1333,80.5712,3.04,119297.3333,194106.0 +19932,6808.939,9914.565,10165.978,2784.412,1094.479,1.7921,102.872,144.1,1086.3333,7.0667,80.4369,3.0,119959.6667,194555.3333 +19933,6882.098,9961.873,10234.6817,2838.023,1095.852,1.7874,102.974,144.7667,1092.5333,6.8,80.1822,3.06,120625.6667,195068.0 +19934,7013.738,10097.362,10304.222,2873.579,1153.142,1.7828,103.161,145.9667,1105.3,6.6333,81.127,2.99,121152.0,195621.0 +19941,7115.652,10195.338,10374.3207,2915.554,1201.675,1.7815,103.025,146.7,1116.8333,6.5667,81.5218,3.2133,121994.0,196085.3333 +19942,7246.931,10333.495,10444.8719,2956.348,1264.948,1.7764,103.688,147.5333,1128.1,6.2,82.6904,3.94,122596.0,196522.0 +19943,7331.075,10393.898,10516.4034,2993.87,1251.749,1.77,103.701,148.9,1149.6,6.0,83.0831,4.4867,123245.0,197050.0 +19944,7455.288,10512.962,10588.5101,3031.873,1307.566,1.7635,103.545,149.7667,1163.0,5.6333,84.1697,5.1667,124449.6667,197600.6667 +19951,7522.289,10550.251,10660.8512,3074.329,1327.586,1.7608,103.114,150.8667,1166.8667,5.4667,84.1047,5.81,124848.6667,197882.0 +19952,7580.997,10581.723,10733.9241,3129.711,1303.988,1.749,102.535,152.1,1177.0,5.6667,83.2542,6.02,124629.3333,198295.6667 +19953,7683.125,10671.738,10807.2352,3172.427,1303.248,1.7383,103.097,152.8667,1183.6667,5.6667,82.8261,5.7967,124933.6667,198807.0 +19954,7772.586,10744.203,10882.1475,3211.762,1335.135,1.7241,103.06,153.7,1191.6333,5.5667,82.4916,5.72,125221.3333,199351.6667 +19961,7868.468,10824.674,10958.2375,3259.617,1355.353,1.7089,102.529,155.0667,1211.1667,5.5333,81.6066,5.3633,125542.0,199776.0 +19962,8032.84,11005.217,11038.9935,3304.626,1418.388,1.6858,102.847,156.4,1239.5333,5.5,82.2476,5.2433,126280.0,200279.3333 +19963,8131.408,11103.935,11125.4637,3348.618,1474.35,1.6763,103.043,157.3,1246.5,5.2667,82.452,5.3067,127218.3333,200849.6667 +19964,8259.771,11219.238,11217.5508,3394.838,1480.128,1.6576,103.233,158.6667,1268.3,5.3333,82.2956,5.28,127840.3333,201457.3333 +19971,8362.655,11291.665,11315.1161,3446.497,1522.404,1.6452,103.317,159.6333,1281.0,5.2333,82.7629,5.2767,128495.6667,202395.6667 +19972,8518.825,11479.33,11417.5148,3496.96,1590.218,1.632,103.219,160.0,1277.7333,5.0,82.6928,5.5233,129339.6667,202835.3333 +19973,8662.823,11622.911,11525.0735,3559.055,1625.251,1.6211,103.326,160.8,1297.3333,4.8667,83.1887,5.5333,129950.3333,203366.6667 +19974,8765.907,11722.722,11636.5249,3618.627,1644.529,1.6067,103.169,161.6667,1307.8,4.6667,83.7156,5.5067,130503.6667,203935.3333 +19981,8866.48,11839.876,11751.2764,3676.961,1712.324,1.5935,103.258,162.0,1306.6667,4.6333,83.1161,5.52,130782.3333,204395.0 +19982,8969.699,11949.492,11869.7764,3743.404,1695.773,1.5788,103.081,162.5333,1319.9,4.4,81.765,5.5,131259.3333,204905.0 +19983,9121.097,12099.191,11990.4282,3806.982,1741.623,1.5633,102.764,163.3667,1334.8333,4.5333,80.8646,5.5333,131568.3333,205482.6667 +19984,9293.991,12294.737,12113.2492,3847.889,1796.964,1.5499,103.522,164.1333,1355.1333,4.4333,81.0535,4.86,132293.6667,206097.6667 +19991,9411.682,12410.778,12238.5118,3897.147,1853.063,1.5377,103.168,164.7333,1384.9667,4.3,80.8521,4.7333,132943.3333,206876.0 +19992,9526.21,12514.408,12365.285,3957.569,1848.341,1.5213,103.071,165.9667,1418.2667,4.2667,80.6023,4.7467,133214.6667,207431.6667 +19993,9686.626,12679.977,12493.7035,4024.047,1893.735,1.5037,103.152,167.2,1440.0667,4.2333,80.2136,5.0933,133570.6667,208043.6667 +19994,9900.169,12888.281,12624.3398,4108.02,1953.097,1.4891,103.137,168.4333,1482.6667,4.0667,80.8811,5.3067,134275.0,208660.3333 +20001,10002.179,12935.252,12756.1978,4205.131,1950.65,1.4752,103.015,170.1,1492.2,4.0333,80.7776,5.6767,136619.3333,211586.0 +20002,10247.72,13170.749,12885.5495,4274.861,2075.786,1.4667,102.828,171.4333,1535.1,3.9333,80.772,6.2733,136946.6667,212242.0 +20003,10318.165,13183.89,13010.0471,4350.834,2059.969,1.4528,102.666,173.0,1557.4667,4.0,79.7339,6.52,136695.3333,212918.6667 +20004,10435.744,13262.25,13129.1448,4425.204,2067.227,1.4404,102.399,174.2333,1577.6,3.9,78.2838,6.4733,137341.3333,213560.3333 +20011,10470.231,13219.251,13242.6261,4496.052,1971.333,1.4222,101.933,175.9,1572.4333,4.2333,76.2684,5.5933,137724.3333,214101.0 +20012,10599.0,13301.394,13350.3998,4532.144,1973.033,1.4095,101.56,177.1333,1590.6333,4.4,74.5433,4.3267,137088.0,214735.6667 +20013,10598.02,13248.142,13452.5044,4555.089,1944.909,1.4034,101.097,177.6333,1591.3667,4.8333,72.8865,3.4967,136719.3333,215421.6667 +20014,10660.465,13284.881,13549.5783,4609.546,1850.091,1.3966,100.918,177.5,1581.9,5.5,71.7826,2.1333,136225.6667,216111.6667 +20021,10783.5,13394.91,13642.0267,4655.831,1912.659,1.3831,100.951,178.0667,1585.6333,5.7,72.1619,1.7333,136105.3333,216664.0 +20022,10887.46,13477.356,13731.5275,4721.022,1933.282,1.3657,101.335,179.4667,1609.5333,5.8333,73.0831,1.75,136360.0,217203.6667 +20023,10984.04,13531.741,13819.4217,4778.693,1933.197,1.3504,101.12,180.4333,1616.7333,5.7333,73.6318,1.74,136806.6667,217867.6667 +20024,11061.433,13549.421,13906.0731,4844.998,1942.531,1.3403,101.111,181.5,1641.8667,5.8667,73.5331,1.4433,136651.6667,218543.0 +20031,11174.129,13619.434,13992.4845,4909.826,1960.221,1.3208,100.663,183.3667,1682.8,5.8667,73.9181,1.25,137444.3333,220109.3333 +20032,11312.766,13741.107,14079.7166,4983.56,1972.386,1.3091,100.642,183.0667,1670.0667,6.1333,73.5049,1.2467,137655.6667,220774.0 +20033,11566.669,13970.157,14166.9684,5054.68,2044.304,1.2912,100.548,184.4333,1724.5,6.1333,74.0054,1.0167,137544.0,221512.6667 +20034,11772.234,14131.379,14255.2957,5124.541,2131.311,1.2823,100.712,185.1333,1741.8667,5.8333,74.8627,0.9967,138273.0,222275.6667 +20041,11923.447,14212.34,14345.4715,5205.543,2154.052,1.2769,100.907,186.7,1779.0,5.7,75.4283,1.0033,138489.0,222356.0 +20042,12112.815,14323.017,14436.9677,5278.129,2262.607,1.2741,100.404,188.1667,1803.0,5.6,76.1141,1.01,138902.0,222973.3333 +20043,12305.307,14457.832,14530.6572,5371.996,2318.272,1.2678,100.51,189.3667,1826.3,5.4333,76.8515,1.4333,139538.6667,223680.0 +20044,12527.214,14605.595,14625.5401,5464.088,2390.082,1.2626,100.516,191.4,1877.4667,5.4333,77.734,1.95,140029.3333,224418.0 +20051,12767.286,14767.846,14718.9072,5549.788,2486.068,1.2596,100.284,192.3667,1892.0,5.3,78.6482,2.47,140428.0,225038.0 +20052,12922.656,14839.707,14809.9664,5643.732,2476.474,1.2558,100.453,193.6667,1919.4667,5.1,78.6991,2.9433,141525.6667,225674.0 +20053,13142.642,14956.291,14899.7647,5730.245,2531.076,1.2438,100.385,196.6,1986.0333,4.9667,78.1064,3.46,142287.0,226422.3333 +20054,13324.204,15041.232,14987.7347,5820.809,2645.263,1.239,100.561,198.4333,2019.7333,4.9667,78.8068,3.98,142599.6667,227196.0 +20061,13599.16,15244.088,15073.6648,5903.311,2709.74,1.2356,100.672,199.4667,2042.7,4.7333,79.1551,4.4567,143449.3333,227763.6667 +20062,13753.424,15281.525,15158.0237,6001.556,2709.252,1.2243,100.662,201.2667,2076.8333,4.6333,78.9365,4.9067,144067.6667,228432.6667 +20063,13870.188,15304.517,15239.1033,6080.335,2709.42,1.2144,100.883,203.1667,2112.9,4.6333,78.7101,5.2467,144547.3333,229166.3333 +20064,14039.56,15433.643,15316.9142,6165.119,2675.406,1.2177,100.816,202.3333,2092.8333,4.4333,78.482,5.2467,145606.0,229896.0 +20071,14215.651,15478.956,15394.5913,6264.013,2664.295,1.2045,100.578,204.317,2129.1,4.5,78.7188,5.2567,146135.0,230839.3333 +20072,14402.082,15577.779,15472.7721,6326.913,2699.217,1.1889,100.714,206.631,2166.1,4.5,79.1876,5.25,145850.6667,231482.0 +20073,14564.117,15671.605,15551.6854,6418.373,2685.969,1.1752,100.556,207.939,2188.1667,4.6667,78.697,5.0733,145943.6667,232210.0 +20074,14715.058,15767.146,15630.1644,6508.952,2642.56,1.1572,100.263,210.4897,2232.6,4.8,78.5673,4.4967,146271.3333,232936.6667 +20081,14706.538,15702.906,15707.8964,6598.077,2563.701,1.1446,100.231,212.7697,2252.8667,5.0,77.977,3.1767,146206.6667,232806.6667 +20082,14865.701,15792.773,15782.9451,6686.336,2540.595,1.1276,100.055,215.5377,2305.8,5.3333,76.461,2.0867,145925.6667,233410.0 +20083,14898.999,15709.562,15854.4792,6731.171,2498.242,1.1151,99.733,218.861,2332.0667,6.0,73.9482,1.94,145270.3333,234110.3333 +20084,14608.208,15366.607,15921.6471,6731.865,2307.915,1.1368,99.202,213.8487,2167.0667,6.8667,69.7976,0.5067,144090.3333,234825.0 +20091,14430.901,15187.475,15983.2985,6694.942,2014.878,1.1346,98.628,212.3777,2115.5667,8.2667,65.2926,0.1833,141499.6667,234912.6667 +20092,14381.236,15161.772,16040.7026,6671.028,1863.65,1.1159,98.087,213.507,2139.8,9.3,63.7623,0.18,140304.3333,235459.3333 +20093,14448.882,15216.647,16094.7417,6710.929,1841.416,1.0937,97.982,215.344,2193.3667,9.6333,65.3514,0.1567,139403.6667,236093.0 +20094,14651.248,15379.155,16147.0634,6767.885,1998.71,1.0825,98.327,217.03,2222.9,9.9333,66.7467,0.12,138368.0,236739.0 +20101,14764.611,15456.059,16199.7102,6835.669,2038.161,1.07,98.612,217.374,2245.1,9.8333,68.1434,0.1333,138590.0,236996.3333 +20102,14980.193,15605.628,16253.7162,6916.911,2148.795,1.0618,99.246,217.2973,2247.3333,9.6333,70.2245,0.1933,139226.3333,237506.0 +20103,15141.605,15726.282,16310.4477,6985.635,2236.495,1.0545,99.702,217.9343,2262.7,9.4667,71.3347,0.1867,139337.6667,238103.6667 +20104,15309.471,15807.995,16369.3052,7031.511,2238.44,1.0464,99.697,219.699,2320.3667,9.5,71.942,0.1867,139154.6667,238711.3333 +20111,15351.444,15769.911,16430.4648,7089.16,2205.962,1.0374,99.364,222.0437,2381.1,9.0333,72.7261,0.1567,139427.6667,238851.6667 +20112,15557.535,15876.839,16494.1242,7158.561,2297.352,1.0291,99.808,224.5683,2431.5667,9.0667,72.8293,0.0933,139531.3333,239316.0 +20113,15647.681,15870.684,16560.1095,7227.254,2322.84,1.0225,99.776,226.0327,2437.0,9.0,73.5921,0.0833,139883.0,239871.0 +20114,15842.267,16048.702,16627.8917,7247.97,2504.095,1.0162,100.008,227.0473,2448.8,8.6333,74.1353,0.0733,140698.6667,240431.3333 +20121,16068.824,16179.968,16697.3907,7330.585,2567.75,1.009,100.136,228.326,2490.6,8.2667,74.8661,0.1033,141826.0,242436.0 +20122,16207.13,16253.726,16768.9712,7388.679,2636.863,1.0034,99.984,228.808,2482.8333,8.2,74.717,0.1533,142165.3333,242968.3333 +20123,16319.54,16282.151,16842.1603,7427.587,2644.119,0.9982,99.903,229.841,2490.1333,8.0333,74.228,0.1433,142542.3333,243564.0 +20124,16420.386,16300.035,16916.6539,7491.645,2638.282,0.9896,99.976,231.3693,2510.5,7.8,74.2101,0.16,143364.6667,244169.0 +20131,16629.05,16441.485,16992.5715,7533.987,2738.236,0.9826,99.972,232.2993,2542.8333,7.7333,74.5227,0.1433,143323.3333,244828.6667 +20132,16699.551,16464.402,17069.5661,7591.64,2775.276,0.9794,99.857,232.045,2513.8667,7.5333,74.4512,0.1167,143838.6667,245363.3333 +20133,16911.068,16594.743,17147.1508,7648.836,2879.995,0.9734,99.814,233.3,2541.4,7.2333,74.456,0.0833,144336.0,245961.0 +20134,17133.114,16712.76,17225.8024,7759.758,2910.546,0.9689,99.818,234.1627,2564.1333,6.9333,74.7773,0.0867,144264.6667,246564.3333 +20141,17144.281,16654.247,17305.4195,7828.587,2899.216,0.9635,99.868,235.621,2586.0,6.6667,74.6624,0.0733,145310.6667,247086.0 +20142,17462.703,16868.109,17385.7105,7921.739,3030.425,0.9556,100.047,236.8723,2623.5333,6.2,75.6349,0.0933,145913.0,247625.0 +20143,17743.227,17064.616,17467.5492,8036.39,3107.62,0.9523,100.093,237.4783,2642.2333,6.0667,76.1195,0.09,146569.0,248232.6667 +20144,17852.54,17141.235,17550.0148,8152.359,3139.452,0.9498,100.377,236.8883,2631.7333,5.7,76.3817,0.1,147482.0,248842.6667 +20151,17991.348,17280.647,17632.7606,8215.995,3245.05,0.9506,100.192,235.355,2584.1,5.5333,75.989,0.11,148106.0,249900.6667 +20152,18193.707,17380.875,17715.6013,8297.35,3245.825,0.9424,99.922,236.96,2617.7333,5.4333,76.0501,0.1233,148714.6667,250461.3333 +20153,18306.96,17437.08,17797.9113,8387.315,3251.67,0.9353,99.95,237.855,2641.9333,5.1,76.2634,0.1367,148945.6667,251099.0 +20154,18332.079,17462.579,17879.075,8461.274,3206.098,0.9306,99.928,237.837,2617.8,5.0333,75.6876,0.16,149612.3333,251741.3333 +20161,18425.306,17565.465,17959.5247,8560.649,3174.386,0.9237,99.821,237.6893,2605.7333,4.9,75.5328,0.36,150936.6667,252580.6667 +20162,18611.617,17618.581,18039.0586,8652.684,3178.537,0.9158,99.648,239.5903,2648.7667,4.9333,75.158,0.3733,151143.0,253180.0 +20163,18775.459,17724.489,18117.6492,8750.7,3185.73,0.9071,99.656,240.6073,2653.6333,4.9,75.0849,0.3967,151698.0,253855.0 +20164,18968.041,17812.56,18195.8769,8841.634,3281.493,0.9008,99.394,242.1347,2678.4667,4.7667,75.128,0.45,151968.0,254534.3333 +20171,19153.912,17896.623,18274.5328,8951.04,3283.481,0.8961,99.37,243.8387,2724.7333,4.5667,75.3508,0.7,152565.6667,254247.3333 +20172,19322.92,17996.802,18352.9509,9028.583,3357.362,0.8928,99.789,244.12,2738.8333,4.3667,76.2176,0.95,153214.0,254770.6667 +20173,19558.693,18126.226,18433.5035,9108.266,3413.3,0.8872,99.541,245.287,2762.1667,4.3333,76.1702,1.1533,153786.0,255356.6667 +20174,19882.965,18296.685,18516.0761,9234.339,3471.425,0.8788,99.649,247.2383,2821.9667,4.1667,77.0926,1.2033,153772.6667,255941.3333 +20181,20143.716,18436.262,18600.5425,9369.007,3550.839,0.8733,99.659,249.2543,2849.0667,4.0333,77.3251,1.4467,155018.3333,256937.0 +20182,20492.492,18590.004,18687.3221,9510.327,3603.22,0.8692,99.795,250.681,2883.2,3.9333,77.837,1.7367,155596.6667,257456.0 +20183,20659.102,18679.599,18776.0991,9629.389,3679.569,0.8655,99.775,251.7703,2894.9667,3.7667,78.1066,1.9233,155865.6667,258066.3333 +20184,20813.325,18721.281,18865.3729,9730.536,3717.518,0.8608,99.581,252.69,2911.0,3.8333,77.4994,2.22,156555.6667,258703.3333 +20191,21001.591,18833.195,18954.7646,9772.746,3801.932,0.8611,99.34,253.2927,2909.5,3.8667,76.523,2.4033,156825.0,258389.3333 +20192,21289.268,18982.528,19044.8815,9896.347,3842.963,0.855,99.188,255.283,2970.1667,3.6,75.7752,2.3967,156911.6667,258863.6667 +20193,21505.012,19112.653,19135.4193,10016.772,3858.192,0.8499,99.112,256.225,2981.2667,3.6333,75.6762,2.19,157839.0,259431.6667 +20194,21694.458,19202.31,19225.5991,10113.165,3801.943,0.8418,99.076,257.7853,3001.6,3.6,75.3778,1.6433,158544.0,260015.3333 diff --git a/lectures/_static/lecture_specific/subjective_beliefs_business_cycles/bbh_michigan_monthly.csv b/lectures/_static/lecture_specific/subjective_beliefs_business_cycles/bbh_michigan_monthly.csv new file mode 100644 index 00000000..b926586b --- /dev/null +++ b/lectures/_static/lecture_specific/subjective_beliefs_business_cycles/bbh_michigan_monthly.csv @@ -0,0 +1,508 @@ +yyyymm,px1_mean,share_more,share_same,share_less,unrate +197801,6.1,30,48,20,6.4 +197802,8.5,24,41,30,6.3 +197803,7.5,31,52,14,6.3 +197804,8.0,25,56,17,6.1 +197805,8.9,26,45,23,6.0 +197806,8.0,39,51,8,5.9 +197807,7.6,32,53,12,6.2 +197808,10.5,33,48,16,5.9 +197809,8.5,31,52,15,6.0 +197810,7.9,30,55,12,5.8 +197811,9.6,35,44,16,5.9 +197812,8.3,46,42,7,6.0 +197901,9.7,41,45,10,5.9 +197902,11.4,34,50,14,5.9 +197903,10.0,41,46,9,5.8 +197904,11.1,45,40,11,5.8 +197905,12.0,40,46,9,5.6 +197906,12.0,52,39,6,5.7 +197907,11.5,62,30,6,5.7 +197908,11.2,62,30,7,6.0 +197909,10.4,55,36,7,5.9 +197910,9.5,55,36,7,6.0 +197911,11.9,54,36,8,5.9 +197912,11.7,61,30,8,6.0 +198001,13.8,54,34,9,6.3 +198002,11.2,46,38,13,6.3 +198003,12.7,57,35,7,6.3 +198004,12.0,62,29,7,6.9 +198005,9.5,72,23,5,7.5 +198006,9.0,63,25,11,7.6 +198007,10.0,59,30,11,7.8 +198008,8.8,40,38,19,7.7 +198009,9.3,40,36,22,7.5 +198010,10.0,30,46,21,7.5 +198011,9.8,24,47,26,7.5 +198012,10.9,39,41,16,7.2 +198101,8.6,38,41,19,7.5 +198102,9.1,40,42,16,7.4 +198103,7.9,41,43,13,7.4 +198104,9.3,40,41,17,7.2 +198105,8.6,36,46,17,7.5 +198106,7.8,36,44,18,7.5 +198107,7.7,36,44,18,7.2 +198108,7.7,36,47,14,7.4 +198109,8.7,39,45,15,7.6 +198110,8.3,50,38,11,7.9 +198111,8.8,54,33,11,8.3 +198112,6.5,59,28,11,8.5 +198201,6.4,49,34,16,8.6 +198202,6.4,52,33,13,8.9 +198203,5.8,54,32,13,9.0 +198204,5.1,50,34,14,9.3 +198205,4.8,47,35,17,9.4 +198206,5.8,48,35,16,9.6 +198207,5.9,48,35,16,9.8 +198208,6.3,46,35,17,9.8 +198209,5.3,43,38,18,10.1 +198210,5.9,41,36,21,10.4 +198211,5.2,37,42,19,10.8 +198212,4.8,37,40,22,10.8 +198301,4.7,30,45,23,10.4 +198302,4.5,29,41,29,10.4 +198303,3.1,21,43,36,10.3 +198304,4.5,15,41,42,10.2 +198305,4.5,13,44,42,10.1 +198306,4.6,15,47,37,10.1 +198307,4.5,16,44,39,9.4 +198308,5.0,15,40,42,9.5 +198309,5.0,17,46,36,9.2 +198310,5.2,20,44,35,8.8 +198311,5.1,16,47,35,8.5 +198312,4.9,17,45,36,8.3 +198401,4.6,14,48,36,8.0 +198402,5.1,16,50,31,7.8 +198403,5.0,16,45,38,7.8 +198404,5.8,18,52,28,7.7 +198405,5.3,20,51,29,7.4 +198406,4.8,23,52,24,7.2 +198407,4.5,19,53,26,7.5 +198408,4.5,21,54,24,7.5 +198409,4.2,22,50,26,7.3 +198410,5.3,24,54,20,7.4 +198411,5.1,24,52,22,7.2 +198412,4.7,28,50,21,7.3 +198501,4.0,25,54,20,7.3 +198502,4.8,28,50,20,7.2 +198503,4.3,29,47,23,7.2 +198504,5.0,28,50,20,7.3 +198505,4.6,28,52,19,7.2 +198506,4.9,25,58,16,7.4 +198507,4.2,27,58,14,7.4 +198508,3.6,29,52,17,7.1 +198509,4.4,31,53,14,7.1 +198510,4.8,32,51,16,7.1 +198511,4.4,29,54,15,7.0 +198512,5.0,30,54,15,7.0 +198601,4.2,30,54,15,6.7 +198602,3.7,29,51,19,7.2 +198603,3.0,33,48,17,7.2 +198604,3.4,32,51,15,7.1 +198605,3.3,31,54,14,7.2 +198606,3.7,26,53,20,7.2 +198607,3.6,27,56,15,7.0 +198608,4.2,32,54,13,6.9 +198609,3.4,32,54,11,7.0 +198610,3.7,33,55,10,7.0 +198611,3.6,34,54,11,6.9 +198612,4.0,42,46,11,6.6 +198701,3.9,38,49,12,6.6 +198702,4.1,33,52,14,6.6 +198703,3.6,35,48,15,6.6 +198704,3.7,33,52,13,6.3 +198705,4.7,32,53,13,6.3 +198706,4.4,30,52,16,6.2 +198707,4.2,30,55,14,6.1 +198708,4.4,27,56,15,6.0 +198709,4.2,29,57,13,5.9 +198710,4.3,29,52,17,6.0 +198711,3.8,36,53,10,5.8 +198712,3.9,34,51,12,5.7 +198801,4.3,33,54,12,5.7 +198802,4.4,33,53,12,5.7 +198803,4.0,28,58,12,5.7 +198804,4.5,28,56,14,5.4 +198805,4.8,21,67,11,5.6 +198806,5.6,31,53,15,5.4 +198807,5.6,29,54,15,5.4 +198808,5.3,27,53,15,5.6 +198809,5.5,27,52,17,5.4 +198810,5.1,22,60,16,5.4 +198811,5.4,27,57,14,5.3 +198812,5.0,28,55,15,5.3 +198901,5.1,34,50,15,5.4 +198902,4.8,30,55,13,5.2 +198903,5.6,33,53,12,5.0 +198904,5.0,29,55,14,5.2 +198905,5.8,35,52,12,5.2 +198906,5.0,29,59,10,5.3 +198907,5.1,31,57,11,5.2 +198908,4.7,35,54,10,5.2 +198909,4.4,26,61,11,5.3 +198910,4.6,30,55,14,5.3 +198911,4.7,35,54,10,5.4 +198912,4.4,37,51,11,5.4 +199001,5.3,36,52,10,5.4 +199002,5.1,44,48,7,5.3 +199003,4.9,34,55,10,5.2 +199004,4.4,36,52,11,5.4 +199005,4.6,37,52,10,5.4 +199006,4.9,39,52,8,5.2 +199007,4.6,37,54,7,5.5 +199008,6.1,46,44,9,5.7 +199009,5.8,44,45,9,5.9 +199010,6.0,62,30,6,5.9 +199011,5.5,64,30,5,6.2 +199012,5.3,60,34,6,6.3 +199101,4.8,62,29,8,6.4 +199102,4.6,56,31,12,6.6 +199103,4.4,36,46,17,6.8 +199104,4.1,45,39,15,6.7 +199105,4.4,43,44,12,6.9 +199106,4.7,36,48,14,6.9 +199107,3.7,39,48,13,6.8 +199108,4.2,40,45,13,6.9 +199109,3.6,38,50,12,6.9 +199110,4.6,48,39,11,7.0 +199111,4.5,46,44,9,7.0 +199112,3.6,52,37,9,7.3 +199201,3.1,57,33,9,7.3 +199202,3.3,56,30,12,7.4 +199203,3.0,43,42,14,7.4 +199204,3.5,42,40,16,7.4 +199205,3.2,36,45,18,7.6 +199206,4.0,37,48,14,7.8 +199207,3.8,45,40,13,7.7 +199208,3.9,38,46,15,7.6 +199209,4.0,42,43,15,7.6 +199210,3.5,41,41,16,7.3 +199211,4.4,28,52,19,7.4 +199212,3.2,26,46,27,7.4 +199301,3.4,25,46,27,7.3 +199302,4.5,32,44,22,7.1 +199303,4.9,37,43,20,7.0 +199304,4.0,33,48,18,7.1 +199305,4.3,38,48,13,7.1 +199306,4.8,40,46,13,7.0 +199307,4.4,42,44,12,6.9 +199308,4.7,42,43,13,6.8 +199309,4.7,45,42,12,6.7 +199310,3.9,46,44,9,6.8 +199311,3.5,42,45,11,6.6 +199312,3.7,33,47,18,6.5 +199401,3.4,25,51,21,6.6 +199402,3.7,32,51,17,6.6 +199403,4.4,32,47,20,6.5 +199404,4.5,32,51,16,6.4 +199405,3.9,32,51,16,6.1 +199406,4.0,30,51,16,6.1 +199407,4.2,29,52,18,6.1 +199408,4.6,34,51,13,6.0 +199409,4.7,34,46,17,5.9 +199410,3.8,30,56,14,5.8 +199411,4.5,31,50,18,5.6 +199412,4.0,30,47,22,5.5 +199501,3.7,28,54,16,5.6 +199502,4.0,32,53,13,5.4 +199503,4.6,35,49,15,5.4 +199504,4.3,34,51,14,5.8 +199505,3.9,30,57,12,5.6 +199506,3.9,32,55,12,5.6 +199507,3.8,38,52,9,5.7 +199508,3.9,34,55,11,5.7 +199509,4.0,37,52,11,5.6 +199510,3.5,43,46,10,5.5 +199511,3.7,35,53,11,5.6 +199512,3.2,35,51,13,5.6 +199601,4.0,46,43,11,5.6 +199602,3.6,40,50,9,5.5 +199603,4.1,35,52,12,5.5 +199604,4.5,35,53,11,5.6 +199605,4.8,33,54,12,5.6 +199606,4.1,35,52,12,5.3 +199607,4.2,30,54,15,5.5 +199608,4.1,30,53,16,5.1 +199609,4.3,29,53,17,5.2 +199610,4.1,28,57,14,5.2 +199611,3.9,26,57,16,5.4 +199612,3.9,24,60,14,5.4 +199701,4.1,31,51,17,5.3 +199702,3.8,25,56,18,5.2 +199703,3.5,27,57,15,5.2 +199704,3.7,28,55,15,5.1 +199705,3.7,25,57,16,4.9 +199706,3.5,22,62,14,5.0 +199707,3.4,23,57,19,4.9 +199708,3.3,23,59,16,4.8 +199709,3.5,22,57,19,4.9 +199710,3.2,20,57,20,4.7 +199711,3.4,23,61,15,4.6 +199712,3.4,32,49,18,4.7 +199801,2.8,24,55,18,4.6 +199802,2.6,19,59,20,4.6 +199803,2.9,20,58,20,4.7 +199804,2.7,16,63,19,4.3 +199805,3.1,21,60,19,4.4 +199806,3.2,26,51,21,4.5 +199807,3.1,26,58,15,4.5 +199808,2.7,26,56,17,4.5 +199809,2.7,26,59,14,4.6 +199810,2.6,33,53,12,4.5 +199811,2.7,34,50,15,4.4 +199812,2.8,36,50,12,4.4 +199901,3.0,28,58,13,4.3 +199902,2.8,23,59,15,4.4 +199903,3.1,26,60,13,4.2 +199904,3.0,23,62,14,4.3 +199905,3.2,24,61,14,4.2 +199906,3.1,19,62,17,4.3 +199907,3.0,22,64,13,4.3 +199908,3.2,27,57,14,4.2 +199909,3.2,24,61,14,4.2 +199910,3.5,25,61,13,4.1 +199911,3.3,26,56,15,4.1 +199912,3.6,21,61,16,4.0 +200001,3.5,21,61,17,4.0 +200002,3.5,21,62,15,4.1 +200003,3.8,22,62,13,4.0 +200004,3.5,22,62,13,3.8 +200005,3.5,20,64,16,4.0 +200006,3.4,25,59,13,4.0 +200007,3.7,24,60,13,4.0 +200008,3.5,23,59,15,4.1 +200009,3.7,23,61,12,3.9 +200010,4.1,29,57,12,3.9 +200011,3.8,27,57,13,3.9 +200012,3.4,39,47,12,3.9 +200101,3.8,47,42,8,4.2 +200102,3.2,53,36,10,4.2 +200103,3.3,50,42,7,4.3 +200104,3.7,53,39,7,4.4 +200105,3.9,47,42,9,4.3 +200106,4.0,45,44,10,4.5 +200107,3.0,49,41,9,4.6 +200108,3.1,52,37,10,4.9 +200109,3.2,60,29,9,5.0 +200110,1.6,59,30,9,5.3 +200111,1.0,56,30,13,5.5 +200112,1.9,48,36,13,5.7 +200201,2.2,40,40,17,5.7 +200202,2.4,42,40,18,5.7 +200203,3.1,34,42,22,5.7 +200204,3.1,32,44,23,5.9 +200205,3.1,30,49,19,5.8 +200206,3.0,31,51,16,5.8 +200207,2.7,44,43,12,5.8 +200208,2.6,42,44,13,5.7 +200209,3.1,36,47,16,5.7 +200210,2.9,44,40,13,5.7 +200211,2.5,37,47,14,5.9 +200212,2.7,43,40,16,6.0 +200301,2.7,43,44,12,5.8 +200302,3.2,44,41,14,5.9 +200303,3.8,44,41,14,5.9 +200304,2.7,37,45,17,6.0 +200305,2.5,29,47,22,6.1 +200306,2.5,35,44,20,6.3 +200307,2.3,34,46,18,6.2 +200308,2.8,32,47,21,6.1 +200309,3.4,33,48,19,6.1 +200310,3.1,37,43,20,6.0 +200311,3.1,31,42,26,5.8 +200312,2.8,26,45,28,5.7 +200401,2.9,24,47,29,5.7 +200402,2.9,28,49,23,5.6 +200403,3.4,31,45,23,5.8 +200404,4.0,29,48,22,5.6 +200405,3.9,30,47,23,5.6 +200406,4.0,23,48,27,5.6 +200407,3.5,22,47,29,5.5 +200408,3.1,28,49,22,5.4 +200409,3.2,28,48,23,5.4 +200410,3.6,26,52,21,5.5 +200411,3.3,28,49,22,5.4 +200412,3.4,26,51,23,5.4 +200501,3.5,31,49,20,5.3 +200502,3.3,29,53,18,5.4 +200503,4.0,29,54,17,5.2 +200504,4.0,35,48,17,5.2 +200505,3.8,37,45,18,5.1 +200506,4.0,32,52,16,5.0 +200507,3.6,30,56,13,5.0 +200508,3.7,36,51,13,4.9 +200509,5.5,48,41,11,5.0 +200510,5.5,48,37,14,5.0 +200511,4.1,36,50,14,5.0 +200512,4.1,41,44,15,4.9 +200601,3.8,38,50,11,4.7 +200602,3.6,40,47,12,4.8 +200603,3.8,42,44,13,4.7 +200604,4.4,41,46,12,4.7 +200605,4.7,40,46,13,4.6 +200606,4.4,38,50,11,4.6 +200607,3.8,39,51,10,4.7 +200608,4.6,41,47,11,4.7 +200609,3.6,39,47,14,4.5 +200610,3.7,31,59,9,4.4 +200611,3.3,31,59,10,4.5 +200612,3.5,34,53,13,4.4 +200701,3.6,27,60,12,4.6 +200702,3.6,33,58,8,4.5 +200703,3.6,33,57,10,4.4 +200704,4.0,38,53,9,4.5 +200705,4.3,30,59,10,4.4 +200706,4.2,37,51,12,4.6 +200707,4.2,33,56,10,4.7 +200708,4.0,39,52,8,4.6 +200709,4.0,36,54,9,4.7 +200710,3.7,38,54,8,4.7 +200711,4.3,39,50,11,4.7 +200712,4.4,47,45,8,5.0 +200801,4.0,47,46,6,5.0 +200802,3.9,50,41,9,4.9 +200803,4.6,55,38,7,5.1 +200804,5.7,59,36,5,5.0 +200805,7.0,56,41,3,5.4 +200806,6.5,64,31,5,5.6 +200807,6.3,61,32,7,5.8 +200808,5.3,55,40,5,6.1 +200809,4.6,50,41,9,6.1 +200810,4.3,62,31,6,6.5 +200811,2.9,69,24,7,6.8 +200812,1.7,69,24,7,7.3 +200901,2.5,66,21,12,7.8 +200902,2.3,69,20,10,8.3 +200903,2.4,64,27,9,8.7 +200904,3.1,56,31,12,9.0 +200905,3.2,46,40,14,9.4 +200906,3.9,48,37,15,9.5 +200907,3.6,50,36,14,9.5 +200908,3.0,39,45,15,9.6 +200909,2.8,30,49,20,9.8 +200910,3.2,36,48,16,10.0 +200911,3.1,40,43,16,9.9 +200912,3.0,34,44,22,9.9 +201001,3.4,30,50,19,9.8 +201002,3.6,27,51,21,9.8 +201003,3.4,29,53,17,9.9 +201004,3.8,29,46,24,9.9 +201005,4.1,26,53,21,9.6 +201006,3.3,26,51,22,9.4 +201007,3.3,31,48,20,9.4 +201008,3.2,31,50,19,9.5 +201009,3.0,30,53,16,9.5 +201010,3.3,30,51,17,9.4 +201011,3.7,28,51,20,9.8 +201012,3.9,25,54,20,9.3 +201101,4.2,22,56,21,9.1 +201102,4.4,22,48,28,9.0 +201103,5.2,27,50,22,9.0 +201104,5.3,29,49,21,9.1 +201105,4.8,22,52,25,9.0 +201106,4.5,26,53,21,9.1 +201107,4.4,31,55,14,9.0 +201108,4.4,43,45,12,9.0 +201109,4.3,35,54,10,9.0 +201110,4.0,34,53,12,8.8 +201111,4.0,26,58,16,8.6 +201112,3.7,29,52,18,8.5 +201201,4.0,25,52,23,8.3 +201202,4.1,25,46,29,8.3 +201203,4.7,19,51,29,8.2 +201204,3.8,22,53,24,8.2 +201205,3.6,22,50,27,8.2 +201206,3.7,27,49,23,8.2 +201207,3.9,26,52,21,8.2 +201208,4.3,25,53,21,8.1 +201209,4.3,20,53,27,7.8 +201210,4.2,19,49,30,7.8 +201211,4.0,24,42,30,7.7 +201212,4.0,35,40,25,7.9 +201301,4.5,31,47,22,8.0 +201302,4.4,27,48,23,7.7 +201303,4.0,31,43,25,7.5 +201304,4.0,29,47,23,7.6 +201305,4.2,26,48,25,7.5 +201306,3.8,23,52,24,7.5 +201307,4.1,23,56,21,7.3 +201308,4.2,28,45,26,7.2 +201309,4.1,32,45,22,7.2 +201310,3.6,30,50,19,7.2 +201311,3.7,35,44,21,6.9 +201312,3.8,26,52,22,6.7 +201401,4.0,29,46,23,6.6 +201402,4.3,34,47,18,6.7 +201403,4.2,30,54,15,6.7 +201404,4.0,26,53,21,6.2 +201405,4.4,23,48,28,6.3 +201406,3.7,24,53,22,6.1 +201407,4.0,28,48,22,6.2 +201408,4.0,26,52,21,6.1 +201409,3.8,29,49,21,5.9 +201410,3.5,22,52,25,5.7 +201411,3.0,19,53,28,5.8 +201412,3.0,21,52,27,5.6 +201501,2.7,19,52,29,5.7 +201502,3.1,24,45,30,5.5 +201503,3.5,21,51,27,5.4 +201504,3.2,20,51,28,5.4 +201505,3.4,22,53,24,5.6 +201506,3.5,20,54,25,5.3 +201507,3.7,25,53,21,5.2 +201508,3.5,25,51,24,5.1 +201509,3.1,27,52,19,5.0 +201510,3.5,27,52,20,5.0 +201511,3.2,21,55,22,5.1 +201512,3.0,25,53,21,5.0 +201601,3.1,29,52,18,4.8 +201602,2.9,26,54,19,4.9 +201603,3.3,26,54,20,5.0 +201604,3.3,31,52,16,5.1 +201605,3.0,24,54,21,4.8 +201606,3.1,28,54,18,4.9 +201607,3.2,31,51,17,4.8 +201608,3.0,27,51,20,4.9 +201609,3.0,26,52,20,5.0 +201610,3.0,27,51,20,4.9 +201611,3.0,26,50,23,4.7 +201612,2.8,22,48,28,4.7 +201701,3.1,22,43,33,4.7 +201702,3.3,26,38,35,4.6 +201703,3.2,25,37,36,4.4 +201704,2.9,23,41,36,4.4 +201705,3.0,25,44,30,4.4 +201706,3.3,27,43,30,4.3 +201707,3.0,27,48,24,4.3 +201708,3.1,25,46,29,4.4 +201709,3.3,25,47,28,4.3 +201710,3.0,23,47,29,4.2 +201711,2.9,22,49,29,4.2 +201712,3.2,25,46,29,4.1 +201801,3.0,26,43,31,4.0 +201802,3.1,23,42,35,4.1 +201803,3.3,22,45,32,4.0 +201804,3.3,25,46,28,4.0 +201805,3.3,24,51,24,3.8 +201806,3.7,22,47,30,4.0 +201807,3.7,26,44,29,3.8 +201808,3.7,25,45,30,3.8 +201809,3.3,20,50,29,3.7 +201810,3.7,23,46,29,3.8 +201811,3.3,22,50,27,3.8 +201812,3.3,30,47,22,3.9 +201901,2.9,33,47,19,4.0 +201902,3.1,31,46,23,3.8 +201903,2.9,22,52,25,3.8 +201904,3.1,24,49,27,3.6 +201905,3.4,22,53,25,3.6 +201906,3.5,27,49,24,3.6 +201907,3.2,25,51,23,3.7 +201908,3.4,30,51,19,3.7 +201909,3.3,31,48,20,3.5 +201910,3.0,32,48,20,3.6 +201911,3.1,23,53,23,3.6 +201912,2.8,31,46,23,3.6 +202001,2.9,21,53,26,3.5 +202002,2.8,23,56,21,3.5 +202003,2.5,39,39,21,4.4 diff --git a/lectures/subjective_beliefs_business_cycles.md b/lectures/subjective_beliefs_business_cycles.md index 3d0d1537..cd5d6452 100644 --- a/lectures/subjective_beliefs_business_cycles.md +++ b/lectures/subjective_beliefs_business_cycles.md @@ -34,14 +34,16 @@ These biases, called *belief wedges*, are: * **One-factor in structure**: a single latent state accounts for most variation across wedges. -We interpret this evidence using +We follow {cite:t}`bhandari2025survey` in interpreting this evidence using **robust preferences** ({cite:t}`HansenSargent2001`; {cite:t}`HansenSargent2008`). -Robust preferences provide model-consistent notions of pessimism and optimism: +Robust preferences provide a natural way to model pessimism and optimism: a pessimistic agent acts as if states that deliver low continuation values are more likely than they really are, while an optimistic agent overweights states that deliver high continuation values. +This is done through the distortion studied in {doc}`five_preferences`. + When calibrated to the Michigan Survey of Consumers (1982Q1-2019Q4), this mechanism yields a time-varying *belief shock* that substantially reduces the **unemployment volatility puzzle** --- @@ -51,32 +53,12 @@ policy shocks generate far too little unemployment volatility. In this lecture, we will cover: * How to define and measure belief wedges from household survey data. -* Why optimal pessimism is a mean shift of the shock distribution, with - size equal to $\theta_t$ times the shock's exposure to continuation values. +* Why optimal pessimism is a mean shift of the shock distribution, + proportional to the shock's exposure to continuation values. * How belief distortions propagate through a linearized DSGE model. * Why a calibrated belief shock helps resolve the unemployment volatility puzzle. -The lecture is self-contained in the following sense. - -All empirical moments, calibration values, and model objects used in the code -are reported below, so no external data files are needed. - -Benchmark numbers from {cite:t}`bhandari2025survey` are quoted for comparison, -but every computation below is generated from the equations and parameter -values stated here. - -Some notation and units will be used throughout: - -* `pp` means percentage points. -* Inflation is reported at an annualized rate when explicitly marked - "ann."; otherwise it is a quarterly rate. -* $u_t$, $\pi_t$, and $y_t$ denote unemployment, inflation, and the output gap. -* $a_t$ is total factor productivity (TFP), and $w_t^r$ is a monetary-policy - shock. -* $\theta_t$ is the belief-shock or pessimism state: larger $\theta_t$ means - agents put more subjective probability on low-continuation-value states. - We start with the following imports ```{code-cell} ipython3 @@ -84,9 +66,11 @@ import datetime from typing import NamedTuple import numpy as np +import pandas as pd import matplotlib.pyplot as plt import matplotlib.dates as mdates from scipy.linalg import solve_discrete_lyapunov +from scipy.stats import norm ``` ## Measuring belief wedges @@ -141,138 +125,201 @@ The raw Michigan unemployment question is categorical, so {cite:t}`bhandari2025s convert it into a quantitative forecast using the Carlson--Parkin procedure as adapted by {cite:t}`MankiwReisWolfers2003`. -We do not need those raw survey responses below. +### Replicating the wedges -Instead, the empirical section works with the already-constructed wedge -moments listed next. +{cite:t}`bhandari2025survey` document the properties of the belief wedges on +the sample 1982Q1--2019Q4, and we now replicate that evidence. -### Empirical facts +The raw inputs are extracted from the paper's publicly +available replication package +([doi:10.5281/zenodo.10194324](https://doi.org/10.5281/zenodo.10194324), +licensed CC-BY-4.0). -Using data from 1982Q1 to 2019Q4, {cite:t}`bhandari2025survey` document: +The first file contains the quarterly macroeconomic series for the +forecasting VAR. -| Statistic | Unemployment wedge | Inflation wedge | -|---|---|---| -| Mean | 0.52 pp | 1.22 pp | -| Standard deviation | 0.57 pp | 0.97 pp | -| Correlation with output gap | −0.49 | −0.30 | +(Monthly series are averaged to quarterly frequency.) -Both wedges are **positive on average**: households expect higher unemployment -and higher inflation than the rational forecast. +The second file contains monthly Michigan Survey aggregates: the mean +one-year-ahead inflation forecast and the shares of households answering +"more unemployment," "about the same," and "less unemployment," together with +the monthly unemployment rate. -Both wedges are **countercyclical**: they rise during every NBER recession in -the sample. +```{code-cell} ipython3 +data_path = '_static/lecture_specific/subjective_beliefs_business_cycles/' +macro_q = pd.read_csv(data_path + 'bbh_macro_quarterly.csv', + index_col='YYYYQ') +mich_m = pd.read_csv(data_path + 'bbh_michigan_monthly.csv', + index_col='yyyymm') +``` -These facts are robust: they survive replacing the VAR forecast with the SPF -forecast, alternative VAR specifications, and extending the sample back to -1960. +Quarters are indexed as `YYYYQ`, so `19821` means 1982Q1, and months as +`yyyymm`. -A positive unemployment wedge is naturally read as pessimism, since -unemployment is high in bad times. +#### The VAR benchmark forecast -The positive inflation wedge carries the same interpretation because -households regard high inflation as a feature of bad times: in the Bank of -England's Inflation Attitudes Survey, 50% to 80% of households consistently -answer that faster inflation would weaken the economy. +The data-generating forecast $E_t[\cdot]$ comes from a quarterly VAR with two +lags in nine variables: CPI inflation over the past year, annualized real GDP +growth, the unemployment rate, the log change in the relative price of +investment goods, capacity utilization, log hours worked per capita, the +consumption rate, the investment rate, and the federal funds rate. -The two wedges are positively correlated, with a correlation of 0.34, and the -first principal component of the wedge series explains **78.6%** of their -joint variation. +```{code-cell} ipython3 +q = macro_q +var_data = pd.DataFrame({ + 'infl_yoy': 100 * (q.CPIAUCSL / q.CPIAUCSL.shift(4) - 1), + 'gdp_gr': 400 * np.log(q.GDPC1 / q.GDPC1.shift(1)), + 'unrate': q.UNRATE, + 'dpiric': 100 * np.log(q.PIRIC / q.PIRIC.shift(1)), + 'cumfns': q.CUMFNS, + 'hours_pc': 100 * np.log(q.PRS85006023 * q.CE16OV / q.CNP16OV), + 'cons_r': 100 * (q.PCEND + q.PCESV) / q.GDP, + 'inv_r': 100 * q.GPDI / q.GDP, + 'ffr': q.FEDFUNDS, +}) +output_gap = 100 * np.log(q.GDPC1 / q.GDPPOT) +``` -(The principal component is computed from the raw wedges, so it reflects both -their correlation and the larger variance of the inflation wedge.) +The VAR is estimated by least squares on 1960Q1--2019Q4, and forecasts are +iterated four quarters ahead from every quarter. -The same one-factor pattern appears in the cross section. +```{code-cell} ipython3 +def var_forecasts(data, first=19601, last=20194, horizon=4): + """OLS VAR(2) and iterated `horizon`-step-ahead forecasts.""" + X, idx = data.values, data.index.values + est = (idx >= first) & (idx <= last) + Y_est = np.array([X[t] for t in range(2, len(idx)) if est[t]]) + X_est = np.array([np.concatenate([X[t-1], X[t-2], [1.0]]) + for t in range(2, len(idx)) if est[t]]) + B = np.linalg.lstsq(X_est, Y_est, rcond=None)[0] + + forecast = np.full_like(X, np.nan) + for t in range(1, len(idx)): + z1, z2 = X[t], X[t-1] + if np.isnan(z1).any() or np.isnan(z2).any(): + continue + for h in range(horizon): + z1, z2 = np.concatenate([z1, z2, [1.0]]) @ B, z1 + forecast[t] = z1 + return pd.DataFrame(forecast, index=idx, columns=data.columns) + +E_t4 = var_forecasts(var_data) # E_t[x_{t+4}], info through quarter t +``` -Michigan Survey households with high inflation forecasts are more likely to -expect unemployment to rise, to expect worse aggregate and personal financial -conditions, and to assign higher probability to losing their own jobs. +#### From categorical answers to a forecast -Average wedges also line up positively across demographic groups, and although -more educated and higher-income households make smaller forecast errors, every -group overpredicts both variables. +The Michigan unemployment question is categorical, so we convert the answer +shares into a mean forecast with the Carlson--Parkin procedure. -The FRBNY Survey of Consumer Expectations (SCE) shows the same patterns. +Assume household forecasts of the change in unemployment over the next year +are normally distributed across households, $N(\mu_t, \sigma_t^2)$, and that a +household answers "about the same" when its forecast lies in $[-a, a]$. -This evidence supports the interpretation that the wedges reflect a common -pessimism/optimism component rather than two unrelated forecast mistakes. +The shares answering "more" ($q_t^u$) and "less" ($q_t^d$) then satisfy -The following code simulates a stylized wedge process that matches the sample -length, the mean and standard deviation of each wedge, and the 0.34 -correlation between the wedges. +$$ -The point is not to recreate the raw Michigan series. +q_t^u = 1 - \Phi\!\left(\frac{a - \mu_t}{\sigma_t}\right), +\qquad +q_t^d = \Phi\!\left(\frac{-a - \mu_t}{\sigma_t}\right), -Instead, the simulation separates the common pessimism factor from residual -survey noise, so the wedges are strongly related but not perfectly collinear. +$$ -```{code-cell} ipython3 -# Baseline belief-shock parameters used throughout the lecture. -μ_θ = 5.64 # mean of belief-shock parameter θ -ρ_θ = 0.714 # AR(1) persistence of θ -σ_θ = 4.3 # innovation volatility +where $\Phi$ is the standard normal cdf, and inverting the two equations +gives -# Wedge loadings used later in the model illustrations. -c_u = 0.52 / μ_θ -c_π = 1.22 / μ_θ +$$ -# Empirical wedge moments used to discipline the simulation. -mean_u, mean_π = 0.52, 1.22 -std_u, std_π = 0.57, 0.97 -corr_wedges = 0.34 +\sigma_t = \frac{2a}{\Phi^{-1}(1 - q_t^u) - \Phi^{-1}(q_t^d)}, +\qquad +\mu_t = a - \sigma_t\, \Phi^{-1}(1 - q_t^u). -T = 152 # 38 years * 4 quarters +$$ -# Simulate the belief shock. -rng = np.random.default_rng(42) -θ = np.zeros(T) -θ[0] = μ_θ -for t in range(1, T): - θ[t] = ((1 - ρ_θ) * μ_θ - + ρ_θ * θ[t-1] - + σ_θ * rng.standard_normal()) +The threshold $a$ scales the whole series; {cite:t}`bhandari2025survey` pin it +down by comparing the implied cross-sectional dispersion with that of SPF +forecasts. -def standardize(x): - """Return a de-meaned series with unit standard deviation.""" - return (x - np.mean(x)) / np.std(x) +We set $a = 1.045$, which reproduces their fitted forecast series. +```{code-cell} ipython3 +def carlson_parkin(share_more, share_less, a=1.045): + """Mean forecast implied by categorical shares (normal cross-section).""" + z_up, z_down = norm.ppf(1 - share_more), norm.ppf(share_less) + σ = 2 * a / (z_up - z_down) + return a - σ * z_up +``` -def orthogonal_noise(rng, *basis, T=T): - """Generate standardized noise orthogonal to the supplied basis series.""" - e = standardize(rng.standard_normal(T)) - for b in basis: - e = e - (e @ b) / (b @ b) * b - return standardize(e) +#### Constructing the wedges +Timing follows the paper: responses from the first month of quarter $t+1$ are +treated as forecasts made with information through quarter $t$. -# Loading each wedge on the common factor with weight √corr makes the -# sample correlation between the two wedges equal corr_wedges exactly. -common_weight = np.sqrt(corr_wedges) -noise_weight = np.sqrt(1 - corr_wedges) +The unemployment *level* forecast adds the expected change to the +unemployment rate in the month the forecast is made. -common_factor = standardize(θ) -noise_u = orthogonal_noise(rng, common_factor) -noise_π = orthogonal_noise(rng, common_factor, noise_u) +Each wedge then subtracts the corresponding VAR forecast: year-over-year +inflation, and the unemployment rate four quarters ahead. -wedge_u = mean_u + std_u * (common_weight * common_factor - + noise_weight * noise_u) -wedge_π = mean_π + std_π * (common_weight * common_factor - + noise_weight * noise_π) +Both wedges are measured in percentage points (pp), the unit we use for them +throughout the lecture. -# Principal components of the raw (non-standardized) wedges, as in the paper. -wedges = np.column_stack([wedge_u, wedge_π]) -eigvals = np.linalg.eigvalsh(np.cov(wedges, rowvar=False)) +```{code-cell} ipython3 +def build_wedges(mich_m, E_t4, first=19821, last=20194): + """Survey minus VAR forecasts for unemployment and inflation.""" + rows = [] + for yq in E_t4.index: + if not first <= yq <= last: + continue + y, qq = yq // 10, yq % 10 + mm = (y + (qq == 4)) * 100 + (qq % 4) * 3 + 1 # 1st month of qtr t+1 + s = mich_m.loc[mm] + total = s.share_more + s.share_same + s.share_less + du = carlson_parkin(s.share_more / total, s.share_less / total) + rows.append((yq, + s.unrate + du - E_t4.loc[yq, 'unrate'], + s.px1_mean - E_t4.loc[yq, 'infl_yoy'])) + return pd.DataFrame(rows, columns=['YYYYQ', 'unemp', 'infl'] + ).set_index('YYYYQ') + +wedges = build_wedges(mich_m, E_t4) +wedge_u, wedge_π = wedges.unemp, wedges.infl +W = np.column_stack([wedge_u, wedge_π]) +eigvals = np.linalg.eigvalsh(np.cov(W, rowvar=False)) pc1_share = eigvals[-1] / eigvals.sum() -# Quarterly dates, 1982Q1-2019Q4 -quarters = [datetime.date(1982 + (q // 4), 3 * (q % 4) + 1, 1) - for q in range(T)] +# Quarterly dates and NBER recessions for plotting +quarters = [datetime.date(yq // 10, 3 * (yq % 10) - 2, 1) + for yq in wedges.index] + + +def fred_recession_spans(start, end): + """NBER recession spans from FRED's monthly USREC indicator.""" + fetch_start = pd.Timestamp(start) - pd.DateOffset(years=1) + fetch_end = pd.Timestamp(end) + pd.DateOffset(months=1) + rec = pd.read_csv( + 'https://fred.stlouisfed.org/graph/fredgraph.csv?id=USREC', + parse_dates=['observation_date'], + index_col='observation_date' + )['USREC'].loc[fetch_start:fetch_end] + rec = pd.to_numeric(rec, errors='coerce').fillna(0).astype(bool) + + starts = rec.index[rec & ~rec.shift(fill_value=False)] + ends = rec.index[~rec & rec.shift(fill_value=False)] + if rec.iloc[-1]: + ends = ends.append(pd.DatetimeIndex([rec.index[-1] + + pd.offsets.MonthBegin()])) + return [(s.date(), e.date()) for s, e in zip(starts, ends)] + + +recessions = fred_recession_spans(quarters[0], quarters[-1]) ``` ```{code-cell} ipython3 --- mystnb: figure: - caption: simulated belief wedges + caption: replicated belief wedges, 1982Q1-2019Q4 name: fig-sbbc-belief-wedges --- fig, axes = plt.subplots(2, 1, figsize=(11, 6), sharex=True) @@ -292,13 +339,21 @@ axes[1].set_ylabel('percentage points') axes[1].legend(loc='upper left') for ax in axes: + for start, end in recessions: + ax.axvspan(start, end, color='grey', alpha=0.25, linewidth=0) + ax.axhline(0, color='black', linewidth=0.6, linestyle=':') ax.xaxis.set_major_locator(mdates.YearLocator(5)) ax.xaxis.set_major_formatter(mdates.DateFormatter('%Y')) + ax.set_xlim(quarters[0], quarters[-1]) plt.tight_layout() plt.show() ``` +Both wedges are positive most of the time --- households persistently +overpredict unemployment and inflation --- and both rise during the shaded +NBER recessions. + ```{code-cell} ipython3 --- mystnb: @@ -307,7 +362,7 @@ mystnb: name: fig-sbbc-wedge-scatter --- fig, ax = plt.subplots() -sc = ax.scatter(wedge_u, wedge_π, c=range(T), cmap='RdYlGn_r', +sc = ax.scatter(wedge_u, wedge_π, c=range(len(wedges)), cmap='RdYlGn_r', alpha=0.7, s=20) plt.colorbar(sc, ax=ax, label='quarter (dark = recent)') ax.set_xlabel('unemployment wedge (pp)') @@ -320,29 +375,57 @@ plt.tight_layout() plt.show() ``` -The first figure plots the simulated unemployment wedge in the top panel and -the simulated inflation wedge in the bottom panel. +The scatter plot shows the one-factor structure. -The dashed horizontal lines show the sample means, which match the empirical -values reported above. +Each point is one quarter, with the horizontal coordinate equal to the +unemployment wedge and the vertical coordinate equal to the inflation wedge. -Both wedges have a common cyclical component driven by $\theta_t$, but they also -contain residual components that stand in for survey noise and other -idiosyncratic variation. +The points form an upward-sloping cloud rather than a line: a common +pessimism factor drives both wedges, while survey noise and other +idiosyncratic variation keep them from being collinear. -The scatter plot makes the one-factor structure visible. +(The principal-component share is computed from the raw wedges, so it +reflects both their correlation and the larger variance of the inflation +wedge.) -Each point is one quarter, with the horizontal coordinate equal to the -unemployment wedge and the vertical coordinate equal to the inflation wedge. +### Empirical facts + +The figures above shows three key empirical facts about the belief wedges: -The points form an upward-sloping cloud rather than a line: the common factor -drives both wedges, while survey noise keeps them from being collinear. +- Both wedges are **positive on average**: households expect higher unemployment +and higher inflation than the rational forecast. + +- Both wedges are **countercyclical**: they rise during every NBER recession in +the sample. + +- The wedges are **positively correlated and share one dominant factor**: the +first principal component explains about four-fifths of their joint +variation. + +A positive unemployment wedge is naturally read as pessimism, since +unemployment is high in bad times. -With the means, standard deviations, and correlation matched, the first -principal component of the simulated wedges accounts for about 78.5% of their -joint variation, essentially the 78.6% share in the data. +The positive inflation wedge carries the same interpretation because +households regard high inflation as a feature of bad times. + +The same one-factor pattern appears in the cross section. + +This evidence supports the interpretation that the wedges reflect a common +pessimism/optimism component rather than two unrelated forecast mistakes. -This is the one-factor structure that motivates the theoretical framework. +These moments are the calibration targets for the belief shock $\theta_t$, +the pessimism parameter formalized in the next section. + +```{code-cell} ipython3 +# Belief-shock calibration from the paper +μ_θ = 5.64 # mean of belief-shock parameter θ +ρ_θ = 0.714 # AR(1) persistence: autocorrelation of the wedges' first PC +σ_θ = 4.3 # innovation volatility + +# Wedge loadings used later in the model illustrations. +c_u = 0.52 / μ_θ +c_π = 1.22 / μ_θ +``` ## A model of pessimism @@ -350,41 +433,35 @@ This is the one-factor structure that motivates the theoretical framework. Why would households have systematically biased beliefs? -One disciplined answer comes from **robust control** or **multiplier preferences** -({cite}`HansenSargent2001`, {cite}`HansenSargent2008`). +One answer comes from **robust control** or **multiplier preferences** +({cite:t}`HansenSargent2001`, {cite:t}`HansenSargent2008`). -An agent represented by multiplier preferences solves +Recall that an agent represented by multiplier preferences solves $$ - -V_t \;=\; \min_{\substack{m_{t+1} > 0 \\ E_t[m_{t+1}] = 1}} +v_t \;=\; \min_{\substack{m_{t+1} > 0 \\ E_t[m_{t+1}] = 1}} \Bigl\{ u(x_t) - + \beta E_t\!\left[m_{t+1} V_{t+1}\right] + + \beta E_t\!\left[m_{t+1} v_{t+1}\right] + \frac{\beta}{\theta_t}\, E_t\!\left[m_{t+1} \log m_{t+1}\right] \Bigr\}. - $$ Here $m_{t+1}$ is a **likelihood ratio** (Radon–Nikodym derivative) that distorts the reference measure, and the last term is an entropy penalty that keeps the distortion from being too extreme. -The state vector $x_t \in \mathbb{R}^n$ follows a Markov law of motion +Assume state vector $x_t \in \mathbb{R}^n$ follows a Markov law of motion $$ - x_{t+1} = \psi(x_t, w_{t+1}), - $$ where $w_{t+1} \sim N(0, I_k)$ is i.i.d. under the data-generating measure, and the penalty parameter is linear in the state: $$ - \theta_t = \bar\theta x_t, - $$ for a $1 \times n$ vector of parameters $\bar\theta$. @@ -396,31 +473,27 @@ $\theta_t$ means more pessimism. Because $\theta_t$ is linear in the state, it can turn negative, in which case the inner problem becomes a maximization that tilts probability toward -high-continuation-value states --- optimism. +high-continuation-value states, which corresponds to optimism. The inner minimization has a closed-form solution. -Minimizing $E_t[m_{t+1} V_{t+1}] + \frac{1}{\theta_t} E_t[m_{t+1} \log m_{t+1}]$ +Minimizing $E_t[m_{t+1} v_{t+1}] + \frac{1}{\theta_t} E_t[m_{t+1} \log m_{t+1}]$ state by state subject to $E_t[m_{t+1}] = 1$ gives the first-order condition $$ - -V_{t+1} + \frac{1}{\theta_t}\left(\log m_{t+1} + 1\right) + \lambda_t = 0, - +v_{t+1} + \frac{1}{\theta_t}\left(\log m_{t+1} + 1\right) + \lambda_t = 0, $$ where $\lambda_t$ is the multiplier on the constraint, so -$m_{t+1} \propto \exp(-\theta_t V_{t+1})$ and the constraint pins down the +$m_{t+1} \propto \exp(-\theta_t v_{t+1})$ and the constraint pins down the normalization: $$ - m_{t+1}^* \;=\; -\frac{\exp(-\theta_t V_{t+1})}{E_t[\exp(-\theta_t V_{t+1})]}. - +\frac{\exp(-\theta_t v_{t+1})}{E_t[\exp(-\theta_t v_{t+1})]}. $$ -Since $m_{t+1}^*$ assigns higher weight to states where $V_{t+1}$ is low (bad +Since $m_{t+1}^*$ assigns higher weight to states where $v_{t+1}$ is low (bad outcomes), pessimistic agents effectively over-weight recessions in their probability assessments. @@ -428,20 +501,18 @@ Substituting $m_{t+1}^*$ back into the recursion gives the equivalent **risk-sensitive** form $$ - -V_t = u(x_t) - \frac{\beta}{\theta_t} -\log E_t\!\left[\exp(-\theta_t V_{t+1})\right], - +v_t = u(x_t) - \frac{\beta}{\theta_t} +\log E_t\!\left[\exp(-\theta_t v_{t+1})\right], $$ which replaces the expected continuation value with a soft minimum: as -$\theta_t \to 0$ it reduces to $u(x_t) + \beta E_t[V_{t+1}]$, and as +$\theta_t \to 0$ it reduces to $u(x_t) + \beta E_t[v_{t+1}]$, and as $\theta_t \to \infty$ it approaches the worst case over states. In the robust-control literature, this distortion expresses fear of model misspecification. -{cite:t}`bhandari2025survey` instead take the recursion directly as a model of +{cite:t}`bhandari2025survey` instead take the recursion as a model of pessimism and optimism, and let survey data discipline the process for $\theta_t$. @@ -463,12 +534,10 @@ forecasts. Using $\tilde{E}_t[\cdot] = E_t[m_{t+1}^* \cdot]$: $$ - \Delta_t^{(1)}(z) = \tilde{E}_t[z_{t+1}] - E_t[z_{t+1}] = E_t\!\left[m_{t+1}^* z_{t+1}\right] - E_t[z_{t+1}] = \operatorname{Cov}_t(m_{t+1}^*, z_{t+1}). - $$ The last equality holds because $E_t[m_{t+1}^*] = 1$, so @@ -478,146 +547,166 @@ $E_t[m^*_{t+1} z_{t+1}] - E_t[z_{t+1}] So the belief wedge equals the covariance between the distorted likelihood ratio and the variable of interest. -When $V_{t+1}$ is high in states where +When $v_{t+1}$ is high in states where $z_{t+1}$ is also high, $m_{t+1}^*$ will be low in those states, making the -covariance negative --- i.e.\ the agent *underestimates* good-state variables. +covariance negative (i.e. the agent *underestimates* good-state variables). For unemployment (which varies inversely with good economic outcomes), the wedge is positive: pessimists expect higher unemployment than the model predicts. ### Illustration: optimal belief distortion -To see the mechanism concretely, consider an **endowment economy** with a -scalar log-consumption state $x_t$ and dynamics +We now compute, in the smallest model that can carry them, the +risk-sensitive recursion, the optimal distortion $m_{t+1}^*$, and the belief +wedge in closed form. -$$ +The payoff is the central formula of the lecture: optimal pessimism is a +**mean shift** of the shock distribution, equal to $-\theta_t$ times the +exposure of the continuation value to the shock. -x_{t+1} = \rho_x x_t + \sigma_x w_{t+1}, \qquad w_{t+1} \sim N(0,1). +Consider an endowment economy in which log consumption $x_t$ follows the +linear process below. +$$ +x_{t+1} = \rho_x x_t + \sigma_x w_{t+1}, \qquad w_{t+1} \sim N(0,1). $$ -With log utility, $u(x_t) = (1 - \beta)x_t$, and we guess that the -continuation value is linear: $V_t = V_x x_t + V_q$. +Here $\rho_x$ is the persistence of the state, $\sigma_x$ is the standard +deviation of its innovation, and period utility is $u(x_t) = (1 - \beta)x_t$ +for log utility in consumption. -The Gaussian structure makes everything explicit. +The calculation has four steps: -Since $-\theta_t V_{t+1} = -\theta_t(V_x \rho_x x_t + V_q) - \theta_t V_x -\sigma_x w_{t+1}$ is linear in the standard normal shock, the moment -generating function $E[\exp(a w)] = \exp(a^2/2)$ evaluates the -risk-sensitive recursion in closed form: +1. guess an affine continuation value; +2. evaluate the risk-sensitive recursion in closed form; +3. derive the optimal belief distortion and the wedge it implies; +4. solve for the value-function slope. -$$ +With linear dynamics, Gaussian shocks, and linear utility, the continuation +value is affine in the state, so we guess $v_t = v_x x_t + v_q$ and verify +the guess by substitution. + +The slope $v_x = \partial v_t / \partial x_t$ measures how much the agent +values an extra unit of the state. --\frac{1}{\theta_t} \log E_t\!\left[\exp(-\theta_t V_{t+1})\right] -= V_x \rho_x x_t + V_q - \frac{\theta_t}{2}\, V_x^2 \sigma_x^2. +We solve it from the recursion in Step 4. +Since $-\theta_t v_{t+1} = -\theta_t(v_x \rho_x x_t + v_q) - \theta_t v_x +\sigma_x w_{t+1}$ is linear in the standard normal shock, the moment +generating function $E[\exp(a w)] = \exp(a^2/2)$ evaluates the expectation in +closed form: + +$$ +-\frac{1}{\theta_t} \log E_t\!\left[\exp(-\theta_t v_{t+1})\right] += v_x \rho_x x_t + v_q - \frac{\theta_t}{2}\, v_x^2 \sigma_x^2. $$ -The expected continuation value is penalized by half the pessimism parameter -times the conditional variance of continuation values: pessimism acts like an +Pessimism subtracts half of $\theta_t$ times the conditional variance of +continuation values from the ordinary expectation, so it acts like an endogenous discount on risky continuation utilities. + The same linearity pins down the optimal distortion. -Writing $a_t = -\theta_t V_x \sigma_x$, the likelihood ratio becomes +Writing $\alpha_t = -\theta_t v_x \sigma_x$, the likelihood ratio becomes $$ - -m_{t+1}^* = \frac{\exp(a_t w_{t+1})}{E_t[\exp(a_t w_{t+1})]} -= \exp\!\left(a_t w_{t+1} - \tfrac{1}{2} a_t^2\right), - +m_{t+1}^* = \frac{\exp(\alpha_t w_{t+1})} + {E_t[\exp(\alpha_t w_{t+1})]} += \exp\!\left(\alpha_t w_{t+1} - \tfrac{1}{2} \alpha_t^2\right), $$ -which is exactly the density of $N(a_t, 1)$ divided by the density of +which is exactly the density of $N(\alpha_t, 1)$ divided by the density of $N(0, 1)$. Pessimism is therefore a **mean shift**: under the subjective measure, $$ - w_{t+1} \;\sim\; N(\nu_t, 1), \qquad -\nu_t = -\theta_t V_x \sigma_x. - +\nu_t = -\theta_t v_x \sigma_x. $$ -The drift $\nu_t$ is the pessimism parameter times the exposure of the -continuation value to the shock --- the agent slants beliefs exactly in the +The drift $\nu_t$ is the negative of the pessimism parameter times the +exposure of the continuation value to the shock. + +The agent tilts beliefs exactly in the direction that hurts most, by an amount the entropy penalty limits. The belief wedge for the state follows immediately: $$ - \Delta_t^{(1)}(x) = \tilde E_t[x_{t+1}] - E_t[x_{t+1}] -= \sigma_x \nu_t = -\theta_t V_x \sigma_x^2. - += \sigma_x \nu_t = -\theta_t v_x \sigma_x^2. $$ -When $V_x > 0$ (high consumption states are good) and $\theta_t > 0$ -(pessimism), the wedge is negative --- the agent *underestimates* -future consumption. +Here $\tilde E_t$ denotes expectation under the subjective measure implied by +$m_{t+1}^*$, while $E_t$ denotes expectation under the data-generating measure. + +When $v_x > 0$ (high consumption states are good) and $\theta_t > 0$ +(pessimism), the wedge is negative, so the agent *underestimates* future +consumption. For a variable that enters the value function with a negative sign, such as -unemployment, the same pessimism generates a **positive** wedge. +unemployment, the same pessimism generates a *positive* wedge. -Finally, substituting the closed-form recursion back into -$V_t = u(x_t) - \frac{\beta}{\theta_t}\log E_t[\exp(-\theta_t V_{t+1})]$ -and matching coefficients on $x_t$ --- using the scaling of $\theta_t$ -derived in the appendix, under which the penalty term contributes to the -slope with coefficient $\mu_\theta$ --- yields the **Riccati equation** +To pin down the stationary slope used in the code, evaluate the +recursion at the mean pessimism level $\mu_\theta$. -$$ +Substituting the closed-form recursion back into +$v_t = u(x_t) - \frac{\beta}{\mu_\theta} +\log E_t[\exp(-\mu_\theta v_{t+1})]$ and matching coefficients on $x_t$ +yields the **Riccati equation** -V_x = u_x + \beta \rho_x V_x - \frac{\beta}{2}\,\mu_\theta\, \sigma_x^2 V_x^2, +$$ +v_x = u_x + \beta \rho_x v_x - \frac{\beta}{2}\,\mu_\theta\, \sigma_x^2 v_x^2, \qquad u_x = 1 - \beta. - $$ The quadratic term is the price of pessimism: it lowers the marginal value of the state relative to the rational-expectations value -$V_x^{RE} = u_x / (1 - \beta\rho_x)$. +$v_x^{RE} = u_x / (1 - \beta\rho_x)$. We now turn this illustration into code, building it up from small pieces. -The first ingredient is the slope $V_x$ of the continuation value. +The first ingredient is the slope $v_x$ of the continuation value. It solves the scalar Riccati equation, which we write as a quadratic -$a V_x^2 + b V_x + c = 0$ and solve with the quadratic formula. +$a v_x^2 + b v_x + c = 0$ and solve with the quadratic formula. We keep the root that collapses to the rational-expectations value -$V_x^{RE} = u_x / (1 - \beta\rho_x)$ as the pessimism parameter $\mu_\theta \to 0$. +$v_x^{RE} = u_x / (1 - \beta\rho_x)$ as the pessimism parameter $\mu_\theta \to 0$. ```{code-cell} ipython3 -def solve_Vx(β, ρ_x, σ_x, μ_θ): +def solve_vx(β, ρ_x, σ_x, μ_θ): """ - Solve the scalar Riccati equation for the value-function slope Vx: + Solve the scalar Riccati equation for the value-function slope vx: - Vx = u_x - (β/2) μ_θ σ_x**2 Vx**2 + β ρ_x Vx, with u_x = 1 - β. + vx = u_x - (β/2) μ_θ σ_x**2 vx**2 + β ρ_x vx, with u_x = 1 - β. """ u_x = 1.0 - β # marginal utility of log consumption - Vx_re = u_x / (1.0 - β * ρ_x) # rational-expectations (θ = 0) value + vx_re = u_x / (1.0 - β * ρ_x) # rational-expectations (θ = 0) value - # Coefficients of a Vx**2 + b Vx + c = 0 + # Coefficients of a vx**2 + b vx + c = 0 a = 0.5 * β * σ_x**2 * μ_θ b = 1.0 - β * ρ_x c = -u_x if abs(a) < 1e-14: # no pessimism: equation is linear - return Vx_re + return vx_re disc = b**2 - 4.0 * a * c if disc < 0: # no real root: fall back to RE - return Vx_re + return vx_re # Keep the root closest to the rational-expectations value r1 = (-b + np.sqrt(disc)) / (2.0 * a) r2 = (-b - np.sqrt(disc)) / (2.0 * a) - return r1 if abs(r1 - Vx_re) < abs(r2 - Vx_re) else r2 + return r1 if abs(r1 - vx_re) < abs(r2 - vx_re) else r2 ``` We store the primitives in a `NamedTuple`, together with the solved slope -$V_x$, and use `create_belief_model` to build an instance. +$v_x$, and use `create_belief_model` to build an instance. ```{code-cell} ipython3 class BeliefModel(NamedTuple): @@ -627,31 +716,31 @@ class BeliefModel(NamedTuple): μ_θ: float # mean of the belief-shock parameter θ ρ_θ: float # AR(1) persistence of θ σ_θ: float # volatility of the θ innovation - Vx: float # slope of the linearized continuation value + vx: float # slope of the linearized continuation value def create_belief_model(β=0.994, ρ_x=0.85, σ_x=0.005, μ_θ=5.64, ρ_θ=0.714, σ_θ=4.3): - """Build a belief model, solving the Riccati equation for Vx.""" - Vx = solve_Vx(β, ρ_x, σ_x, μ_θ) + """Build a belief model, solving the Riccati equation for vx.""" + vx = solve_vx(β, ρ_x, σ_x, μ_θ) return BeliefModel(β=β, ρ_x=ρ_x, σ_x=σ_x, - μ_θ=μ_θ, ρ_θ=ρ_θ, σ_θ=σ_θ, Vx=Vx) + μ_θ=μ_θ, ρ_θ=ρ_θ, σ_θ=σ_θ, vx=vx) ``` Two functions map a value of $\theta_t$ into the implied distortion. -The drift $\nu_t = -\theta_t V_x \sigma_x$ is the mean shift of the shock under +The drift $\nu_t = -\theta_t v_x \sigma_x$ is the mean shift of the shock under the subjective measure; the wedge $\Delta_t^{(1)}(x) = \sigma_x \nu_t$ is the resulting forecast bias for the state. ```{code-cell} ipython3 def belief_drift(model, θ): - """Mean shift of the shock under subjective beliefs: ν = -θ Vx σ_x.""" - return -θ * model.Vx * model.σ_x + """Mean shift of the shock under subjective beliefs: ν = -θ vx σ_x.""" + return -θ * model.vx * model.σ_x def belief_wedge(model, θ): - """One-period belief wedge for the state: Δ = σ_x ν = -θ Vx σ_x**2.""" + """One-period belief wedge for the state: Δ = σ_x ν = -θ vx σ_x**2.""" return model.σ_x * belief_drift(model, θ) ``` @@ -671,15 +760,15 @@ def simulate_θ(model, T=200, seed=42): ``` Building the model at the baseline calibration, we compare the robust slope -$V_x$ with its rational-expectations counterpart, and report the mean belief +$v_x$ with its rational-expectations counterpart, and report the mean belief drift and wedge. ```{code-cell} ipython3 model = create_belief_model() -Vx_re = (1 - model.β) / (1 - model.β * model.ρ_x) -print(f"RE value of Vx: {Vx_re:.4f}") -print(f"Robust value of Vx: {model.Vx:.4f}") +vx_re = (1 - model.β) / (1 - model.β * model.ρ_x) +print(f"RE value of v_x: {vx_re:.4f}") +print(f"Robust value of v_x: {model.vx:.4f}") print(f"Belief drift at θ_bar: {belief_drift(model, model.μ_θ) * 100:.4f} pp") print(f"Belief wedge at θ_bar: {belief_wedge(model, model.μ_θ) * 100:.4f} pp") ``` @@ -691,16 +780,14 @@ mystnb: caption: objective and subjective shock distributions name: fig-sbbc-shock-distributions --- -# Scale the tiny drift by σ_x for visibility. - θ_vals = [0, model.μ_θ, 2 * model.μ_θ] -labels = ['θ = 0 (rational)', +labels = ['θ = 0', f'θ = θ_bar = {model.μ_θ:.1f} (mean)', f'θ = 2θ_bar (pessimistic)'] colors = ['black', 'steelblue', 'firebrick'] # Drift in units of σ_x -ν_tilde = [-θ * model.Vx for θ in θ_vals] +ν_tilde = [-θ * model.vx for θ in θ_vals] x_grid = np.linspace(-4, 4, 500) @@ -712,7 +799,7 @@ for μ, label, color in zip(ν_tilde, labels, colors): ax.axvline(0, color='grey', linestyle=':', linewidth=0.8) ax.set_xlabel( 'innovation shift in units of $\\sigma_x$: ' - '$\\nu_t / \\sigma_x = -\\theta_t V_x$' + '$\\nu_t / \\sigma_x = -\\theta_t v_x$' ) ax.set_ylabel('density') ax.legend() @@ -747,9 +834,7 @@ $\tilde w_{t+1} \sim N(0, 1)$ under the subjective measure, into the dynamics of $x_t$ gives $$ - -x_{t+1} = -\theta_t V_x \sigma_x^2 + \rho_x x_t + \sigma_x \tilde w_{t+1}. - +x_{t+1} = -\theta_t v_x \sigma_x^2 + \rho_x x_t + \sigma_x \tilde w_{t+1}. $$ With $\theta_t = \bar\theta(\bar x + x_t)$, collecting terms shows that @@ -757,16 +842,15 @@ subjective beliefs change both the intercept and the slope of the perceived dynamics: $$ - -\tilde\rho_x = \rho_x - \bar\theta\, V_x \sigma_x^2, +\tilde\rho_x = \rho_x - \bar\theta\, v_x \sigma_x^2, \qquad -\tilde\psi_q = -\bar\theta \bar{x}\, V_x \sigma_x^2. - +\tilde\psi_q = -\bar\theta \bar{x}\, v_x \sigma_x^2. $$ -For a good state ($V_x > 0$), pessimism adds a negative drift; for a bad -state such as unemployment ($V_x < 0$), the same formula raises the -subjective persistence --- pessimists believe bad times last longer. +For a good state ($v_x > 0$), pessimism adds a negative drift. + +For a bad state such as unemployment ($v_x < 0$), the same formula raises the +subjective persistence, so pessimists believe bad times last longer. The code below compares objective and subjective forecast paths of the consumption state, starting from the steady state, holding the pessimism @@ -774,7 +858,7 @@ level fixed along the forecast path. ```{code-cell} ipython3 def forecast_paths(model, θ, x0=0.0, τ_max=20): - """Objective and subjective forecast paths E_t[x_{t+τ}], Ẽ_t[x_{t+τ}].""" + """Objective and subjective forecast paths of the state x.""" ν = belief_drift(model, θ) # subjective mean of the shock obj = np.empty(τ_max + 1) subj = np.empty(τ_max + 1) @@ -824,8 +908,8 @@ pessimism is twice as high, because the constant drift $\sigma_x \nu_t$ accumulates at the persistence $\rho_x$. On average the feared bad times never arrive, so subjective forecasts are -systematically wrong --- and that systematic error is exactly the belief -wedge measured in the surveys. +systematically wrong, and that systematic error is exactly the belief wedge +measured in the surveys. This figure is the scalar version of a key picture in {cite:t}`bhandari2025survey`: after a pessimism shock in the structural @@ -843,10 +927,8 @@ Let the state vector be $x_t \in \mathbb{R}^n$ with **objective** law of motion $$ - x_{t+1} = \psi_q + \psi_x x_t + \psi_w w_{t+1}, \qquad w_{t+1} \sim N(0, I_k). - $$ To first order, the belief factor $\theta_t = \bar\theta x_t$ equals @@ -855,12 +937,10 @@ $\bar\theta(\bar{x} + x_{1t})$. Under the optimal belief distortion the shocks are re-centered: $$ - -w_{t+1} \;\sim\; N\!\left(- \theta_t (V_x \psi_w)',\; I_k\right), - +w_{t+1} \;\sim\; N\!\left(- \theta_t (v_x \psi_w)',\; I_k\right), $$ -where $V_x$ is the row vector of first derivatives of the continuation value +where $v_x$ is the row vector of first derivatives of the continuation value and $\bar{x}$ is the non-stochastic steady state. In a standard first-order perturbation, belief distortions would vanish: as @@ -871,13 +951,31 @@ order. shock volatility, which keeps the subjective model distinct from the data-generating process in the linear solution; the appendix gives details. -The resulting **belief wedge** for any variable $z_t = \bar{z}' x_t$ is +The wedge formula comes directly from comparing the one-step-ahead objective +and subjective conditional means. +Under the objective measure, $E_t[w_{t+1}] = 0$, so + +$$ +E_t[x_{t+1}] = \psi_q + \psi_x x_t. $$ -\Delta_t^{(1)}(z) -\;=\; -\theta_t\, \bar{z}' (\psi_w \psi_w') V_x'. +Under the subjective measure, the shock mean is +$\tilde E_t[w_{t+1}] = -\theta_t (v_x \psi_w)'$, so +$$ +\tilde E_t[x_{t+1}] += \psi_q + \psi_x x_t + - \theta_t \psi_w (v_x \psi_w)'. +$$ + +For any linear variable $z_t = \bar{z}' x_t$, the one-period belief wedge is +therefore + +$$ +\Delta_t^{(1)}(z) +\;=\; \tilde E_t[z_{t+1}] - E_t[z_{t+1}] +\;=\; -\theta_t\, \bar{z}' (\psi_w \psi_w') v_x'. $$ Because the drift moves with $\theta_t$, subjective beliefs change both the @@ -885,29 +983,70 @@ conditional mean and the persistence of the state: adverse states are more persistent under the subjective measure than under the data-generating measure. -### Riccati equation for $V_x$ +### Riccati equation for $v_x$ -The key object is $V_x$, which solves +The key object is $v_x$, which solves $$ - -V_x +v_x \;=\; u_x - - \frac{\beta}{2}\, V_x \psi_w \psi_w' V_x' \bar\theta - + \beta\, V_x \psi_x. - + - \frac{\beta}{2}\, v_x \psi_w \psi_w' v_x' \bar\theta + + \beta\, v_x \psi_x. $$ This is a modified Riccati equation: the middle term arises from the entropy penalty on beliefs and vanishes under rational expectations ($\bar\theta = 0$). +To see why the extra term has this form, focus on the continuation value. +Locally, write it as linear in next period's state, + +$$ + +v_{t+1} \approx v_x x_{t+1}. + +$$ + +Since $x_{t+1} = \psi_q + \psi_x x_t + \psi_w w_{t+1}$, the risky part of +continuation value is $v_x \psi_w w_{t+1}$, with conditional variance + +$$ + +v_x \psi_w \psi_w' v_x'. + +$$ + +Robust preferences replace the ordinary expected continuation value with an +entropy-adjusted one. For a Gaussian linear payoff, the minimization over +distorted beliefs gives + +$$ + +E_t[v_{t+1}] +- \frac{\theta_t}{2}\,\operatorname{Var}_t(v_{t+1}). + +$$ + +Thus the agent subtracts a variance penalty from continuation value. Because +$\theta_t = \bar\theta x_t$, matching the coefficient on $x_t$ in the Bellman +equation adds the term + +$$ + +- \frac{\beta}{2}\, + \left(v_x \psi_w \psi_w' v_x'\right)\bar\theta. + +$$ + +The term is quadratic in $v_x$ because the variance of the continuation value +depends on the square of its exposure to shocks, $v_x \psi_w$. + ### One-factor structure An important consequence of the formula for $\Delta_t^{(1)}(z)$ is that the *time variation* in all belief wedges is driven by the **single scalar** belief factor $\theta_t \approx \bar\theta(\bar{x} + x_{1t})$. -The cross-sectional loadings $-\bar{z}'(\psi_w\psi_w')V_x'$ are +The cross-sectional loadings $-\bar{z}'(\psi_w\psi_w')v_x'$ are fixed by the model's structural parameters. The loadings are not free parameters: they equal covariances of shocks with @@ -981,333 +1120,14 @@ belief factor whose path is close to the first principal component. ## A New Keynesian model with belief distortions -### Model description - -Following {cite:t}`bhandari2025survey`, we embed the belief-distortion -mechanism in a New Keynesian model with a **search-and-matching** labor market -({cite}`Shimer2005`; {cite}`ChristianoEichenbaumTrabandt2016`). - -The economy is populated by a representative household with subjective -beliefs, competitive producers of a final good, monopolistic producers of -intermediate goods, and labor-market firms that hire workers through a -matching friction. - -In the benchmark, all agents share the household's subjective beliefs; -the rational-firms variant below relaxes this. - -#### Representative household - -The household chooses consumption $C_t$ and bond holdings $B_{t+1}$ under -robust preferences: - -$$ - -V_t = \max_{C_t,\, B_{t+1}}\; -\min_{\substack{m_{t+1} > 0 \\ E_t[m_{t+1}] = 1}} -\log C_t + \beta E_t\!\left[m_{t+1} V_{t+1}\right] -+ \frac{\beta}{\theta_t} E_t\!\left[m_{t+1} \log m_{t+1}\right], - -$$ - -where the penalty parameter follows the exogenous AR(1) **belief shock** - -$$ - -\theta_t = (1 - \rho_\theta)\mu_\theta + \rho_\theta \theta_{t-1} -+ \sigma_\theta w_t^\theta. - -$$ - -This fits the framework above by appending $\theta_t$ to the state vector -$x_t$ and letting $\bar\theta$ select that component. - -The household consists of a unit mass of workers who perfectly share -consumption risk. +We now use the empirical belief factor to generate impulse responses. -A fraction $L_t$ is employed and earns the real wage $\xi_t$; the remaining -$1 - L_t$ are unemployed and receive benefits $D$ financed by lump-sum taxes. +The full model in {cite:t}`bhandari2025survey` is a New Keynesian model with +search-and-matching frictions. Beliefs matter because consumption decisions, +vacancy posting, wage bargaining, and price setting are forward-looking. -The nominal budget constraint is - -$$ - -P_t C_t + B_{t+1} \;\le\; (1 - L_t)\, P_t D + L_t P_t \xi_t -+ R_{t-1} B_t - T_t, - -$$ - -where $P_t$ is the price level, $B_{t+1}$ are one-period bonds purchased at -$t$ with gross return $R_t$, and $T_t$ collects lump-sum taxes net of profits. - -The process for $\theta_t$ is exogenous, but which states are *adverse* is -endogenous: equilibrium dynamics determine which states deliver low -continuation values $V_{t+1}$, and those are the states the household -overweights. - -#### Labour market - -At the end of each period, an employed worker keeps the job with probability -$\rho$, so $1 - \rho L_{t-1}$ workers search at the beginning of period $t$. - -Employment evolves as - -$$ - -L_t = \rho L_{t-1} + (1 - \rho L_{t-1})\, f_t, - -$$ - -where $f_t$ is the job-finding probability, and measured unemployment is -$u_t = 1 - L_t$. - -Labor-market firms post $v_t L_{t-1}$ vacancies at flow cost $\kappa_v$ each, -so labor market tightness is - -$$ - -\zeta_t = \frac{v_t L_{t-1}}{1 - \rho L_{t-1}}. - -$$ - -A Cobb--Douglas matching function -$M_t = \mu\, (v_t L_{t-1})^{\nu} (1 - \rho L_{t-1})^{1-\nu}$ -combines vacancies and searchers, so - -$$ - -f_t = \mu \zeta_t^{\nu}, -\qquad -q_t = \frac{f_t}{\zeta_t}, - -$$ - -where $q_t$ is the probability of filling a vacancy. - -Workers value jobs using the household's subjective beliefs. - -Let $s_{t+1} = \beta C_t / C_{t+1}$ denote the household's stochastic discount -factor and $\tilde E_t[\cdot]$ the subjective expectation. - -The values of unemployment and employment satisfy - -$$ - -U_t = D + \tilde E_t\!\left[ -s_{t+1}\bigl(f_{t+1} J^w_{t+1} + (1 - f_{t+1}) U_{t+1}\bigr)\right] - -$$ - -and - -$$ - -J^w_t = \xi_t -+ \tilde E_t\!\left[s_{t+1}\bigl(\rho + (1-\rho) f_{t+1}\bigr) J^w_{t+1}\right] -+ \tilde E_t\!\left[s_{t+1} (1-\rho)(1 - f_{t+1})\, U_{t+1}\right], - -$$ - -where $\rho + (1-\rho)f_{t+1}$ combines keeping the current job with losing it -but immediately finding a new one. - -A firm that employs a worker sells the labor services at the competitive price -$\vartheta_t$ and values the match at - -$$ - -J_t = \vartheta_t - \xi_t + \rho\, \tilde E_t\!\left[s_{t+1} J_{t+1}\right], - -$$ - -and free entry drives the expected profit from posting a vacancy to zero: - -$$ - -J_t = \frac{\kappa_v}{q_t}. - -$$ - -Wages are set by Nash bargaining with rigid wages, following -{cite}`Shimer2010`. - -The firm and the worker bargain over a *target* wage $\xi_t^*$ that splits the -match surplus according to the bargaining weight $\eta$: - -$$ - -\eta \left(J_t + \xi_t - \xi_t^*\right) -= (1 - \eta)\left(J^w_t - U_t + \xi_t^* - \xi_t\right). - -$$ - -The actual wage adjusts only partially toward the target, - -$$ - -\xi_t = \chi_w\, \xi_{t-1} + (1 - \chi_w)\, \xi_t^*, - -$$ - -with $\chi_w = 0$ corresponding to flexible wages. - -Vacancy posting and bargaining are forward-looking, so fluctuations in -$\theta_t$ directly move firms' incentives to create matches. - -This is a sharp contrast with a Walrasian spot labor market, where one-period -contracts leave no role for beliefs about future economic conditions. - -#### Production and price setting - -A competitive final-good sector aggregates a continuum of intermediate -varieties, - -$$ - -Y_t = \left[\int_0^1 (Y_{j,t})^{(\varepsilon-1)/\varepsilon}\, dj -\right]^{\varepsilon/(\varepsilon-1)}, -\qquad -Y_{j,t} = \left(\frac{P_t}{P_{j,t}}\right)^{\varepsilon} Y_t, - -$$ - -where $\varepsilon$ is the elasticity of substitution. - -Monopolistic intermediate producers buy labor services at price $\vartheta_t$ -and produce with the linear technology - -$$ - -Y_{j,t} = \exp(a_t)\, l_{j,t} - \phi, - -$$ - -where $a_t$ is log TFP and $\phi$ is a fixed cost. - -Each producer reoptimizes its price with probability $1 - \chi_p$ (Calvo), so -price setting is a dynamic problem and is distorted by the firm's subjective -beliefs. - -Goods and labor markets clear: - -$$ - -C_t + \frac{\kappa_v}{q_t}\, h_t L_{t-1} = Y_t, -\qquad -\int_0^1 l_{j,t}\, dj = L_t, - -$$ - -where $h_t = f_t (1 - \rho L_{t-1}) / L_{t-1}$ is the hiring rate and the -second term in the resource constraint is total vacancy-posting costs. - -#### Monetary policy and shocks - -The monetary authority follows a Taylor rule with smoothing, - -$$ - -\log \frac{R_t}{\bar R} -= \rho_r \log \frac{R_{t-1}}{\bar R} -+ (1 - \rho_r)\left[ -r_\pi \log \frac{\pi_t}{\bar\pi} + r_y \log \frac{Y_t}{Y^*} -\right] -+ \sigma_r w_t^r, - -$$ - -where $\pi_t = P_t / P_{t-1}$ is gross inflation, $\bar\pi$ is the intercept, -and $Y^*$ is steady-state output. - -TFP follows - -$$ - -a_{t+1} = \rho_a a_t + \sigma_a w_{t+1}^a. - -$$ - -The three innovations are independent under the data-generating measure $P$: - -$$ - -(w_t^r,\, w_t^a,\, w_t^\theta)' \overset{iid}{\sim} N(0, I). - -$$ - -Under the subjective measure they are not independent: the optimal distortion -ties their joint distribution to the current level of $\theta_t$, and this -subjective correlation structure is the source of the belief wedges. - -#### Where beliefs enter - -Every forward-looking equilibrium condition uses the subjective expectation -$\tilde E_t$: the household's consumption Euler equation, the worker and firm -match values, free entry, the bargaining problem, and the Calvo price setters' -first-order conditions. - -In the linearized solution, each of these conditions is one row of the system -$0 = \tilde E_t[g(x_{t+1}, x_t, x_{t-1}, w_{t+1}, w_t)]$ described in the -appendix, where the belief distortion can be switched on or off row by row. - -### Calibration - -The model is calibrated to quarterly U.S. data, 1982Q1–2019Q4. - -| Parameter | Symbol | Value | Description | -|---|---|---|---| -| Discount factor | $\beta$ | 0.994 | Quarterly | -| Elast. of substitution | $\varepsilon$ | 6 | Across intermediate goods | -| Price stickiness | $\chi_p$ | 0.75 | Calvo parameter | -| Wage rigidity | $\chi_w$ | 0.925 | Partial adjustment | -| Steady-state markup | $\lambda$ | 1.2 | | -| Policy-rule intercept | $\bar\pi$ | 0.01 | Targets 2% annual inflation | -| Policy-rule smoothing | $\rho_r$ | 0.84 | | -| Taylor-rule inflation loading | $r_\pi$ | 1.60 | | -| Taylor-rule output loading | $r_y$ | 0.028 | | -| Mean pessimism | $\mu_\theta$ | 5.64 | | -| Persistence of $\theta$ | $\rho_\theta$ | 0.714 | | -| Volatility of $\theta$ shock | $\sigma_\theta$ | 4.3 | | -| TFP persistence | $\rho_a$ | 0.840 | | -| TFP volatility | $100\sigma_a$ | 0.568 | | -| MP volatility | $100\sigma_r$ | 0.078 | | -| Job survival probability | $\rho$ | 0.89 | Separation rate $1-\rho=0.11$ | -| Matching efficiency | $\mu$ | 0.67 | | -| Matching-function curvature | $\nu$ | 0.72 | From {cite}`Shimer2005` | -| Worker bargaining weight | $\eta$ | 0.72 | From {cite}`Shimer2005` | -| Vacancy posting cost | $\kappa_v$ | 0.09 | | -| Unemployment benefit flow | $D$ | 0.57 | | - -The calibration has three blocks. - -Conventional steady-state targets pin down most parameters: a 1% annual real -return, a 20% markup with price spells of about three quarters, an 11% -quarterly job separation rate, a job-finding rate of 0.67, a flow value of -unemployment equal to 70% of wages, and steady-state labor market tightness -equal to one. - -The TFP process is estimated from measured TFP, and the wage rigidity and -monetary policy parameters are chosen so that responses of inflation and -unemployment to TFP and monetary policy shocks match VAR evidence. - -Matching these responses matters because the wedge formula above makes belief -distortions proportional to covariances of shocks with continuation values: -the model has to propagate fundamental shocks correctly before its belief -wedges can be meaningfully compared with the survey data. - -The belief-shock parameters are read off the survey wedges: -$\rho_\theta = 0.714$ is the autocorrelation of the first principal component -of the wedges, and $\mu_\theta$ and $\sigma_\theta$ fit the means and -volatilities of the two wedges. - -### A self-contained linear surrogate - -Solving the full structural model requires the series expansion method -described in the appendix. - -For computation in the main text, we use a small reduced-form vector -autoregression that is calibrated to reproduce the main benchmark moments in -the moment table reported below and the qualitative shape of the -belief-shock impulse responses: +Rather than solve that full system here, we use a small linear surrogate that +keeps only the pieces needed for transparent IRFs: $$ @@ -1319,28 +1139,16 @@ where $s_t = (u_t, \pi_t, y_t, \theta_t, a_t)'$ collects unemployment, inflation, output, the belief shock, and TFP, and $\epsilon_{t+1} \sim N(0, I_3)$ contains the three structural shocks. -The coefficient matrices $A$ and $B$ are not the full structural solution. - -A full structural solution would obtain them from equilibrium conditions: -households and firms distort probabilities according to continuation values, -and those distorted beliefs feed back into consumption, price setting, vacancy -posting, and wages. +The belief shock follows the persistence estimated from the survey wedges, +$\rho_\theta = 0.714$. The two wedge loadings are chosen so that +$c_u \mu_\theta = 0.52$ and $c_\pi \mu_\theta = 1.22$ at +$\mu_\theta = 5.64$. -Here we compress those equilibrium channels into a few linear loadings. - -The loadings are chosen so that the volatilities of unemployment, inflation, -and output, computed from the Lyapunov equation, match the benchmark and -no-belief-shock columns of the moment table in the next section. - -The volatility match is essentially exact; conditional objects, such as -impact magnitudes and correlations with output, are only qualitatively right. - -This surrogate is useful for transparent computations, but it should not be -used to analyse the diagnostic variants such as "only $\theta_t$", no TFP -shocks, or rational firms. - -For those variants, the relevant structural moments are reported directly -below. +The entries that connect $\theta_t$ to $u_t$, $\pi_t$, and $y_t$ should be +read as reduced-form summaries of the full subjective-expectations channel. +They are calibrated to give the right signs and a reasonable scale for the +belief-shock IRF: higher pessimism raises unemployment, temporarily raises +inflation, and lowers output. We index the five state variables with named constants, so that later code can refer to, say, the belief shock as `I_THETA` rather than a bare number. @@ -1350,18 +1158,8 @@ refer to, say, the belief shock as `I_THETA` rather than a bare number. I_U, I_PI, I_Y, I_THETA, I_A = 0, 1, 2, 3, 4 ``` -The factory `create_nk_model` builds the transition matrix $A$ and the shock -loadings $B$ from the calibration values stated above. - -The belief shock $\theta_t$ and TFP $a_t$ follow AR(1) processes; the -endogenous variables inherit their own persistence and load on $\theta_t$, -$a_t$, and the monetary policy shock. - -In the structural model, the effects of $\theta_t$ arise because pessimism -changes the subjective distribution of the fundamental shocks. - -In the surrogate, these effects are summarized by the coefficients -$\phi_{u\theta}$, $\phi_{\pi\theta}$, and $\phi_{y\theta}$. +The object below stores the transition matrix, shock loadings, and the two +wedge loadings. That is enough to compute the impulse responses. ```{code-cell} ipython3 class NKModel(NamedTuple): @@ -1373,7 +1171,7 @@ class NKModel(NamedTuple): def create_nk_model(): """Build the pedagogical reduced-form NK model (state and shock matrices).""" - # Exogenous-process parameters from the calibration table above. + # Exogenous-process parameters from bhandari2025survey. ρ_θ, σ_θ = 0.714, 4.3 ρ_a, σ_a = 0.840, 0.00568 @@ -1437,119 +1235,22 @@ def irf(model, shock_idx, T=25): return resp, wu, w_π ``` -For the unconditional moments we simulate the model and, separately, solve the -discrete Lyapunov equation $\Sigma = A\Sigma A' + BB'$ for the stationary -covariance. - -Passing `include_θ_shock=False` zeros out the belief shock, which isolates the -contribution of the TFP and monetary policy shocks. - -```{code-cell} ipython3 -def simulate_nk(model, T=200, seed=42): - """Simulate the model for T periods under the data-generating measure.""" - rng = np.random.default_rng(seed) - A, B = model.A, model.B - k = B.shape[1] - s = np.zeros((A.shape[0], T)) - for t in range(1, T): - s[:, t] = A @ s[:, t-1] + B @ rng.standard_normal(k) - return s - - -def unconditional_stds(model, include_θ_shock=True): - """Unconditional standard deviations from the discrete Lyapunov equation.""" - B_use = model.B.copy() - if not include_θ_shock: - B_use[:, 0] = 0.0 # shut down the belief shock - Σ = solve_discrete_lyapunov(model.A, B_use @ B_use.T) - return np.sqrt(np.diag(Σ)) -``` - ```{code-cell} ipython3 nk = create_nk_model() ``` ## Quantitative results -### Benchmark moments and model targets - -The table below collects the empirical moments and model moments most relevant -for this lecture. - -All values are percentages or percentage points; inflation is annualized and -output is detrended. - -| Moment | Data | Benchmark | No $\theta_t$ | Only $\theta_t$ | -|---|---:|---:|---:|---:| -| Mean inflation wedge | 1.22 | 0.90 | 0.00 | 0.00 | -| Mean unemployment wedge | 0.52 | 0.55 | 0.00 | 0.00 | -| Volatility of inflation wedge | 0.97 | 0.73 | 0.00 | 0.00 | -| Volatility of unemployment wedge | 0.57 | 0.45 | 0.00 | 0.00 | -| Volatility of inflation | 1.37 | 1.16 | 0.99 | 0.00 | -| Volatility of output | 2.00 | 2.22 | 1.55 | 0.00 | -| Volatility of unemployment | 1.70 | 1.39 | 0.55 | 0.00 | -| Corr. inflation wedge, output | −0.30 | −0.67 | 0.00 | 0.00 | -| Corr. unemployment wedge, output | −0.49 | −0.67 | 0.00 | 0.00 | -| Corr. inflation, output | 0.10 | −0.82 | −0.85 | 0.00 | -| Corr. unemployment, output | −0.87 | −0.74 | −0.33 | 0.00 | - -Two lessons are important. - -First, shutting down the belief shock returns the familiar unemployment -volatility puzzle: unemployment volatility falls from 1.39 in the benchmark to -0.55, far below the data value of 1.70. - -Second, the "only $\theta_t$" column is zero in the structural model. - -If TFP and monetary policy uncertainty are absent, there is no payoff-relevant -uncertainty for pessimistic agents to distort, so time variation in -$\theta_t$ alone does not move the economy. - -This is why the benchmark emphasizes the interaction between belief shocks and -fundamental shocks, especially TFP shocks. - -This point is also why the reduced-form surrogate should be read carefully. - -In the structural model, $\theta_t$ changes how agents weight fundamental -shocks. - -In the surrogate, that interaction is compressed into direct loadings from -$\theta_t$ to unemployment, inflation, and output. - -Thus the surrogate is useful for the benchmark and no-belief-shock comparisons, -but the diagnostic columns in the table are structural results rather than -additional simulations of the surrogate. - ### Impulse responses to the belief shock A positive innovation to $\theta_t$ makes households more pessimistic. -The -mechanism works this way: - -1. Households put more subjective probability on futures with low - productivity, tight monetary policy, and continued pessimism, so they cut - current consumption to smooth the bad times they anticipate. -2. Firms, who share these beliefs, mark down their valuation of new matches; - vacancy posting and the job-finding rate fall, output falls, and - unemployment rises. -3. Price setters expect lower future productivity and hence higher marginal - costs, so they resist cutting prices despite the fall in demand. -4. The belief wedges jump on impact, then decay with the persistence - $\rho_\theta = 0.714$. - -In the structural benchmark, a one-standard-deviation belief shock raises -unemployment by about one percentage point and lowers output by about one -percent. +In the full structural model, higher pessimism makes households and firms act +as if bad future states are more likely. Vacancy posting weakens, output +falls, unemployment rises, and the two survey wedges jump together. -Inflation rises briefly and then falls, leaving a roughly zero cumulative -10-quarter response. - -The contraction is sizable: the cumulative 10-quarter output loss is about -two-thirds of the loss following a one-standard-deviation drop in TFP. - -The reduced-form impulse responses below are calibrated to reproduce these -signs and the volatility scale in the moment table above. +The reduced-form system below is calibrated to reproduce those signs and to +make the belief wedges decay with $\rho_\theta = 0.714$. ```{code-cell} ipython3 --- @@ -1587,39 +1288,12 @@ plt.tight_layout() plt.show() ``` -This figure has six panels. - -The first row plots the macroeconomic responses of unemployment, inflation, -and output to a one-standard-deviation innovation in the belief shock. +The first row shows the macroeconomic responses, and the second row shows the +belief shock and the two implied survey wedges. -The second row plots the belief shock itself and the two implied survey wedges. - -The impulse responses show that a belief shock: - -* Raises unemployment persistently. -* Raises inflation temporarily, with the response gradually decaying back to - zero in this reduced-form representation. -* Lowers output, so the shock looks like a pessimistic recessionary force. -* Generates belief wedges for both unemployment and inflation that closely - mirror the dynamics of $\theta_t$ itself --- consistent with the one-factor - structure. - -The structural model contains one additional object that the surrogate does not -capture: impulse responses under the **subjective** measure. - -After a positive $\theta_t$ innovation, pessimistic agents behave as if future -TFP shocks are worse, monetary policy shocks are tighter, and future -pessimism is more persistent than under the data-generating measure. - -Agents distort the TFP shock more than the monetary policy shock, because TFP -matters more for their continuation values. - -Under the subjective measure, households expect consumption to fall further -and recover far more slowly than it actually does, and they expect inflation -to remain persistently high. - -That subjective correlation structure is what makes both unemployment and -inflation forecasts biased upward. +The shock raises unemployment, lowers output, and generates comoving +unemployment and inflation wedges. Inflation rises temporarily in this +calibration, then decays back toward zero. ### The unemployment volatility puzzle @@ -1632,6 +1306,27 @@ unemployment volatility of only 0.55, compared to 1.70 in the data. Adding the belief shock substantially closes the gap: +```{code-cell} ipython3 +def simulate_nk(model, T=200, seed=42): + """Simulate the model for T periods under the data-generating measure.""" + rng = np.random.default_rng(seed) + A, B = model.A, model.B + k = B.shape[1] + s = np.zeros((A.shape[0], T)) + for t in range(1, T): + s[:, t] = A @ s[:, t-1] + B @ rng.standard_normal(k) + return s + + +def unconditional_stds(model, include_θ_shock=True): + """Unconditional standard deviations from the discrete Lyapunov equation.""" + B_use = model.B.copy() + if not include_θ_shock: + B_use[:, 0] = 0.0 # shut down the belief shock + Σ = solve_discrete_lyapunov(model.A, B_use @ B_use.T) + return np.sqrt(np.diag(Σ)) +``` + ```{code-cell} ipython3 --- mystnb: @@ -1649,7 +1344,7 @@ scale = [100, 400, 100] # convert to pp (unemployment, annualized inflation, std_full_scaled = [std_full[i] * scale[j] for j, i in enumerate(idx)] std_no_θ_scaled = [std_no_θ[i] * scale[j] for j, i in enumerate(idx)] -# Data values from the benchmark moment table above. +# Data standard deviations reported by bhandari2025survey. data_std = [1.70, 1.37, 2.00] # unemployment, inflation, output x = np.arange(len(labels_vol)) @@ -2073,9 +1768,9 @@ $$ F = \bar\theta, \qquad -H = -(V_x \psi_w)', +H = -(v_x \psi_w)', \qquad -\bar H = -\bar\theta\,\bar x\,(V_x \psi_w)'. +\bar H = -\bar\theta\,\bar x\,(v_x \psi_w)'. $$ @@ -2156,9 +1851,9 @@ mystnb: ψ_x_sc = np.array([[model.ρ_x]]) ψ_w_sc = np.array([[model.σ_x]]) F_sc = np.array([[model.μ_θ]]) # θ-bar -H_sc = np.array([[-model.Vx * model.σ_x]]) # -(Vx ψ_w)' +H_sc = np.array([[-model.vx * model.σ_x]]) # -(vx ψ_w)' x_bar_sc = 1.0 -H_bar_sc = -model.μ_θ * x_bar_sc * np.array([[model.Vx * model.σ_x]]) +H_bar_sc = -model.μ_θ * x_bar_sc * np.array([[model.vx * model.σ_x]]) τ_max = 20 wc, ws = compute_tau_wedge_loadings(ψ_x_sc, ψ_w_sc, H_sc, H_bar_sc, F_sc, τ_max) @@ -2244,13 +1939,13 @@ This ensures that the deterministic steady state does not collapse to the rational-expectations solution. -Guessing $V_{1t} = V_x x_{1t} + V_q$ and matching coefficients yields -the **Riccati equation for $V_x$**: +Guessing $v_{1t} = v_x x_{1t} + v_q$ and matching coefficients yields +the **Riccati equation for $v_x$**: $$ -V_x = u_x - \frac{\beta}{2}\, V_x \psi_w \psi_w' V_x' \bar\theta - + \beta\, V_x \psi_x, +v_x = u_x - \frac{\beta}{2}\, v_x \psi_w \psi_w' v_x' \bar\theta + + \beta\, v_x \psi_x, $$ @@ -2258,18 +1953,18 @@ and the constant $$ -V_q = u_q - \frac{\beta}{2}\,\bar\theta\, \bar x\, - V_x \psi_w \psi_w' V_x' + \beta\, V_x \psi_q + \beta V_q. +v_q = u_q - \frac{\beta}{2}\,\bar\theta\, \bar x\, + v_x \psi_w \psi_w' v_x' + \beta\, v_x \psi_q + \beta v_q. $$ -The Riccati equation is quadratic in $V_x$. +The Riccati equation is quadratic in $v_x$. For the stationary scalar case it reduces to $$ -a\, V_x^2 + b\, V_x + c = 0, +a\, v_x^2 + b\, v_x + c = 0, \qquad a = \frac{\beta}{2}\sigma_x^2 \bar\theta,\quad b = 1 - \beta\rho_x,\quad @@ -2288,7 +1983,7 @@ as follows: $$ w_{t+1} \;\sim\; -N\!\left(-\bar\theta(\bar x + x_{1t})(V_x \psi_w)',\; I_k\right). +N\!\left(-\bar\theta(\bar x + x_{1t})(v_x \psi_w)',\; I_k\right). $$ @@ -2298,7 +1993,7 @@ $$ \Delta_t^{(1)} = \tilde E_t[x_{t+1}] - E_t[x_{t+1}] = \psi_w\, \tilde E_t[w_{t+1}] -= -\bar\theta(\bar x + x_{1t})(\psi_w \psi_w') V_x'. += -\bar\theta(\bar x + x_{1t})(\psi_w \psi_w') v_x'. $$ @@ -2345,12 +2040,12 @@ $$ \mathbb{E} = \operatorname{stack}\Bigl\{ \sigma_i\, [g_{x^+}\psi_w + g_{w^+}]^i\, - (V_x \psi_w)'\, \bar\theta + (v_x \psi_w)'\, \bar\theta \Bigr\}. $$ -These equations are solved jointly with the Riccati equation for $V_x$. +These equations are solved jointly with the Riccati equation for $v_x$. Compared with the standard Blanchard–Kahn solution, the only modification is the additive term $-\mathbb{E}$ that shifts the @@ -2388,14 +2083,14 @@ distant terminal date $T$ (where belief distortions vanish) back to the present. The continuation value in the $f$-direction satisfies a separate recursion -for $V_f$, and the belief distortion matrix becomes +for $v_f$, and the belief distortion matrix becomes $$ \mathbb{E} = \operatorname{stack}\Bigl\{ \sigma_i\bigl[ - g_{x^+}\psi_{xf}\sigma_f^2(V_f + V_x\psi_{xf}) - + (g_{x^+}\psi_w + g_{w^+})\psi_w' V_x' + g_{x^+}\psi_{xf}\sigma_f^2(v_f + v_x\psi_{xf}) + + (g_{x^+}\psi_w + g_{w^+})\psi_w' v_x' \bigr]^i \Bigr\}\bar\theta_f. @@ -2407,8 +2102,8 @@ The algorithm therefore decomposes cleanly into two stages: $\psi_x$, $\psi_w$ using the standard Blanchard–Kahn method; these are *unaffected* by the belief shock. -2. **Stage 2 (belief distortion block)**: given $\psi_x, \psi_w, V_x$, - iterate backward to convergence to find $\psi_{xf}$, $V_f$, and +2. **Stage 2 (belief distortion block)**: given $\psi_x, \psi_w, v_x$, + iterate backward to convergence to find $\psi_{xf}$, $v_f$, and $\mathbb{E}$. This separation is a major practical advantage: existing rational-expectations @@ -2429,33 +2124,33 @@ from the re-centred shocks. u_x = 1 - model.β # Stage 2 value block: iterate on the Riccati equation -Vx_iter = u_x / (1 - model.β * ψ_x_re) # start at the RE value +vx_iter = u_x / (1 - model.β * ψ_x_re) # start at the RE value for it in range(1, 200): - Vx_new = (u_x + model.β * ψ_x_re * Vx_iter - - 0.5 * model.β * model.μ_θ * ψ_w_re**2 * Vx_iter**2) - if abs(Vx_new - Vx_iter) < 1e-15: + vx_new = (u_x + model.β * ψ_x_re * vx_iter + - 0.5 * model.β * model.μ_θ * ψ_w_re**2 * vx_iter**2) + if abs(vx_new - vx_iter) < 1e-15: break - Vx_iter = Vx_new + vx_iter = vx_new -print(f"Riccati by iteration ({it} steps): Vx = {Vx_iter:.10f}") -print(f"Riccati by quadratic formula: Vx = {model.Vx:.10f}") +print(f"Riccati by iteration ({it} steps): v_x = {vx_iter:.10f}") +print(f"Riccati by quadratic formula: v_x = {model.vx:.10f}") # Subjective transition coefficients implied by the re-centred shocks -ψ_x_subj = ψ_x_re - model.μ_θ * Vx_iter * ψ_w_re**2 -ψ_q_subj = -model.μ_θ * 1.0 * Vx_iter * ψ_w_re**2 # x̄ normalized to 1 +ψ_x_subj = ψ_x_re - model.μ_θ * vx_iter * ψ_w_re**2 +ψ_q_subj = -model.μ_θ * 1.0 * vx_iter * ψ_w_re**2 # steady state x_bar = 1 -print(f"\nObjective persistence: ψ_x = {ψ_x_re:.8f}") -print(f"Subjective persistence: ψ̃_x = {ψ_x_subj:.8f}") -print(f"Subjective drift: ψ̃_q = {ψ_q_subj:.2e}") +print(f"\nObjective persistence: ψ_x = {ψ_x_re:.8f}") +print(f"Subjective persistence: ψ_x_subj = {ψ_x_subj:.8f}") +print(f"Subjective drift: ψ_q_subj = {ψ_q_subj:.2e}") ``` The two solution methods agree. -For this consumption-type state ($V_x > 0$), the subjective persistence is +For this consumption-type state ($v_x > 0$), the subjective persistence is slightly *below* the objective one and the subjective drift is negative: the pessimist expects good states to fade and bad outcomes to arrive. -For an unemployment-type state ($V_x < 0$), both signs flip, so subjective +For an unemployment-type state ($v_x < 0$), both signs flip, so subjective persistence exceeds objective persistence --- the formal statement of "pessimists believe bad times last longer". @@ -2481,7 +2176,7 @@ The agent's problem is $$ -V_t^* = \max_{\{y_{t+j}\}} \min_{\substack{m_{t+j}>0 \\ E_{t+j-1}[m_{t+j}]=1}} +v_t^* = \max_{\{y_{t+j}\}} \min_{\substack{m_{t+j}>0 \\ E_{t+j-1}[m_{t+j}]=1}} \sum_{j=0}^{\infty} \beta^j E_t[M_{t,t+j} u_{t+j}] + \mathcal{E}_t. @@ -2510,27 +2205,27 @@ function satisfies the recursive form stated in the main lecture: $$ -V_t^* = \max_{y_t} \min_{\substack{m_{t+1}>0 \\ E_t[m_{t+1}]=1}} +v_t^* = \max_{y_t} \min_{\substack{m_{t+1}>0 \\ E_t[m_{t+1}]=1}} u_t + \frac{\beta}{\theta_t} E_t[m_{t+1} \log m_{t+1}] - + \beta E_t[m_{t+1} V_{t+1}^*]. + + \beta E_t[m_{t+1} v_{t+1}^*]. $$ ```{code-cell} ipython3 θ_path = np.array([3.0, 5.64, 8.0, 12.0]) # rising pessimism scenario -def one_period_entropy(θ, Vx, σ_x): +def one_period_entropy(θ, vx, σ_x): """ Entropy E_t[m_{t+1} log m_{t+1}] under the optimal distortion - for Gaussian shocks: = (1/2) * (θ * Vx * σ_x)^2. + for Gaussian shocks: = (1/2) * (θ * vx * σ_x)^2. """ - return 0.5 * (θ * Vx * σ_x) ** 2 + return 0.5 * (θ * vx * σ_x) ** 2 print("Effect of time-varying θ on entropy and belief wedge:") print(f"{'θ_t':>8} {'H_t (entropy)':>16} {'Δ(x) = σ_x ν_t (pp)':>22}") print('-' * 52) for th in θ_path: - H = one_period_entropy(th, model.Vx, model.σ_x) + H = one_period_entropy(th, model.vx, model.σ_x) bw = belief_wedge(model, th) * 100 print(f"{th:>8.2f} {H:>16.6f} {bw:>22.4f}") @@ -2554,7 +2249,7 @@ $\theta_t$ makes agents overweight states with low continuation value. With Gaussian shocks, the optimal change of measure is especially simple: it shifts the mean of the innovation by -$-\theta_t (V_x \psi_w)'$. +$-\theta_t (v_x \psi_w)'$. This mean shift implies belief wedges that are proportional to $\theta_t$ and to the covariance between shocks and continuation values. @@ -2579,7 +2274,7 @@ In the simple endowment economy built by `create_belief_model`, suppose the state variable is log consumption $x_t$ with $\rho_x = 0.90$, $\sigma_x = 0.01$, $\beta = 0.99$. -1. Compute $V_x$ under rational expectations and under pessimism +1. Compute $v_x$ under rational expectations and under pessimism $\mu_\theta = 4$. 2. What is the sign of the belief wedge for consumption growth? 3. If instead the agent forecasts unemployment (which enters the value @@ -2593,11 +2288,11 @@ $\beta = 0.99$. :class: dropdown ``` -**Part (a)** --- Under rational expectations ($\theta = 0$): +*Part 1.* Under rational expectations ($\theta = 0$): $$ -V_x^{RE} = \frac{u_x}{1 - \beta \rho_x} +v_x^{RE} = \frac{u_x}{1 - \beta \rho_x} = \frac{1 - \beta}{1 - \beta \rho_x}. $$ @@ -2608,35 +2303,31 @@ $$ σ_x_ex = 0.01 μ_θ_ex = 4.0 -Vx_re_ex = (1 - β_ex) / (1 - β_ex * ρ_x_ex) -print(f"V_x (rational expectations): {Vx_re_ex:.4f}") +vx_re_ex = (1 - β_ex) / (1 - β_ex * ρ_x_ex) +print(f"v_x (rational expectations): {vx_re_ex:.4f}") m_ex = create_belief_model(β=β_ex, ρ_x=ρ_x_ex, σ_x=σ_x_ex, μ_θ=μ_θ_ex) -print(f"V_x (with pessimism θ_bar={μ_θ_ex}): {m_ex.Vx:.4f}") +print(f"v_x (with pessimism θ_bar={μ_θ_ex}): {m_ex.vx:.4f}") ``` -**Part (b)** --- The belief wedge for consumption growth is +*Part 2.* Under rational expectations ($\theta = 0$): $$ - \Delta_t^{(1)}(x) -= -\theta_t V_x \sigma_x^2. - += -\theta_t v_x \sigma_x^2. $$ -Since $V_x > 0$ and $\theta_t > 0$, the wedge is **negative**: pessimistic +Since $v_x > 0$ and $\theta_t > 0$, the wedge is **negative**: pessimistic agents underestimate consumption growth relative to the model. -**Part (c)** --- For unemployment, $u_x < 0$, so $V_x^u < 0$. +*Part 3.* For unemployment, $u_x < 0$, so $v_x^u < 0$. The belief wedge becomes $$ - \Delta_t^{(1)}(u) -= -\theta_t V_x^u \sigma_x^2 > 0 - += -\theta_t v_x^u \sigma_x^2 > 0 $$ (positive, because pessimism makes agents over-estimate unemployment). @@ -2707,9 +2398,10 @@ inherits this relationship and rises with $\rho_\theta$. Using the reduced-form NK model built by `create_nk_model`: -(a) Compute the fraction of unemployment variance explained by each of the +1. Compute the fraction of unemployment variance explained by each of the three shocks. -(b) Show that the belief shock is the dominant driver of unemployment + +2. Show that the belief shock is the dominant driver of unemployment fluctuations, while TFP shocks matter much more for inflation and output than they do for unemployment. ```{exercise-end} @@ -2764,12 +2456,12 @@ for most of the variation in inflation. **Changing the degree of pessimism** -Solve the Riccati equation (`solve_Vx`) for a grid of +Solve the Riccati equation (`solve_vx`) for a grid of $\mu_\theta$ values from 0 (rational expectations) to 15. For each value, -compute the steady-state (unconditional mean) belief wedge and the ratio of -robust to rational $V_x$. +compute the normalized subjective drift $\nu / \sigma_x = -\mu_\theta v_x$ +and the steady-state belief wedge. Discuss how the robust value function differs from the rational-expectations value function. @@ -2783,25 +2475,21 @@ the rational-expectations value function. ```{code-cell} ipython3 μ_grid = np.linspace(0, 15, 100) -Vx_vals = [] +drift_norm = [] wedge_ss = [] -Vx_re = (1 - 0.994) / (1 - 0.994 * 0.85) - for μ in μ_grid: m_temp = create_belief_model(μ_θ=μ) - Vx_vals.append(m_temp.Vx) + drift_norm.append(-μ * m_temp.vx) wedge_ss.append(belief_wedge(m_temp, μ) * 100) # in pp fig, axes = plt.subplots(1, 2, figsize=(11, 4)) -fig.suptitle('Value sensitivity and steady-state wedge') +fig.suptitle('Subjective drift and steady-state wedge') -axes[0].plot(μ_grid, Vx_vals, color='steelblue', linewidth=2) -axes[0].axhline(Vx_re, color='grey', linestyle='--', - label=f'RE value $V_x^{{RE}}={Vx_re:.3f}$') +axes[0].plot(μ_grid, drift_norm, color='steelblue', linewidth=2) +axes[0].axhline(0, color='grey', linestyle='--', linewidth=0.8) axes[0].set_xlabel('mean pessimism $\\mu_\\theta$') -axes[0].set_ylabel('$V_x$') -axes[0].legend() +axes[0].set_ylabel('normalized drift $\\nu / \\sigma_x$') axes[1].plot(μ_grid, np.array(wedge_ss), color='firebrick', linewidth=2) axes[1].set_xlabel('mean pessimism $\\mu_\\theta$') @@ -2811,21 +2499,17 @@ plt.tight_layout(rect=[0, 0, 1, 0.94]) plt.show() ``` -The left panel plots the solved value-function slope $V_x$ against the mean -pessimism parameter $\mu_\theta$. - -The dashed horizontal line is the rational-expectations benchmark. +The left panel plots the normalized subjective drift +$\nu / \sigma_x = -\mu_\theta v_x$. The right panel plots the corresponding steady-state belief wedge. -As $\mu_\theta$ rises, the Riccati equation introduces an additional -curvature term that lowers $V_x$ (less marginal value to the current state) -because the agent effectively prices in the possibility of bad future -outcomes. +The normalized drift is the horizontal shift used in the shock-distribution +figure above, so it is easier to see than the tiny movement in $v_x$ itself. The steady-state consumption wedge becomes more negative, approximately linearly in magnitude, since -$\Delta^{(1)} \propto -\mu_\theta V_x \sigma_x^2$ and $V_x$ is approximately +$\Delta^{(1)} \propto -\mu_\theta v_x \sigma_x^2$ and $v_x$ is approximately constant for small $\mu_\theta$. ```{solution-end} From 738e15527c330b643f3b72f42398441df3793b4b Mon Sep 17 00:00:00 2001 From: HumphreyYang Date: Fri, 12 Jun 2026 14:19:11 +1000 Subject: [PATCH 24/25] updates --- .../subjective_beliefs_business_cycles.md | 310 ++++++++++++++---- 1 file changed, 241 insertions(+), 69 deletions(-) diff --git a/lectures/subjective_beliefs_business_cycles.md b/lectures/subjective_beliefs_business_cycles.md index cd5d6452..ad1b0193 100644 --- a/lectures/subjective_beliefs_business_cycles.md +++ b/lectures/subjective_beliefs_business_cycles.md @@ -384,13 +384,17 @@ The points form an upward-sloping cloud rather than a line: a common pessimism factor drives both wedges, while survey noise and other idiosyncratic variation keep them from being collinear. -(The principal-component share is computed from the raw wedges, so it -reflects both their correlation and the larger variance of the inflation -wedge.) +The reported PC1 share of 0.809 is computed from the raw covariance matrix of +the two replicated wedges, so it reflects both their correlation and the +larger variance of the inflation wedge. + +{cite:t}`bhandari2025survey` report 78.6% under their preferred +normalization; depending on the normalization, the first component explains +roughly 79--81% of the joint variation. ### Empirical facts -The figures above shows three key empirical facts about the belief wedges: +The figures above show three key empirical facts about the belief wedges: - Both wedges are **positive on average**: households expect higher unemployment and higher inflation than the rational forecast. @@ -416,19 +420,44 @@ pessimism/optimism component rather than two unrelated forecast mistakes. These moments are the calibration targets for the belief shock $\theta_t$, the pessimism parameter formalized in the next section. +The code below also defines two *wedge loadings*, $c_u$ and $c_\pi$, that the +model illustrations later in the lecture use to map $\theta_t$ into wedges. + +In the full model these loadings are endogenous equilibrium objects, +covariances between shocks and continuation values, but here we set them +directly so that the implied wedges equal the empirical means of 0.52 and +1.22 pp at $\theta_t = \mu_\theta$. + ```{code-cell} ipython3 # Belief-shock calibration from the paper μ_θ = 5.64 # mean of belief-shock parameter θ ρ_θ = 0.714 # AR(1) persistence: autocorrelation of the wedges' first PC σ_θ = 4.3 # innovation volatility -# Wedge loadings used later in the model illustrations. +# Wedge loadings used later in the model illustrations +# In the full model these are equilibrium objects c_u = 0.52 / μ_θ c_π = 1.22 / μ_θ ``` ## A model of pessimism +Before turning to the theory, the table below collects the notation used in +the rest of the lecture. + +| Symbol | Meaning | +|---|---| +| $x_t$ | state (a vector in general; log consumption in the scalar example) | +| $\bar x$ | steady state of the state; $x_{1t}$ is the first-order deviation from $\bar x$ | +| $w_{t+1}$ | standard normal innovation under the data-generating measure | +| $m_{t+1}$ | likelihood ratio that distorts the data-generating measure | +| $\theta_t$ | belief factor: $\theta_t > 0$ is pessimism, $\theta_t < 0$ is optimism | +| $\bar\theta$ | loading of the belief factor on the state, $\theta_t = \bar\theta x_t$ | +| $\mu_\theta$, $\rho_\theta$, $\sigma_\theta$ | mean ($\mu_\theta = \bar\theta \bar x$), persistence, and innovation volatility of $\theta_t$ | +| $v_t$, $v_x$, $v_q$ | continuation value, its slope in the state, and its constant term | +| $\nu_t$ | subjective mean shift of the innovation $w_{t+1}$ | +| $\Delta_t^{(\tau)}(z)$ | $\tau$-period belief wedge for variable $z$ | + ### Robust preferences Why would households have systematically biased beliefs? @@ -507,7 +536,9 @@ $$ which replaces the expected continuation value with a soft minimum: as $\theta_t \to 0$ it reduces to $u(x_t) + \beta E_t[v_{t+1}]$, and as -$\theta_t \to \infty$ it approaches the worst case over states. +$\theta_t \to \infty$ it approaches the worst case over states in a bounded +or finite-state setting (with unbounded Gaussian shocks the soft minimum +instead falls without bound). In the robust-control literature, this distortion expresses fear of model misspecification. @@ -580,12 +611,21 @@ The calculation has four steps: 1. guess an affine continuation value; 2. evaluate the risk-sensitive recursion in closed form; 3. derive the optimal belief distortion and the wedge it implies; -4. solve for the value-function slope. +4. solve for the value-function slope, first with a fixed $\theta$ and then + with a state-dependent $\theta_t$. + +Steps 1--3 treat the current value of $\theta_t$ as given; they go through +unchanged whatever that value is. With linear dynamics, Gaussian shocks, and linear utility, the continuation value is affine in the state, so we guess $v_t = v_x x_t + v_q$ and verify the guess by substitution. +(The affine guess with constant coefficients is exact when $\theta$ is +constant; when $\theta_t$ varies over time, it is the first-order +approximation of {cite:t}`bhandari2025survey`, whose perturbation method the +appendix describes.) + The slope $v_x = \partial v_t / \partial x_t$ measures how much the agent values an extra unit of the state. @@ -650,12 +690,49 @@ consumption. For a variable that enters the value function with a negative sign, such as unemployment, the same pessimism generates a *positive* wedge. -To pin down the stationary slope used in the code, evaluate the -recursion at the mean pessimism level $\mu_\theta$. +It remains to pin down the slope $v_x$ (Step 4), and here the distinction +between a fixed and a state-dependent pessimism parameter matters. + +*Case 1: fixed $\theta$.* + +Suppose first that $\theta_t = \theta$ is a constant. + +Substituting the closed-form recursion back into the Bellman equation gives + +$$ +v_x x_t + v_q += (1 - \beta)\, x_t ++ \beta\left(v_x \rho_x x_t + v_q - \frac{\theta}{2}\, v_x^2 \sigma_x^2\right). +$$ + +The variance penalty $-\frac{\theta}{2} v_x^2 \sigma_x^2$ does not involve +$x_t$, so matching coefficients on $x_t$ gives the rational-expectations +slope $v_x = u_x / (1 - \beta\rho_x)$ with $u_x = 1 - \beta$, while the +penalty only lowers the constant $v_q$. -Substituting the closed-form recursion back into -$v_t = u(x_t) - \frac{\beta}{\mu_\theta} -\log E_t[\exp(-\mu_\theta v_{t+1})]$ and matching coefficients on $x_t$ +With constant pessimism, the agent tilts beliefs, but the marginal value of +the state is unchanged. + +*Case 2: state-dependent $\theta_t$.* + +Now suppose, as in {cite:t}`bhandari2025survey`, that pessimism moves with +the state, + +$$ +\theta_t = \bar\theta(\bar x + x_t), +$$ + +where $\bar x$ is the steady state and $x_t$ the deviation from it; we +normalize $\bar x = 1$, so that the steady-state pessimism level is +$\mu_\theta = \bar\theta \bar x = \bar\theta$. + +The variance penalty is now proportional to the state, + +$$ +-\frac{\beta}{2}\,\bar\theta(\bar x + x_t)\, v_x^2 \sigma_x^2, +$$ + +so it contributes to the coefficient on $x_t$, and matching coefficients yields the **Riccati equation** $$ @@ -663,12 +740,19 @@ v_x = u_x + \beta \rho_x v_x - \frac{\beta}{2}\,\mu_\theta\, \sigma_x^2 v_x^2, \qquad u_x = 1 - \beta. $$ -The quadratic term is the price of pessimism: it lowers the marginal value of -the state relative to the rational-expectations value +The quadratic term is the price of state-dependent pessimism: it lowers the +marginal value of the state relative to the rational-expectations value $v_x^{RE} = u_x / (1 - \beta\rho_x)$. +If $\theta_t$ were fixed, the same variance penalty would affect only the +constant term, as in Case 1; the Riccati term in the slope exists precisely +because pessimism varies with the state. + We now turn this illustration into code, building it up from small pieces. +Because the quantitative model uses the state-dependent specification, the +code implements Case 2. + The first ingredient is the slope $v_x$ of the continuation value. It solves the scalar Riccati equation, which we write as a quadratic @@ -746,6 +830,12 @@ def belief_wedge(model, θ): A last helper simulates the AR(1) belief shock $\theta_t$. +This is a third specification of pessimism, distinct from the fixed $\theta$ +of Case 1 and the state-dependent $\theta_t$ of Case 2: the quantitative +model treats $\theta_t$ as an exogenous AR(1) process calibrated to the +survey wedges, and the appendix shows how it fits into the perturbation +solution. + ```{code-cell} ipython3 def simulate_θ(model, T=200, seed=42): """Simulate the AR(1) belief shock θ_t.""" @@ -767,17 +857,40 @@ drift and wedge. model = create_belief_model() vx_re = (1 - model.β) / (1 - model.β * model.ρ_x) -print(f"RE value of v_x: {vx_re:.4f}") -print(f"Robust value of v_x: {model.vx:.4f}") -print(f"Belief drift at θ_bar: {belief_drift(model, model.μ_θ) * 100:.4f} pp") -print(f"Belief wedge at θ_bar: {belief_wedge(model, model.μ_θ) * 100:.4f} pp") +print(f"RE value of v_x: {vx_re:.8f}") +print(f"Robust value of v_x: {model.vx:.8f}") +print(f"Belief drift at θ_bar: ν = {belief_drift(model, model.μ_θ):.5f} " + "(standard deviations of w)") +print(f"Belief wedge at θ_bar: Δ = {belief_wedge(model, model.μ_θ) * 100:.5f} " + "(% of consumption)") ``` +Both the drift and the wedge are tiny at this calibration: log consumption +has a small innovation standard deviation, so the exposure $v_x \sigma_x$ of +the continuation value to the shock is small, and the entropy penalty allows +only a small tilt. + +In the full model, the corresponding exposures of continuation values to +shocks are much larger, and they generate the percentage-point wedges seen in +the surveys. + +The next figure illustrates the tilt. + +Because the true drift $\nu_t = -\theta_t v_x \sigma_x \approx -0.001$ is +invisible on a density plot, the figure instead shifts each curve by the +**scaled drift** $\nu_t / \sigma_x = -\theta_t v_x$, magnifying the true +shift by a factor of $1/\sigma_x = 200$. + +The plotted curves are therefore *not* the actual subjective densities of +$w_{t+1}$ at this calibration; they show the direction of the tilt and how it +grows with $\theta_t$, while the printed output reports the true drifts. + ```{code-cell} ipython3 --- mystnb: figure: - caption: objective and subjective shock distributions + caption: objective and subjective shock distributions (drift scaled for + visibility) name: fig-sbbc-shock-distributions --- θ_vals = [0, model.μ_θ, 2 * model.μ_θ] @@ -786,19 +899,21 @@ labels = ['θ = 0', f'θ = 2θ_bar (pessimistic)'] colors = ['black', 'steelblue', 'firebrick'] -# Drift in units of σ_x -ν_tilde = [-θ * model.vx for θ in θ_vals] +# True drift ν = -θ vx σ_x, and the version scaled by 1/σ_x +# that the figure plots so that the shift is visible +ν_true = [belief_drift(model, θ) for θ in θ_vals] +ν_scaled = [ν / model.σ_x for ν in ν_true] x_grid = np.linspace(-4, 4, 500) fig, ax = plt.subplots() -for μ, label, color in zip(ν_tilde, labels, colors): - pdf = (1 / np.sqrt(2 * np.pi)) * np.exp(-0.5 * (x_grid - μ)**2) - ax.plot(x_grid, pdf, label=label, color=color, linewidth=2) +for μ, label, color in zip(ν_scaled, labels, colors): + ax.plot(x_grid, norm.pdf(x_grid, loc=μ), + label=label, color=color, linewidth=2) ax.axvline(0, color='grey', linestyle=':', linewidth=0.8) ax.set_xlabel( - 'innovation shift in units of $\\sigma_x$: ' + 'scaled innovation shift: ' '$\\nu_t / \\sigma_x = -\\theta_t v_x$' ) ax.set_ylabel('density') @@ -806,9 +921,9 @@ ax.legend() plt.tight_layout() plt.show() -print("Mean shifts (in units of σ_x):") -for μ, label in zip(ν_tilde, labels): - print(f" {label:35s} ν_tilde = {μ:.4f}") +print("True and scaled subjective drifts:") +for ν, ν_s, label in zip(ν_true, ν_scaled, labels): + print(f" {label:35s} ν = {ν:9.5f} ν/σ_x = {ν_s:.4f}") ``` The figure shows how pessimism (higher $\theta_t$) shifts the perceived @@ -817,10 +932,8 @@ distribution of future shocks to the left. The black curve is the objective distribution, centered at zero. The blue and red curves are subjective distributions for progressively larger -values of $\theta_t$. - -The horizontal axis measures the shift in units of $\sigma_x$, so the leftward -movement is the normalized subjective drift $\nu_t / \sigma_x$. +values of $\theta_t$, with the mean shift drawn at the scaled drift +$\nu_t / \sigma_x$ rather than at the (tiny) true drift $\nu_t$. An agent with $\theta_t > 0$ believes bad shocks are more likely than they actually are. @@ -837,9 +950,9 @@ $$ x_{t+1} = -\theta_t v_x \sigma_x^2 + \rho_x x_t + \sigma_x \tilde w_{t+1}. $$ -With $\theta_t = \bar\theta(\bar x + x_t)$, collecting terms shows that -subjective beliefs change both the intercept and the slope of the perceived -dynamics: +With the state-dependent specification of Case 2, +$\theta_t = \bar\theta(\bar x + x_t)$, collecting terms shows that subjective +beliefs change both the intercept and the slope of the perceived dynamics: $$ \tilde\rho_x = \rho_x - \bar\theta\, v_x \sigma_x^2, @@ -1056,9 +1169,18 @@ The only free parameters describing beliefs are the three governing the $\theta_t$ process, so every additional surveyed variable adds an overidentifying restriction on the model. -This theoretical prediction -matches the empirical finding that one principal component explains 78.6% -of the joint variation in the unemployment and inflation wedges. +This theoretical prediction matches the empirical finding that one principal +component explains about four-fifths of the joint variation in the +unemployment and inflation wedges. + +The code below illustrates the one-factor structure, but with a shortcut: in +place of the model-implied loadings $-\bar{z}'(\psi_w\psi_w')v_x'$, it uses +the empirical loadings $c_u$ and $c_\pi$ defined earlier, so the lines pass +through the empirical mean wedges at $\theta = \mu_\theta$. + +In the full model the loadings are endogenous, and the paper's structural +benchmark implies mean wedges of 0.55 and 0.90 rather than the data values +0.52 and 1.22. ```{code-cell} ipython3 --- @@ -1069,7 +1191,7 @@ mystnb: --- θ_grid = np.linspace(0, 20, 200) -# Loadings match the mean empirical wedges. +# Empirical loadings loading_u = c_u # 0.52 / 5.64 pp per unit of θ (unemployment) loading_π = c_π # 1.22 / 5.64 pp per unit of θ (inflation) @@ -1118,16 +1240,20 @@ In the data the relation is looser because survey responses contain measurement error; a hidden-factor model that allows for such error recovers a belief factor whose path is close to the first principal component. -## A New Keynesian model with belief distortions +## A reduced-form emulator of the New Keynesian model We now use the empirical belief factor to generate impulse responses. The full model in {cite:t}`bhandari2025survey` is a New Keynesian model with -search-and-matching frictions. Beliefs matter because consumption decisions, -vacancy posting, wage bargaining, and price setting are forward-looking. +households, Calvo price setting, search-and-matching labor frictions, Nash +wage bargaining, TFP shocks, monetary-policy shocks, and the belief shock, +all calibrated inside the full equilibrium system. -Rather than solve that full system here, we use a small linear surrogate that -keeps only the pieces needed for transparent IRFs: +Beliefs matter there because consumption decisions, vacancy posting, wage +bargaining, and price setting are forward-looking. + +Rather than solve that system here, we use a small linear emulator that keeps +only the pieces needed for transparent impulse responses: $$ @@ -1139,17 +1265,31 @@ where $s_t = (u_t, \pi_t, y_t, \theta_t, a_t)'$ collects unemployment, inflation, output, the belief shock, and TFP, and $\epsilon_{t+1} \sim N(0, I_3)$ contains the three structural shocks. +The matrices below are chosen to reproduce selected signs and moments from +the paper; they are not obtained by solving the structural equilibrium +conditions. + The belief shock follows the persistence estimated from the survey wedges, -$\rho_\theta = 0.714$. The two wedge loadings are chosen so that -$c_u \mu_\theta = 0.52$ and $c_\pi \mu_\theta = 1.22$ at -$\mu_\theta = 5.64$. +$\rho_\theta = 0.714$. + +The two wedge loadings are chosen so that $c_u \mu_\theta = 0.52$ and +$c_\pi \mu_\theta = 1.22$ at $\mu_\theta = 5.64$. The entries that connect $\theta_t$ to $u_t$, $\pi_t$, and $y_t$ should be -read as reduced-form summaries of the full subjective-expectations channel. -They are calibrated to give the right signs and a reasonable scale for the +read as reduced-form summaries of the full subjective-expectations channel, +calibrated to give the right signs and a reasonable scale for the belief-shock IRF: higher pessimism raises unemployment, temporarily raises inflation, and lowers output. +One difference from the structural model deserves emphasis. + +Unlike the full model, this reduced-form system makes $\theta_t$ move the +wedges and the macroeconomic variables directly. + +In the structural model, $\theta_t$ matters through distorted probabilities +over payoff-relevant shocks, so the presence and propagation of fundamental +shocks are part of the mechanism. + We index the five state variables with named constants, so that later code can refer to, say, the belief shock as `I_THETA` rather than a bare number. @@ -1170,7 +1310,7 @@ class NKModel(NamedTuple): def create_nk_model(): - """Build the pedagogical reduced-form NK model (state and shock matrices).""" + """Build the reduced-form NK emulator (state and shock matrices).""" # Exogenous-process parameters from bhandari2025survey. ρ_θ, σ_θ = 0.714, 4.3 ρ_a, σ_a = 0.840, 0.00568 @@ -1301,10 +1441,14 @@ A long-standing challenge for New Keynesian models is that standard TFP and monetary policy shocks generate far too little unemployment volatility ({cite}`Shimer2005`). -In the no-belief-shock economy, TFP and monetary policy shocks produce -unemployment volatility of only 0.55, compared to 1.70 in the data. +In the paper's no-belief-shock economy, TFP and monetary policy shocks +produce unemployment volatility of only 0.55, compared to 1.70 in the data. -Adding the belief shock substantially closes the gap: +Adding the belief shock substantially closes the gap. + +The emulator is calibrated to reproduce this experiment: we compute its +unconditional standard deviations from the discrete Lyapunov equation, with +and without the belief shock. ```{code-cell} ipython3 def simulate_nk(model, T=200, seed=42): @@ -1366,8 +1510,9 @@ plt.tight_layout() plt.show() ``` -The bar chart compares three standard deviations: the no-belief-shock economy, -the benchmark economy, and the data. +The bar chart compares three standard deviations: the emulator without the +belief shock, the emulator with it (labeled "Benchmark" after the paper's +benchmark economy), and the data. The main message is visible in the unemployment bars. @@ -1462,8 +1607,9 @@ innovation raises unemployment, lowers inflation, and lowers output on impact, but the responses fade quickly because the monetary-policy shock is i.i.d. Unlike the belief-shock figure, these panels do not include belief wedges: -TFP and monetary-policy shocks do not move $\theta_t$ directly in this -pedagogical representation. +TFP and monetary-policy shocks do not move $\theta_t$ in this reduced-form +illustration, just as in the paper's benchmark, where $\theta_t$ is an +exogenous AR(1) process orthogonal to the other shocks. ### Role of firms' beliefs @@ -2109,13 +2255,16 @@ The algorithm therefore decomposes cleanly into two stages: This separation is a major practical advantage: existing rational-expectations solvers can be used for Stage 1 with only a wrapper for Stage 2. -In the scalar endowment model, Stage 2 is simple because the value function is -the only forward-looking object. +In the scalar endowment model, Stage 2 is simple because the value function +is the only forward-looking object: the state is exogenous, so there is no +feedback coefficient $\psi_{xf}$ to solve for. + +The code below is therefore a scalar toy illustration of the value block of +Stage 2, not an implementation of the full two-stage algorithm. -The code below carries out the two computable ingredients: it solves the -Riccati equation by iteration starting from the rational-expectations value, -as the paper does, and then reads off the subjective transition coefficients -from the re-centred shocks. +It solves the Riccati equation by iteration starting from the +rational-expectations value, as the paper does, and then reads off the +subjective transition coefficients from the re-centred shocks. ```{code-cell} ipython3 # Stage 1: rational-expectations objects of the scalar endowment model @@ -2311,7 +2460,7 @@ m_ex = create_belief_model(β=β_ex, ρ_x=ρ_x_ex, print(f"v_x (with pessimism θ_bar={μ_θ_ex}): {m_ex.vx:.4f}") ``` -*Part 2.* Under rational expectations ($\theta = 0$): +*Part 2.* Under pessimism ($\theta_t > 0$), the consumption wedge is $$ \Delta_t^{(1)}(x) @@ -2436,7 +2585,7 @@ for i, label in zip([I_U, I_PI, I_Y], var_labels): ``` The belief shock accounts for the large majority of unemployment variance in -this calibrated surrogate. +this calibrated emulator. Technology shocks drive most of the inflation variance, and output variance is split roughly evenly between the belief and TFP shocks. @@ -2460,7 +2609,7 @@ Solve the Riccati equation (`solve_vx`) for a grid of $\mu_\theta$ values from 0 (rational expectations) to 15. For each value, -compute the normalized subjective drift $\nu / \sigma_x = -\mu_\theta v_x$ +compute the scaled subjective drift $\nu / \sigma_x = -\mu_\theta v_x$ and the steady-state belief wedge. Discuss how the robust value function differs from @@ -2489,7 +2638,7 @@ fig.suptitle('Subjective drift and steady-state wedge') axes[0].plot(μ_grid, drift_norm, color='steelblue', linewidth=2) axes[0].axhline(0, color='grey', linestyle='--', linewidth=0.8) axes[0].set_xlabel('mean pessimism $\\mu_\\theta$') -axes[0].set_ylabel('normalized drift $\\nu / \\sigma_x$') +axes[0].set_ylabel('scaled drift $\\nu / \\sigma_x$') axes[1].plot(μ_grid, np.array(wedge_ss), color='firebrick', linewidth=2) axes[1].set_xlabel('mean pessimism $\\mu_\\theta$') @@ -2499,12 +2648,12 @@ plt.tight_layout(rect=[0, 0, 1, 0.94]) plt.show() ``` -The left panel plots the normalized subjective drift +The left panel plots the scaled subjective drift $\nu / \sigma_x = -\mu_\theta v_x$. The right panel plots the corresponding steady-state belief wedge. -The normalized drift is the horizontal shift used in the shock-distribution +The scaled drift is the horizontal shift used in the shock-distribution figure above, so it is easier to see than the tiny movement in $v_x$ itself. The steady-state consumption wedge becomes more negative, approximately @@ -2512,5 +2661,28 @@ linearly in magnitude, since $\Delta^{(1)} \propto -\mu_\theta v_x \sigma_x^2$ and $v_x$ is approximately constant for small $\mu_\theta$. +Finally, consider how the robust value function itself changes with +$\mu_\theta$. + +```{code-cell} ipython3 +vx_0 = create_belief_model(μ_θ=0).vx +vx_15 = create_belief_model(μ_θ=15).vx +print(f"v_x at μ_θ = 0: {vx_0:.8f}") +print(f"v_x at μ_θ = 15: {vx_15:.8f}") +print(f"relative change: {(vx_15 - vx_0) / vx_0:.2e}") +``` + +The slope $v_x$ falls as $\mu_\theta$ rises --- the quadratic term in the +Riccati equation lowers the marginal value of the state --- but the change is +on the order of $10^{-5}$ in relative terms, because the quadratic term is +scaled by $\sigma_x^2$. + +The robust value function therefore differs from its rational-expectations +counterpart mainly through the constant $v_q$, which falls as the variance +penalty grows. + +Because $v_x$ is nearly constant, the drift and the wedge are approximately +linear in $\mu_\theta$, which is what both panels show. + ```{solution-end} ``` From aaf06562210080a21682befe3d014d9a4dc3099b Mon Sep 17 00:00:00 2001 From: HumphreyYang Date: Fri, 12 Jun 2026 15:26:57 +1000 Subject: [PATCH 25/25] updates --- .../subjective_beliefs_business_cycles.md | 676 ++++++------------ 1 file changed, 231 insertions(+), 445 deletions(-) diff --git a/lectures/subjective_beliefs_business_cycles.md b/lectures/subjective_beliefs_business_cycles.md index ad1b0193..0477c2fb 100644 --- a/lectures/subjective_beliefs_business_cycles.md +++ b/lectures/subjective_beliefs_business_cycles.md @@ -159,10 +159,17 @@ Quarters are indexed as `YYYYQ`, so `19821` means 1982Q1, and months as #### The VAR benchmark forecast The data-generating forecast $E_t[\cdot]$ comes from a quarterly VAR with two -lags in nine variables: CPI inflation over the past year, annualized real GDP -growth, the unemployment rate, the log change in the relative price of -investment goods, capacity utilization, log hours worked per capita, the -consumption rate, the investment rate, and the federal funds rate. +lags in nine variables: + + - CPI inflation over the past year, + - annualized real GDP growth, + - the unemployment rate, + - the log change in the relative price of investment goods, + - capacity utilization, + - log hours worked per capita, + - the consumption rate, + - the investment rate, and + - the federal funds rate. ```{code-cell} ipython3 q = macro_q @@ -350,10 +357,12 @@ plt.tight_layout() plt.show() ``` -Both wedges are positive most of the time --- households persistently -overpredict unemployment and inflation --- and both rise during the shaded +Both wedges are positive most of the time and both rise during the shaded NBER recessions. +It suggests that households persistently +overpredict unemployment and inflation. + ```{code-cell} ipython3 --- mystnb: @@ -537,8 +546,11 @@ $$ which replaces the expected continuation value with a soft minimum: as $\theta_t \to 0$ it reduces to $u(x_t) + \beta E_t[v_{t+1}]$, and as $\theta_t \to \infty$ it approaches the worst case over states in a bounded -or finite-state setting (with unbounded Gaussian shocks the soft minimum -instead falls without bound). +or finite-state setting. + + +(With unbounded Gaussian shocks, the soft minimum +instead falls without bound.) In the robust-control literature, this distortion expresses fear of model misspecification. @@ -614,17 +626,15 @@ The calculation has four steps: 4. solve for the value-function slope, first with a fixed $\theta$ and then with a state-dependent $\theta_t$. -Steps 1--3 treat the current value of $\theta_t$ as given; they go through -unchanged whatever that value is. +Steps 1--3 treat the current value of $\theta_t$ as given. With linear dynamics, Gaussian shocks, and linear utility, the continuation value is affine in the state, so we guess $v_t = v_x x_t + v_q$ and verify the guess by substitution. (The affine guess with constant coefficients is exact when $\theta$ is -constant; when $\theta_t$ varies over time, it is the first-order -approximation of {cite:t}`bhandari2025survey`, whose perturbation method the -appendix describes.) +constant; when $\theta_t$ varies over time, it requires the first-order +approximation of {cite:t}`bhandari2025survey`.) The slope $v_x = \partial v_t / \partial x_t$ measures how much the agent values an extra unit of the state. @@ -881,10 +891,6 @@ invisible on a density plot, the figure instead shifts each curve by the **scaled drift** $\nu_t / \sigma_x = -\theta_t v_x$, magnifying the true shift by a factor of $1/\sigma_x = 200$. -The plotted curves are therefore *not* the actual subjective densities of -$w_{t+1}$ at this calibration; they show the direction of the tilt and how it -grows with $\theta_t$, while the printed output reports the true drifts. - ```{code-cell} ipython3 --- mystnb: @@ -1054,15 +1060,40 @@ w_{t+1} \;\sim\; N\!\left(- \theta_t (v_x \psi_w)',\; I_k\right), $$ where $v_x$ is the row vector of first derivatives of the continuation value -and $\bar{x}$ is the non-stochastic steady state. +and $\bar{x}$ is the steady state. + +In a standard first-order perturbation, belief distortions would vanish from +the solution. + +The reason is the certainty equivalence of first-order approximations: the +expansion scales shock volatility by a parameter $\mathsf{q}$ and keeps only +terms linear in $\mathsf{q}$, so any object that works through the *variance* +of shocks --- risk premia, precautionary saving, or belief distortions --- is +second order and gets truncated away. -In a standard first-order perturbation, belief distortions would vanish: as -shock volatility shrinks, the entropy penalty makes the distortion second -order. +The scalar example makes the orders visible. -{cite:t}`bhandari2025survey` avoid this by scaling $\theta_t$ jointly with the -shock volatility, which keeps the subjective model distinct from the -data-generating process in the linear solution; the appendix gives details. +The optimal drift $\nu_t = -\theta_t v_x \sigma_x$ shrinks linearly with +volatility: when there is less to fear, the entropy penalty permits only a +smaller tilt. + +The implied wedge $\Delta_t^{(1)}(x) = -\theta_t v_x \sigma_x^2$ is therefore +*quadratic* in volatility --- halve $\sigma_x$ and the wedge falls by a +factor of four --- so it is one order smaller than the dynamics themselves +and drops out of a linear solution. + +A naive linearization would thus behave exactly like its +rational-expectations twin: no wedges, no belief shock, and nothing for the +survey data to discipline. + +{cite:t}`bhandari2025survey` avoid this by scaling $\theta_t$ jointly with +the shock volatility, letting it grow like $1/\mathsf{q}$ as volatility +shrinks. + +The drift $-\theta_t (v_x \psi_w)'$ then stays of order one, the wedge +becomes first order --- the same order as everything else in the linear +solution --- and the subjective law of motion survives as an object distinct +from the data-generating process; the appendix gives details. The wedge formula comes directly from comparing the one-step-ahead objective and subjective conditional means. @@ -1107,10 +1138,27 @@ v_x + \beta\, v_x \psi_x. $$ -This is a modified Riccati equation: the middle term arises from the entropy -penalty on beliefs and vanishes under rational expectations ($\bar\theta = 0$). +This is a modified Riccati equation: like the Riccati equations of +linear-quadratic control, it is quadratic in the unknown $v_x$, and the +middle term vanishes under rational expectations ($\bar\theta = 0$), leaving +the linear equation $v_x = u_x + \beta v_x \psi_x$ with the familiar solution +$v_x = u_x (I - \beta\psi_x)^{-1}$. + +Each term has an economic reading. + +The first term, $u_x$, is the marginal flow utility of the state. + +The last term, $\beta v_x \psi_x$, is the discounted marginal continuation +value: an extra unit of the state today raises next period's state by +$\psi_x$ and hence next period's value by $v_x \psi_x$. + +The middle term is the price of state-dependent pessimism: an extra unit of +state component $j$ raises the belief factor by $\bar\theta_j$, and each unit +of the belief factor discounts the continuation value by half its conditional +variance, $v_x \psi_w \psi_w' v_x'$. To see why the extra term has this form, focus on the continuation value. + Locally, write it as linear in next period's state, $$ @@ -1129,8 +1177,9 @@ v_x \psi_w \psi_w' v_x'. $$ Robust preferences replace the ordinary expected continuation value with an -entropy-adjusted one. For a Gaussian linear payoff, the minimization over -distorted beliefs gives +entropy-adjusted one. + +For a Gaussian linear payoff, the minimization over distorted beliefs gives $$ @@ -1139,9 +1188,11 @@ E_t[v_{t+1}] $$ -Thus the agent subtracts a variance penalty from continuation value. Because -$\theta_t = \bar\theta x_t$, matching the coefficient on $x_t$ in the Bellman -equation adds the term +Thus the agent subtracts a variance penalty from continuation value. + +Because $\theta_t = \bar\theta x_t$, the penalty is linear in the state --- +the mechanism of Case 2 in the scalar illustration --- so matching the +coefficient on $x_t$ in the Bellman equation adds the term $$ @@ -1153,6 +1204,16 @@ $$ The term is quadratic in $v_x$ because the variance of the continuation value depends on the square of its exposure to shocks, $v_x \psi_w$. +Indeed, this equation is the vector version of the scalar Riccati equation: +setting $\psi_x = \rho_x$, $\psi_w = \sigma_x$, and $\bar\theta = \mu_\theta$ +(all scalars) recovers the equation solved by `solve_vx`. + +And if $\theta_t$ were a constant, the variance penalty would not depend on +$x_t$, the middle term would disappear from the coefficient-matching +equation, and only the constant term of the value function would change. + +In that case, it collapses to the linear equation of Case 1. + ### One-factor structure An important consequence of the formula for $\Delta_t^{(1)}(z)$ is that the @@ -1386,7 +1447,9 @@ nk = create_nk_model() A positive innovation to $\theta_t$ makes households more pessimistic. In the full structural model, higher pessimism makes households and firms act -as if bad future states are more likely. Vacancy posting weakens, output +as if bad future states are more likely. + +Vacancy posting weakens, output falls, unemployment rises, and the two survey wedges jump together. The reduced-form system below is calibrated to reproduce those signs and to @@ -1432,14 +1495,15 @@ The first row shows the macroeconomic responses, and the second row shows the belief shock and the two implied survey wedges. The shock raises unemployment, lowers output, and generates comoving -unemployment and inflation wedges. Inflation rises temporarily in this +unemployment and inflation wedges. + +Inflation rises temporarily in this calibration, then decays back toward zero. ### The unemployment volatility puzzle A long-standing challenge for New Keynesian models is that standard TFP and -monetary policy shocks generate far too little unemployment volatility -({cite}`Shimer2005`). +monetary policy shocks generate far too little unemployment volatility {cite}`Shimer2005`. In the paper's no-belief-shock economy, TFP and monetary policy shocks produce unemployment volatility of only 0.55, compared to 1.70 in the data. @@ -1524,96 +1588,9 @@ Adding the calibrated belief shock raises unemployment volatility from about The belief shock also improves the model's fit to the historical record. -{cite:t}`bhandari2025survey` feed measured TFP innovations and the -belief-shock innovations extracted from the wedge data into the model and -compare the implied paths with the data. - -The correlations between model-implied and actual paths are 0.51 for -unemployment, 0.83 for the unemployment wedge, and 0.79 for the inflation -wedge. - -Shutting down fluctuations in $\theta_t$ makes the model overstate -unemployment during the late-1990s boom and understate it around the Great -Recession. - -Through the lens of the model, the late 1990s were a period of relative -optimism, and much of the 2008--09 rise in unemployment reflected an increase -in pessimism. - -One limitation of the benchmark model is its inflation cyclicality. - -Inflation is nearly acyclical in the data (correlation with output of 0.10) -but countercyclical in the model (−0.82). - -The missing ingredients are wage and price markup shocks, which account for -much of unconditional inflation variation in richer DSGE models but have -little explanatory power for output and unemployment, so adding them would -leave the belief-wedge mechanism intact. - -### Impulse responses to TFP and monetary policy shocks - -For completeness, we also show responses to the other two shocks. - -The figure below has TFP responses in the top row and monetary-policy responses -in the bottom row. - -```{code-cell} ipython3 ---- -mystnb: - figure: - caption: impulse responses to TFP and monetary policy shocks - name: fig-sbbc-tfp-mp-irfs ---- -resp_a, _, _ = irf(nk, shock_idx=1, T=T_irf) # TFP shock -resp_r, _, _ = irf(nk, shock_idx=2, T=T_irf) # Monetary policy shock - -fig, axes = plt.subplots(2, 3, figsize=(13, 7)) -axes = axes.flatten() - -series_a = [resp_a[0]*100, resp_a[1]*400, resp_a[2]*100] -series_r = [resp_r[0]*100, resp_r[1]*400, resp_r[2]*100] -var_ylabels = ['unemployment (pp)', 'inflation (pp, ann.)', 'output (%)'] - -for j, (ylabel, ya, yr) in enumerate(zip(var_ylabels, series_a, series_r)): - axes[j].plot(periods, ya, color='steelblue', linewidth=2, - label='TFP shock') - axes[j].axhline(0, color='grey', linewidth=0.7, linestyle='--') - axes[j].set_ylabel(ylabel) - axes[j].set_xlabel('quarters') - axes[j].legend(loc='best') - - axes[j+3].plot(periods, yr, color='darkorange', linewidth=2, - label='MP shock') - axes[j+3].axhline(0, color='grey', linewidth=0.7, linestyle='--') - axes[j+3].set_ylabel(ylabel) - axes[j+3].set_xlabel('quarters') - axes[j+3].legend(loc='best') - -plt.tight_layout() -plt.show() -``` - -The TFP shock raises output and lowers unemployment. - -Inflation also falls in this calibration, reflecting the disinflationary effect -of higher productivity. - -All three responses are persistent because TFP itself is persistent. - -The monetary-policy shock is much less persistent. - -In the plotted reduced-form calibration, a contractionary monetary-policy -innovation raises unemployment, lowers inflation, and lowers output on impact, -but the responses fade quickly because the monetary-policy shock is i.i.d. - -Unlike the belief-shock figure, these panels do not include belief wedges: -TFP and monetary-policy shocks do not move $\theta_t$ in this reduced-form -illustration, just as in the paper's benchmark, where $\theta_t$ is an -exogenous AR(1) process orthogonal to the other shocks. - ### Role of firms' beliefs -In the benchmark model, **firms** as well as +In the benchmark model, *firms* as well as households hold subjective beliefs. What changes when firms instead have rational beliefs? @@ -1643,53 +1620,6 @@ perceived surplus stays larger and the bargained wage declines less. Firm beliefs therefore strengthen the comovement between the unemployment wedge and the inflation wedge, which is needed to match the data. -The sign and size of the inflation response to a belief shock are therefore -diagnostic: the survey evidence is hard to match unless firms, not only -households, put extra subjective probability on high-marginal-cost states. - -### Two diagnostic variants - -Two diagnostic variants show how survey wedges restrict the -model. - -**No TFP shocks** --- Setting $\sigma_a = 0$ leaves only demand-type -disturbances: monetary policy and belief shocks, which lower economic activity -and inflation together. - -The states households fear then combine high unemployment with *low* -inflation, so pessimism produces a negative average inflation wedge and -negative comovement between the two wedges --- both counterfactual. - -This failure persists even after the belief-shock process is recalibrated (to -$\mu_\theta = 150$ and $\sigma_\theta = 117.7$) to keep the unemployment wedge -close to the benchmark: the mean inflation wedge is $-0.32$, its volatility is -only $0.26$, and it becomes procyclical. - -**Rational firms** --- If households are pessimistic but firms have rational -beliefs, unemployment still responds strongly to a belief shock. - -Inflation, however, falls on impact and the inflation wedge is much too small. - -The rational-firms variant keeps the unemployment wedge close to the benchmark, -with mean $0.55$ and volatility $0.45$, but the inflation wedge falls to mean -$0.34$ and volatility $0.29$, compared with $0.90$ and $0.73$ in the -benchmark. - -The table below summarizes the two restrictions. - -| Moment | Benchmark | No TFP shocks | Rational firms | -|---|---:|---:|---:| -| Mean inflation wedge | 0.90 | −0.32 | 0.34 | -| Mean unemployment wedge | 0.55 | 0.54 | 0.55 | -| Volatility of inflation wedge | 0.73 | 0.26 | 0.29 | -| Volatility of unemployment wedge | 0.45 | 0.43 | 0.45 | -| Volatility of unemployment | 1.39 | 0.87 | 1.24 | -| Corr. inflation wedge, output | −0.67 | 0.50 | −0.53 | - -These variants show why the benchmark needs both supply-side uncertainty and -firms' subjective beliefs to match the joint behavior of inflation and -unemployment forecasts. - ### Countercyclicality of wedges A final important prediction is that belief wedges are countercyclical. @@ -1752,60 +1682,11 @@ Periods with weak output tend to coincide with elevated wedges. The simulated correlations are negative, confirming the countercyclicality predicted by the model and documented in the survey data. -### Further empirical checks - -Reduced-form evidence provides additional support for the mechanism. - -In local projections of macroeconomic variables and survey forecasts on -innovations to the first principal component of the belief wedges, a positive -innovation predicts higher unemployment, higher belief wedges, and an -inflation response that is briefly positive before turning mildly negative. - -A second check comes from forecast-error regressions of the form - -$$ - -z_{t+j} - \tilde E_t[z_{t+j}] -= b_0 + b_z z_t + b_f \tilde E_{t-1}[z_{t+j-1}] + \varepsilon_{t+j}, - -$$ - -where $z_t$ is inflation or unemployment. - -Under full-information rational expectations these errors should be mean zero -and unforecastable: $b_0 = b_z = b_f = 0$. - -In the data, $b_0 < 0$ for both variables, restating the average upward biases. - -The slopes reveal two different updating patterns: inflation forecast errors -are predicted by the lagged forecast ($b_f = -0.50$), the signature of -sluggish updating, while unemployment forecast errors are predicted by the -current unemployment rate ($b_z = -0.40$), the signature of overreaction. - -The calibrated model reproduces the signs, magnitudes, and $R^2$ of all three -patterns. - -Models of information frictions and models of overreaction imply $b_0 = 0$, -and each matches only one of the two slope patterns; the belief-wedge model -matches everything because the household pessimistically distorts the *joint* -distribution of inflation and unemployment. - -A final check compares the belief factor with familiar sentiment measures. - -The first principal component of the wedges has correlations of $-0.65$ with -the Michigan Consumer Sentiment Index and $-0.72$ with the Conference Board -Consumer Confidence Index, so the belief wedges capture what sentiment indices -capture --- with the advantage that wedges quantify the forecast bias in -percentage points, which is what makes the model calibration possible. - -In contrast, the wedges are largely uncorrelated with dispersion in SPF -forecasts, so time-varying pessimism is distinct from forecaster disagreement. - ## Extensions Several extensions of the benchmark model are worth noting: -**Heterogeneous beliefs** --- The solution method allows the belief distortion +**Heterogeneous beliefs:** The solution method allows the belief distortion to be switched on or off equation by equation, so different agents can hold different subjective beliefs. @@ -1817,7 +1698,7 @@ shocks would generate belief heterogeneity across households endogenously, with implications for saving, portfolio choice, and the design of social insurance. -**Pessimism induced by TFP** --- The benchmark treats $\theta_t$ as an +**Pessimism induced by TFP:** The benchmark treats $\theta_t$ as an exogenous AR(1) process. Another specification makes negative TFP shocks raise pessimism. @@ -1834,7 +1715,7 @@ the data fall to 0.22 (unemployment), 0.20 (unemployment wedge), and 0.35 {cite:t}`bhandari2025survey` read this as evidence that quantitatively important movements in pessimism are orthogonal to productivity. -**Wage rigidity** --- Wage rigidity is important for amplification. +**Wage rigidity:** Wage rigidity is important for amplification. With flexible wages ($\chi_w = 0$), bargained wages absorb shocks, firm values move less, and unemployment volatility falls from $1.39$ to $0.77$ --- the @@ -1844,7 +1725,7 @@ Lower macroeconomic volatility feeds back into beliefs: with less to fear, the covariance between forecasted variables and continuation values shrinks, and unemployment-wedge volatility falls from $0.45$ to $0.13$. -**Beyond the first-order homoskedastic case** --- The approximation is designed +**Beyond the first-order homoskedastic case:** The approximation is designed to keep subjective-belief effects alive in a linear solution. In richer nonlinear or stochastic-volatility settings, belief wedges could also @@ -1852,7 +1733,7 @@ move because the dispersion of continuation values changes. We do not pursue those extensions here. -**Idiosyncratic risk** --- The benchmark model takes fluctuations in $\theta_t$ as +**Idiosyncratic risk:** The benchmark model takes fluctuations in $\theta_t$ as exogenous, but they can also be endogenized. In a variant where households face uninsurable idiosyncratic risk, a rise in @@ -1956,11 +1837,6 @@ def compute_tau_wedge_loadings(ψ_x, ψ_w, H, H_bar, F, τ_max=20): For simplicity we work with the scalar stationary case (all quantities are scalars or 1-d arrays). - - Returns - ------- - wedge_const : array (tau_max,) constant term of wedge (G0_tilde - G0) - wedge_slope : array (tau_max,) x1t loading of wedge (Gx_tilde - Gx) """ n = ψ_x.shape[0] ψ_x_tild = ψ_x + ψ_w @ (H @ F) # subjective transition matrix @@ -1977,7 +1853,7 @@ def compute_tau_wedge_loadings(ψ_x, ψ_w, H, H_bar, F, τ_max=20): for τ in range(1, τ_max + 1): Gx = ψ_x @ Gx Gx_tild = ψ_x_tild @ Gx_tild - G0 = ψ_x @ G0 # ψ_q = 0 under the objective measure + G0 = ψ_x @ G0 # ψ_q = 0 under the objective measure G0_tild = ψ_x_tild @ G0_tild + ψ_q_tild wedge_slope[τ - 1] = (Gx_tild - Gx)[0] @@ -2031,21 +1907,66 @@ The blue line is the multi-period wedge evaluated at the mean state, and the red dashed line evaluates the same wedge after a one-standard-deviation increase in the belief shock. -The distance from zero grows with the horizon because pessimistic beliefs -affect not only the next shock but also the expected path of future states. +Both wedges are negative because the state is consumption-like ($v_x > 0$): +pessimists *under*-forecast consumption, the mirror image of the positive +unemployment and inflation wedges in the survey data. + +The blue line isolates the constant-term gap $\tilde G_0^{(\tau)} - +G_0^{(\tau)}$: each period the subjective law of motion adds the drift +$\tilde\psi_q$, compounded through the subjective persistence, so the wedge +deepens with the horizon and flattens toward the long-run limit +$\tilde\psi_q / (1 - \tilde\psi_x)$ --- the gap between the subjective and +objective unconditional means. + +Pessimism therefore distorts long-horizon forecasts more than short ones, +until mean reversion saturates the accumulation. + +The red line adds the slope contribution +$(\tilde\psi_x^{\tau} - \psi_x^{\tau})\, x_{1t}$, and this term explains its +hump shape: a difference of two geometric decays is zero at $\tau = 0$, +largest in magnitude at intermediate horizons, and vanishing again as both +powers die out. + +The extra pessimism thus deepens the wedge most at medium horizons (around +ten quarters here), after which the red line climbs back toward the *same* +asymptote as the blue line: today's elevated pessimism is transitory, so it +cannot move very-long-horizon forecasts, which are pinned down by the +steady-state wedge. ### The series expansion -{cite:t}`bhandari2025survey` solve the full general-equilibrium model -using a **series expansion** (perturbation) method -({cite}`BorovickaHansen2014`). +The main text used three results without full derivation: the re-centred +shock distribution, the Riccati equation for $v_x$, and the claim that +belief distortions survive linearization only if $\theta_t$ is scaled +jointly with shock volatility. -The key innovation is a **joint -perturbation** of the shock volatility $q$ and the penalty parameter -$\theta_t$. +This section derives them and explains how {cite:t}`bhandari2025survey` +solve the full general-equilibrium model, using a **series expansion** +(perturbation) method in the tradition of {cite:t}`BorovickaHansen2014`. + +Three problems have to be solved in turn. + +First, the model must be expanded around its deterministic steady state in a +parameter that scales shock volatility; this step is standard. + +Second, the expansion must be modified so that the belief distortion appears +at first order instead of being truncated away by certainty equivalence. + +This is the **joint perturbation** of the shock volatility $\mathsf{q}$ and +the penalty parameter $\theta_t$, the paper's key methodological innovation. + +Third, the distorted expectations must be embedded in the model's +equilibrium conditions, which couples the unknown policy matrices to the +continuation value and modifies the standard Blanchard--Kahn solution. + +A final subsection extends the method to the specification the quantitative +model actually uses, in which the belief factor is an exogenous AR(1) +process. #### Law of motion +The first step is to parameterize how volatile the world is. + Index the model by a scalar perturbation parameter $\mathsf{q}$ that scales shock volatility: @@ -2056,6 +1977,9 @@ x_{t+1}(\mathsf{q}) = \psi\!\left(x_t(\mathsf{q}),\, $$ +Here $\mathsf{q} = 1$ is the model of interest and $\mathsf{q} = 0$ is a +deterministic economy. + Expanding around $\mathsf{q} = 0$ gives $$ @@ -2073,17 +1997,25 @@ x_{1,t+1} = \psi_q + \psi_x x_{1t} + \psi_w w_{t+1}. $$ +A first-order solution keeps only $\bar x$ and $x_{1t}$, and the main text +explained the cost of that truncation: the optimal belief distortion works +through the variance of continuation values, so it is second order in +$\mathsf{q}$ and would be discarded. + #### Continuation value and the Riccati equation -To preserve a nontrivial role for beliefs at first order, the penalty -parameter is **jointly scaled** with $\mathsf{q}$: the effective +The fix makes the agent's taste for distortion grow exactly as fast as the +scope for distortion shrinks. + +The penalty parameter is **jointly scaled** with $\mathsf{q}$: the effective penalization in the perturbed recursion is -$\mathsf{q}/[\bar\theta(\bar x + x_{1t})]$, -which shrinks together with shock volatility. +$\mathsf{q}/[\bar\theta(\bar x + x_{1t})]$, which shrinks together with +shock volatility. -This ensures that the -deterministic steady state does not collapse to the rational-expectations -solution. +Because the optimal drift is the product of the penalty parameter and the +shock exposure of continuation values, scaling one up as the other scales +down keeps the drift at order one, so the subjective model remains distinct +from the data-generating process in the first-order solution. Guessing $v_{1t} = v_x x_{1t} + v_q$ and matching coefficients yields the **Riccati equation for $v_x$**: @@ -2104,6 +2036,14 @@ v_q = u_q - \frac{\beta}{2}\,\bar\theta\, \bar x\, $$ +The slope equation is the modified Riccati equation read term by term in +the main text. + +The constant equation shows where any *fixed* component of pessimism goes: +the variance penalty evaluated at the steady state, proportional to +$\bar\theta \bar x$, shifts only $v_q$ --- Case 1 of the scalar illustration +again. + The Riccati equation is quadratic in $v_x$. For the stationary scalar case it reduces to @@ -2120,6 +2060,10 @@ $$ #### Shock distribution under subjective beliefs +With $v_x$ in hand, the optimal distortion +$m_{t+1}^* \propto \exp(-\theta_t v_{t+1})$ can be evaluated along the +expansion. + Substituting the first-order expansion into the distortion formula shows that the leading term $m_{0,t+1}$ is a lognormal change of measure. @@ -2143,8 +2087,16 @@ $$ $$ +These are the mean-shift and wedge formulas of the main text, now derived +with the belief factor evaluated at its first-order expansion +$\theta_t = \bar\theta(\bar x + x_{1t})$. + #### Equilibrium conditions with subjective beliefs +So far the law of motion $\psi$ was taken as given, but in equilibrium it is +itself determined by optimality and market-clearing conditions, some of +which involve subjective expectations. + The full model's equilibrium conditions take the form $$ @@ -2157,232 +2109,61 @@ where $\mathbb{M}_{t+1} = \mathrm{diag}(m_{t+1}^{\sigma_1}, \ldots, m_{t+1}^{\sigma_n})$ selects which equations involve subjective expectations ($\sigma_i = 1$) versus objective ones ($\sigma_i = 0$). +This equation-by-equation switch is what makes experiments like the +rational-firms variant possible: belief distortions can be turned off in +firms' equations while households remain pessimistic. + First-order expansion of these conditions gives a system in the unknown policy matrices $\psi_x, \psi_w, \psi_q$: $$ -0 = (g_{x^+}\psi_x + g_x - \mathbb{E})\,\psi_x + g_{x^-} +0 = (g_{x^+}\psi_x + g_x - \mathbb{D})\,\psi_x + g_{x^-} $$ $$ -0 = (g_{x^+}\psi_x + g_x - \mathbb{E})\,\psi_w + g_w +0 = (g_{x^+}\psi_x + g_x - \mathbb{D})\,\psi_w + g_w $$ $$ 0 = (g_{x^+}\psi_x + g_{x^+} + g_x)\,\psi_q + g_q - - \mathbb{E}(\bar x + \psi_q), + - \mathbb{D}(\bar x + \psi_q), $$ -where the **belief distortion matrix** $\mathbb{E}$ collects the impact +where the **belief distortion matrix** $\mathbb{D}$ collects the impact of subjective expectations on each equation: $$ -\mathbb{E} = \operatorname{stack}\Bigl\{ +\mathbb{D} = \operatorname{stack}\Bigl\{ \sigma_i\, [g_{x^+}\psi_w + g_{w^+}]^i\, (v_x \psi_w)'\, \bar\theta \Bigr\}. $$ -These equations are solved jointly with the Riccati equation for $v_x$. +(We write $\mathbb{D}$ for this matrix to avoid confusion with the +expectation operator $E_t$.) + +Row $i$ of $\mathbb{D}$ is nonzero only if equation $i$ uses subjective +expectations, and it equals that equation's exposure to next period's +shocks, $[g_{x^+}\psi_w + g_{w^+}]^i$, times the vector +$(v_x \psi_w)' \bar\theta$ that governs the optimal mean shift. + +These equations are solved jointly with the Riccati equation for $v_x$: the +policy matrices determine the continuation value's exposure to shocks, and +that exposure feeds back into the policy matrices through $\mathbb{D}$. Compared with the standard Blanchard–Kahn solution, -the only modification is the additive term $-\mathbb{E}$ that shifts the +the only modification is the additive term $-\mathbb{D}$ that shifts the characteristic matrix; when $\bar\theta = 0$ we recover the standard rational-expectations solution. -#### The AR(1) belief shock as a special case - -Now suppose $\theta_t$ is itself an exogenous AR(1) process: - -$$ - -f_{t+1} = (1 - \rho_f)\bar f + \rho_f f_t + \sigma_f w_{t+1}^f. - -$$ - -Appending $f_t$ to the state vector, the first-order dynamics become - -$$ - -\begin{pmatrix} x_{1,t+1} \\ f_{1,t+1} \end{pmatrix} -= \begin{pmatrix} \psi_q \\ 0 \end{pmatrix} -+ \begin{pmatrix} \psi_x & \rho_f \psi_{xf} \\ 0 & \rho_f \end{pmatrix} -\begin{pmatrix} x_{1t} \\ f_{1t} \end{pmatrix} -+ \begin{pmatrix} \psi_w & \sigma_f \psi_{xf} \\ 0 & \sigma_f \end{pmatrix} -\begin{pmatrix} w_{t+1} \\ w_{t+1}^f \end{pmatrix}. - -$$ - -The new coefficient $\psi_{xf}$ measures how a unit change in the belief -shock $f_{1t}$ feeds into the endogenous state variables. - -It is determined by a backward-induction algorithm that iterates from a -distant terminal date $T$ (where belief distortions vanish) back to the -present. - -The continuation value in the $f$-direction satisfies a separate recursion -for $v_f$, and the belief distortion matrix becomes - -$$ - -\mathbb{E} = \operatorname{stack}\Bigl\{ - \sigma_i\bigl[ - g_{x^+}\psi_{xf}\sigma_f^2(v_f + v_x\psi_{xf}) - + (g_{x^+}\psi_w + g_{w^+})\psi_w' v_x' - \bigr]^i -\Bigr\}\bar\theta_f. - -$$ - -The algorithm therefore decomposes cleanly into two stages: - -1. **Stage 1 (rational-expectations block)**: solve for - $\psi_x$, $\psi_w$ using the standard Blanchard–Kahn method; these are - *unaffected* by the belief shock. - -2. **Stage 2 (belief distortion block)**: given $\psi_x, \psi_w, v_x$, - iterate backward to convergence to find $\psi_{xf}$, $v_f$, and - $\mathbb{E}$. - -This separation is a major practical advantage: existing rational-expectations -solvers can be used for Stage 1 with only a wrapper for Stage 2. - -In the scalar endowment model, Stage 2 is simple because the value function -is the only forward-looking object: the state is exogenous, so there is no -feedback coefficient $\psi_{xf}$ to solve for. - -The code below is therefore a scalar toy illustration of the value block of -Stage 2, not an implementation of the full two-stage algorithm. - -It solves the Riccati equation by iteration starting from the -rational-expectations value, as the paper does, and then reads off the -subjective transition coefficients from the re-centred shocks. - -```{code-cell} ipython3 -# Stage 1: rational-expectations objects of the scalar endowment model -ψ_x_re = model.ρ_x -ψ_w_re = model.σ_x -u_x = 1 - model.β - -# Stage 2 value block: iterate on the Riccati equation -vx_iter = u_x / (1 - model.β * ψ_x_re) # start at the RE value -for it in range(1, 200): - vx_new = (u_x + model.β * ψ_x_re * vx_iter - - 0.5 * model.β * model.μ_θ * ψ_w_re**2 * vx_iter**2) - if abs(vx_new - vx_iter) < 1e-15: - break - vx_iter = vx_new - -print(f"Riccati by iteration ({it} steps): v_x = {vx_iter:.10f}") -print(f"Riccati by quadratic formula: v_x = {model.vx:.10f}") - -# Subjective transition coefficients implied by the re-centred shocks -ψ_x_subj = ψ_x_re - model.μ_θ * vx_iter * ψ_w_re**2 -ψ_q_subj = -model.μ_θ * 1.0 * vx_iter * ψ_w_re**2 # steady state x_bar = 1 - -print(f"\nObjective persistence: ψ_x = {ψ_x_re:.8f}") -print(f"Subjective persistence: ψ_x_subj = {ψ_x_subj:.8f}") -print(f"Subjective drift: ψ_q_subj = {ψ_q_subj:.2e}") -``` - -The two solution methods agree. - -For this consumption-type state ($v_x > 0$), the subjective persistence is -slightly *below* the objective one and the subjective drift is negative: the -pessimist expects good states to fade and bad outcomes to arrive. - -For an unemployment-type state ($v_x < 0$), both signs flip, so subjective -persistence exceeds objective persistence --- the formal statement of -"pessimists believe bad times last longer". - -### Sequence problem and dynamic consistency - -The recursive formulation used throughout the lecture emerges from the -following sequence problem. - -Define the discounted entropy functional - -$$ - -\mathcal{E}_t \;=\; E_t \sum_{j=0}^{\infty} \beta^j - \left[ M_{t,t+j} \frac{\beta}{\theta_{t+j}} - E_{t+j}[m_{t+j+1} \log m_{t+j+1}] - \right], - -$$ - -where $M_{t,t+j} = \prod_{k=1}^j m_{t+k}$. - -The agent's problem is - -$$ - -v_t^* = \max_{\{y_{t+j}\}} \min_{\substack{m_{t+j}>0 \\ E_{t+j-1}[m_{t+j}]=1}} - \sum_{j=0}^{\infty} \beta^j E_t[M_{t,t+j} u_{t+j}] - + \mathcal{E}_t. - -$$ - -The penalty functional $\mathcal{E}_t$ **discounts future entropies -weighted by future penalty parameters $\theta_{t+j}$**, which makes the -agent's choices dynamically consistent: she anticipates how her -pessimism will evolve. - -This differs from the infinite-horizon discounted entropy used in -{cite}`HansenSargent2001`, which is not generally dynamically consistent -when $\theta_t$ is time-varying. - -The recursive form is: - -$$ - -\mathcal{E}_t = \frac{\beta}{\theta_t} E_t[m_{t+1} \log m_{t+1}] - + \beta E_t[m_{t+1} \mathcal{E}_{t+1}]. - -$$ - -Under this penalty, the minimax inequality is an equality, and the value -function satisfies the recursive form stated in the main lecture: - -$$ - -v_t^* = \max_{y_t} \min_{\substack{m_{t+1}>0 \\ E_t[m_{t+1}]=1}} - u_t + \frac{\beta}{\theta_t} E_t[m_{t+1} \log m_{t+1}] - + \beta E_t[m_{t+1} v_{t+1}^*]. - -$$ - -```{code-cell} ipython3 -θ_path = np.array([3.0, 5.64, 8.0, 12.0]) # rising pessimism scenario - -def one_period_entropy(θ, vx, σ_x): - """ - Entropy E_t[m_{t+1} log m_{t+1}] under the optimal distortion - for Gaussian shocks: = (1/2) * (θ * vx * σ_x)^2. - """ - return 0.5 * (θ * vx * σ_x) ** 2 - -print("Effect of time-varying θ on entropy and belief wedge:") -print(f"{'θ_t':>8} {'H_t (entropy)':>16} {'Δ(x) = σ_x ν_t (pp)':>22}") -print('-' * 52) -for th in θ_path: - H = one_period_entropy(th, model.vx, model.σ_x) - bw = belief_wedge(model, th) * 100 - print(f"{th:>8.2f} {H:>16.6f} {bw:>22.4f}") - -print() -print("The entropy penalty grows quadratically in θ,") -print("constraining the agent from distorting beliefs too heavily.") -``` - ## Summary The lecture has built the mechanism from the survey object to the model object. @@ -2408,8 +2189,8 @@ creates comoving unemployment and inflation forecast wedges, and helps close the unemployment volatility gap left by TFP and monetary-policy shocks alone. The survey wedges do double duty: they calibrate the belief-shock process, and -their joint behavior across variables --- means, comovement, cyclicality, and -forecast-error predictability --- over-identifies and thereby tests the model. +their joint behavior across variables, means, comovement, cyclicality, and +forecast-error predictability, over-identifies and thereby tests the model. ## Exercises @@ -2417,7 +2198,7 @@ forecast-error predictability --- over-identifies and thereby tests the model. :label: sbbc_ex1 ``` -**Belief wedge sign** +*Belief wedge sign* In the simple endowment economy built by `create_belief_model`, suppose the state variable is log consumption $x_t$ with $\rho_x = 0.90$, $\sigma_x = 0.01$, @@ -2453,13 +2234,18 @@ $$ μ_θ_ex = 4.0 vx_re_ex = (1 - β_ex) / (1 - β_ex * ρ_x_ex) -print(f"v_x (rational expectations): {vx_re_ex:.4f}") +print(f"v_x (rational expectations): {vx_re_ex:.6f}") m_ex = create_belief_model(β=β_ex, ρ_x=ρ_x_ex, σ_x=σ_x_ex, μ_θ=μ_θ_ex) -print(f"v_x (with pessimism θ_bar={μ_θ_ex}): {m_ex.vx:.4f}") +print(f"v_x (with pessimism θ_bar={μ_θ_ex}): {m_ex.vx:.6f}") +print(f"difference: {m_ex.vx - vx_re_ex:.2e}") ``` +The two slopes differ only in the fifth decimal place: the quadratic term in +the Riccati equation is scaled by $\sigma_x^2$, so at this calibration +pessimism only reduces the marginal value of the state by a small amount. + *Part 2.* Under pessimism ($\theta_t > 0$), the consumption wedge is $$ @@ -2489,7 +2275,7 @@ This matches the empirical finding of a positive mean unemployment wedge. :label: sbbc_ex2 ``` -**Persistence and wedge volatility** +*Persistence and wedge volatility* Using `create_belief_model`, vary $\rho_\theta$ from 0.3 to 0.95 (holding $\sigma_\theta = 4.3$ fixed) and plot how the standard @@ -2543,7 +2329,7 @@ inherits this relationship and rises with $\rho_\theta$. :label: sbbc_ex3 ``` -**Unemployment volatility decomposition** +*Unemployment volatility decomposition* Using the reduced-form NK model built by `create_nk_model`: @@ -2603,7 +2389,7 @@ for most of the variation in inflation. :label: sbbc_ex4 ``` -**Changing the degree of pessimism** +*Changing the degree of pessimism* Solve the Riccati equation (`solve_vx`) for a grid of $\mu_\theta$ values from 0 (rational expectations) to 15.