Di Tella (2017)
The full solution can be found at ditella_with_investment.ipynb.
Problem Setup
This is the stochastic volatility model from Di Tella 20171
Parameter and Variable Definitions
Parameter | Definition | Value |
---|---|---|
\(a\) | relative risk aversion | \(a=1\) |
\(\sigma\) | volatility of TFP shocks | \(\sigma=0.0125\) |
\(\lambda\) | mean reversion coefficient for idiosyncratic risk | \(\lambda=1.38\) |
\(\bar{v}\) | long-run mean of idiosyncratic risk | \(\bar{v}=0.25\) |
\(\bar{\sigma_v}\) | idiosyncratic volatility of capital on aggregate risk | \(\bar{\sigma_v}=-0.17\) |
\(\rho\) | discount rate | \(\rho=0.0665\) |
\(\gamma\) | risk aversion rate | \(\gamma=5\) |
\(\psi\) | inverse of elasticity of intertemporal substitution | \(\psi=0.5\) s.t. EIS=2 |
\(\tau\) | Poisson retirement rate for experts | \(\tau=1.15\) |
\(\phi\) | moral hazzard | \(\phi=0.2\) |
\(A\) | second order coefficient for investment function | \(A=53\) |
\(B\) | first order coefficient for investment function | \(B=-0.8668571428571438\) |
\(\delta\) | shift for investment function | \(\delta=0.05\) |
Type | Definition |
---|---|
State Variables | \((x, v)\in [0.05, 0.95]\times [0.05,0.95]\) |
Agents | \(\xi\) (experts), \(\zeta\) (households) |
Endogenous Variables | \(p\) (price), \(r\) (risk-free rate) |
Note: For an ideal model, \((x,v)\in (0,1)\times (0,\infty)\), we use \([0.05, 0.95]\times [0.05,0.95]\) to be consistent with the original paper.
Equations
Endogenous Equations
HJB Equations
Implementation
-
Import necessary packages
-
Define LaTex-variable mapping so that we can parse LaTex equations
latex_var_mapping = { # variables r"\iota": "iota", r"\hat{e}": "e_hat", r"\hat{c}": "c_hat", r"\sigma_{x,1}": "sigxtop", r"\sigma_{x,2}": "sigxbot", r"\sigma_x": "sigx", r"\sigma_p": "sigp", r"\sigma_\xi": "sigxi", r"\sigma_\zeta": "sigzeta", r"\tilde{\sigma_n}": "signtilde", r"\sigma_n": "sign", r"\pi": "signxi", r"\sigma_w": "sigw", r"\mu_n": "mun", r"\mu_x": "mux", r"\mu_p": "mup", r"\mu_\xi": "muxi", r"\mu_\zeta": "muzeta", r"\mu_w": "muw", # agents r"\xi": "xi", r"\zeta": "zeta", # constants r"\bar{\sigma_v}": "sigv_mean", r"\sigma_v": "sigv", r"\mu_v": "muv", r"\sigma": "sigma", r"\lambda": "lbd", r"\bar{v}": "v_mean", r"\rho": "rho", r"\gamma": "gamma", r"\psi": "psi", r"\tau": "tau", r"\delta": "delta", r"\phi": "phi", }
-
Define the problem. We train on a random sampling of 500 points.
set_seeds(0) pde_model = PDEModel("ditella", {"batch_size": 1000, "num_epochs": 10000, "loss_log_interval": 100, "optimizer_type": OptimizerType.Adam, "sampling_method": SamplingMethod.UniformRandom}, latex_var_mapping) pde_model.set_state(["x", "v"], {"x": [0.05, 0.95], "v": [0.05, 0.95]}) pde_model.add_agents(["xi", "zeta"], {"xi": { "positive": True, }, "zeta": { "positive": True, } }) pde_model.add_endogs(["p", "r"], {"p": { "positive": True, }, }) pde_model.add_params({ "a": 1, "sigma": 0.0125, "lbd": 1.38, "v_mean": 0.25, "sigv_mean": -0.17, "rho": 0.0665, "gamma": 5, "psi": 0.5, "tau": 1.15, "phi": 0.2, "A": 53.2, "B": -0.8668571428571438, "delta": 0.05, }) pde_model.add_equation(r"$g &= \frac{1}{2*A} * (p - B) - \delta$") # g &= \frac{1}{2*A} * (p - B) - \delta pde_model.add_equation(r"$\iota &= A * (g+\delta)^2 + B * (g+\delta)$") # \iota &= A * (g+\delta)^2 + B * (g+\delta) pde_model.add_equation(r"$\mu_v &= \lambda * (\bar{v} - v)$") pde_model.add_equation(r"$\sigma_v &= \bar{\sigma_v} * \sqrt{v}$") pde_model.add_equation(r"$\hat{e} &= \rho^{1/\psi} * \xi^{(\psi-1)/\psi}$") pde_model.add_equation(r"$\hat{c} &= \rho^{1/\psi} * \zeta^{(\psi-1)/\psi}$") pde_model.add_equation(r"$\sigma_{x,1} &= (1-x) * x * \frac{1-\gamma}{\gamma} * \left( \frac{1}{\xi} * \frac{\partial \xi}{\partial v} - \frac{1}{\zeta} * \frac{\partial \zeta}{\partial v} \right)$") pde_model.add_equation(r"$\sigma_{x,2} &= 1 - (1-x) * x * \frac{1-\gamma}{\gamma} * \left( \frac{1}{\xi} * \frac{\partial \xi}{\partial x} - \frac{1}{\zeta} * \frac{\partial \zeta}{\partial x} \right)$") pde_model.add_equation(r"$\sigma_x &= \frac{\sigma_{x,1}}{\sigma_{x,2}} * \sigma_v$") pde_model.add_equation(r"$\sigma_p &= \frac{1}{p} * \left( \frac{\partial p}{\partial v} * \sigma_v + \frac{\partial p}{\partial x} * \sigma_x \right)$") pde_model.add_equation(r"$\sigma_\xi &= \frac{1}{\xi} * \left( \frac{\partial \xi}{\partial v} * \sigma_v + \frac{\partial \xi}{\partial x} * \sigma_x \right)$") pde_model.add_equation(r"$\sigma_\zeta &= \frac{1}{\zeta} * \left( \frac{\partial \zeta}{\partial v} * \sigma_v + \frac{\partial \zeta}{\partial x} * \sigma_x \right)$") pde_model.add_equation(r"$\sigma_n &= \sigma + \sigma_p + \frac{\sigma_x}{x}$") pde_model.add_equation(r"$\pi &= \gamma * \sigma_n + (\gamma-1) * \sigma_\xi$") pde_model.add_equation(r"$\sigma_w &= \frac{\pi}{\gamma} - \frac{\gamma-1}{\gamma} * \sigma_\zeta$") pde_model.add_equation(r"$\mu_w &= r + \pi * \sigma_w$") pde_model.add_equation(r"$\mu_n &= r + \frac{\gamma}{x^2} * (\phi * v)^2 + \pi * \sigma_n$") pde_model.add_equation(r"$\tilde{\sigma_n} &= \frac{\phi}{x} * v$") pde_model.add_equation(r"$\mu_x &= x * \left(\mu_n - \hat{e} - \tau + \frac{a-\iota}{p} - r - \pi * (\sigma+\sigma_p) - \frac{\gamma}{x} * (\phi * v)^2 + (\sigma + \sigma_p)^2 - \sigma_n * (\sigma + \sigma_p)\right)$") pde_model.add_equation(r"$\mu_p &= \frac{1}{p} * \left( \mu_v * \frac{\partial p}{\partial v} + \mu_x * \frac{\partial p}{\partial x} + \frac{1}{2} * \left( \sigma_v^2 * \frac{\partial^2 p}{\partial v^2} + 2 * \sigma_v * \sigma_x * \frac{\partial^2 p}{\partial v \partial x} + \sigma_x^2 * \frac{\partial^2 p}{\partial x^2} \right)\right)$") pde_model.add_equation(r"$\mu_\xi &= \frac{1}{\xi} * \left( \mu_v * \frac{\partial \xi}{\partial v} + \mu_x * \frac{\partial \xi}{\partial x} + \frac{1}{2} * \left( \sigma_v^2 * \frac{\partial^2 \xi}{\partial v^2} + 2 * \sigma_v * \sigma_x * \frac{\partial^2 \xi}{\partial v \partial x} + \sigma_x^2 * \frac{\partial^2 \xi}{\partial x^2} \right)\right)$") pde_model.add_equation(r"$\mu_\zeta &= \frac{1}{\zeta} * \left( \mu_v * \frac{\partial \zeta}{\partial v} + \mu_x * \frac{\partial \zeta}{\partial x} + \frac{1}{2} * \left( \sigma_v^2 * \frac{\partial^2 \zeta}{\partial v^2} + 2 * \sigma_v * \sigma_x * \frac{\partial^2 \zeta}{\partial v \partial x} + \sigma_x^2 * \frac{\partial^2 \zeta}{\partial x^2} \right)\right)$") pde_model.add_endog_equation(r"$a - \iota &= p * (\hat{e} * x + \hat{c} * (1-x))$") pde_model.add_endog_equation(r"$\sigma + \sigma_p &= \sigma_n * x + \sigma_w * (1-x)$") pde_model.add_endog_equation(r"$\frac{a-\iota}{p} + g + \mu_p + \sigma * \sigma_p - r &= (\sigma + \sigma_p) * \pi + \gamma * \frac{1}{x} * (\phi * v)^2$") pde_model.add_hjb_equation(r"$\frac{\hat{e}^{1-\psi}}{1-\psi} * \rho * \xi^{\psi-1} + \frac{\tau}{1-\gamma} * \left(\left(\frac{\zeta}{\xi} \right)^{1-\gamma}-1 \right) + \mu_n - \hat{e} + \mu_\xi - \frac{\gamma}{2} * \left( \sigma_n^2 + \sigma_\xi^2 - 2 * \frac{1-\gamma}{\gamma} * \sigma_n * \sigma_\xi + \tilde{\sigma_n}^2 \right) - \frac{\rho}{1-\psi}$") pde_model.add_hjb_equation(r"$\frac{\hat{c}^{1-\psi}}{1-\psi} * \rho * \zeta^{\psi-1} + \mu_w - \hat{c} + \mu_\zeta - \frac{\gamma}{2} * \left( \sigma_w^2 + \sigma_\zeta^2 - 2 * \frac{1-\gamma}{\gamma} * \sigma_w * \sigma_\zeta \right) - \frac{\rho}{1-\psi}$") print(pde_model)
-
Train and evaluate the best model with min loss
-
Plot the solutions, with additional variables defined by equations.
-
Plot variables in 1D. Specifically, we plot \(p\) (price of capital), \(\sigma_x\) (volatility of \(x\)), \(\Omega = \frac{\xi}{\zeta}\) (relative investment opportunities), \(\sigma+\sigma_p\) (aggregate risk), \(\pi\) (price of risk), and \(r\) (risk-free rate) as functions of \(v\) for \(x=0.05, 0.1, 0.2\), and as functions of \(x\) for \(v=0.1, 0.25, 0.6\).
fig, ax = plt.subplots(2, 3, figsize=(18, 12)) for x_val, linestyle in [(0.05, "-"), (0.1, ":"), (0.2, "--")]: sv = torch.ones((100, 2), device=pde_model.device) * x_val sv[:, 1] = torch.linspace(0.05, 0.95, 100) for i, sv_name in enumerate(pde_model.state_variables): pde_model.variable_val_dict[sv_name] = sv[:, i:i+1] pde_model.update_variables(sv) p = pde_model.variable_val_dict["p"] sigx = pde_model.variable_val_dict["sigx"] omega = pde_model.variable_val_dict["xi"] / pde_model.variable_val_dict["zeta"] ax[0][0].plot(sv[:, 1].detach().cpu().numpy(), p.detach().cpu().numpy().reshape(-1), linestyle=linestyle, label=f"x={x_val}") ax[0][1].plot(sv[:, 1].detach().cpu().numpy(), sigx.detach().cpu().numpy().reshape(-1), linestyle=linestyle, label=f"x={x_val}") ax[0][2].plot(sv[:, 1].detach().cpu().numpy(), omega.detach().cpu().numpy().reshape(-1), linestyle=linestyle, label=f"x={x_val}") ax[0][0].set_title(r"p") ax[0][1].set_title(r"$\sigma_x$") ax[0][2].set_title(r"$\Omega = \xi/\zeta$") ax[0][0].legend() ax[0][1].legend() ax[0][2].legend() for v_val, linestyle in [(0.1, "-"), (0.25, ":"), (0.6, "--")]: sv = torch.ones((100, 2), device=pde_model.device) * v_val sv[:, 0] = torch.linspace(0.05, 0.95, 100) for i, sv_name in enumerate(pde_model.state_variables): pde_model.variable_val_dict[sv_name] = sv[:, i:i+1] pde_model.update_variables(sv) p = pde_model.variable_val_dict["p"] sigx = pde_model.variable_val_dict["sigx"] omega = pde_model.variable_val_dict["xi"] / pde_model.variable_val_dict["zeta"] ax[1][0].plot(sv[:, 0].detach().cpu().numpy(), p.detach().cpu().numpy().reshape(-1), linestyle=linestyle, label=f"v={v_val}") ax[1][1].plot(sv[:, 0].detach().cpu().numpy(), sigx.detach().cpu().numpy().reshape(-1), linestyle=linestyle, label=f"v={v_val}") ax[1][2].plot(sv[:, 0].detach().cpu().numpy(), omega.detach().cpu().numpy().reshape(-1), linestyle=linestyle, label=f"v={v_val}") ax[1][0].set_title(r"p") ax[1][1].set_title(r"$\sigma_x$") ax[1][2].set_title(r"$\Omega = \xi/\zeta$") ax[1][0].legend() ax[1][1].legend() ax[1][2].legend() plt.subplots_adjust() plt.show() fig, ax = plt.subplots(2, 3, figsize=(18, 12)) for x_val, linestyle in [(0.05, "-"), (0.1, ":"), (0.2, "--")]: sv = torch.ones((100, 2), device=pde_model.device) * x_val sv[:, 1] = torch.linspace(0.05, 0.95, 100) for i, sv_name in enumerate(pde_model.state_variables): pde_model.variable_val_dict[sv_name] = sv[:, i:i+1] pde_model.update_variables(sv) sigsigp = pde_model.variable_val_dict["sigma"] + pde_model.variable_val_dict["sigp"] pi = pde_model.variable_val_dict["signxi"] r = pde_model.variable_val_dict["r"] ax[0][0].plot(sv[:, 1].detach().cpu().numpy(), sigsigp.detach().cpu().numpy().reshape(-1), linestyle=linestyle, label=f"x={x_val}") ax[0][1].plot(sv[:, 1].detach().cpu().numpy(), pi.detach().cpu().numpy().reshape(-1), linestyle=linestyle, label=f"x={x_val}") ax[0][2].plot(sv[:, 1].detach().cpu().numpy(), r.detach().cpu().numpy().reshape(-1), linestyle=linestyle, label=f"x={x_val}") ax[0][0].set_title(r"$\sigma+\sigma_p$") ax[0][1].set_title(r"$\pi$") ax[0][2].set_title("r") ax[0][0].legend() ax[0][1].legend() ax[0][2].legend() for v_val, linestyle in [(0.1, "-"), (0.25, ":"), (0.6, "--")]: sv = torch.ones((100, 2), device=pde_model.device) * v_val sv[:, 0] = torch.linspace(0.05, 0.95, 100) for i, sv_name in enumerate(pde_model.state_variables): pde_model.variable_val_dict[sv_name] = sv[:, i:i+1] pde_model.update_variables(sv) sigsigp = pde_model.variable_val_dict["sigma"] + pde_model.variable_val_dict["sigp"] pi = pde_model.variable_val_dict["signxi"] r = pde_model.variable_val_dict["r"] ax[1][0].plot(sv[:, 0].detach().cpu().numpy(), sigsigp.detach().cpu().numpy().reshape(-1), linestyle=linestyle, label=f"v={v_val}") ax[1][1].plot(sv[:, 0].detach().cpu().numpy(), pi.detach().cpu().numpy().reshape(-1), linestyle=linestyle, label=f"v={v_val}") ax[1][2].plot(sv[:, 0].detach().cpu().numpy(), r.detach().cpu().numpy().reshape(-1), linestyle=linestyle, label=f"v={v_val}") ax[1][0].set_title(r"$\sigma+\sigma_p$") ax[1][1].set_title(r"$\pi$") ax[1][2].set_title("r") ax[1][0].legend() ax[1][1].legend() ax[1][2].legend() plt.subplots_adjust() plt.show()
-
Sebastian Di Tella, "Uncertainty Shocks and Balance Sheet Recessions", Journal of Political Economy, 125(6): 2038-2081, 2017 ↩