10. How to Pay for a War: Part 2#

10.1. Overview#

This lecture presents another application of Markov jump linear quadratic dynamic programming and constitutes a sequel to an earlier lecture.

We use a method introduced in lecture Markov Jump LQ dynamic programming toimplement suggestions by [Barro, 1999] and [Barro and McCleary, 2003]) for extending his classic 1979 model of tax smoothing.

[Barro, 1979] model is about a government that borrows and lends in order to help it minimize an intertemporal measure of distortions caused by taxes.

Technically, [Barro, 1979] model looks a lot like a consumption-smoothing model.

Our generalizations of [Barro, 1979] will also look like souped-up consumption-smoothing models.

Wanting tractability induced [Barro, 1979] to assume that

  • the government trades only one-period risk-free debt, and

  • the one-period risk-free interest rate is constant

In our earlier lecture, we relaxed the second of these assumptions but not the first.

In particular, we used Markov jump linear quadratic dynamic programming to allow the exogenous interest rate to vary over time.

In this lecture, we add a maturity composition decision to the government’s problem by expanding the dimension of the state.

We assume

  • that the government borrows or saves in the form of risk-free bonds of maturities 1,2,,H.

  • that interest rates on those bonds are time-varying and in particular are governed by a jointly stationary stochastic process.

In addition to what’s in Anaconda, this lecture deploys the quantecon library:

!pip install --upgrade quantecon

Hide code cell output

Requirement already satisfied: quantecon in /home/runner/miniconda3/envs/quantecon/lib/python3.12/site-packages (0.8.2)
Requirement already satisfied: numba>=0.49.0 in /home/runner/miniconda3/envs/quantecon/lib/python3.12/site-packages (from quantecon) (0.60.0)
Requirement already satisfied: numpy>=1.17.0 in /home/runner/miniconda3/envs/quantecon/lib/python3.12/site-packages (from quantecon) (1.26.4)
Requirement already satisfied: requests in /home/runner/miniconda3/envs/quantecon/lib/python3.12/site-packages (from quantecon) (2.32.3)
Requirement already satisfied: scipy>=1.5.0 in /home/runner/miniconda3/envs/quantecon/lib/python3.12/site-packages (from quantecon) (1.13.1)
Requirement already satisfied: sympy in /home/runner/miniconda3/envs/quantecon/lib/python3.12/site-packages (from quantecon) (1.13.2)
Requirement already satisfied: llvmlite<0.44,>=0.43.0dev0 in /home/runner/miniconda3/envs/quantecon/lib/python3.12/site-packages (from numba>=0.49.0->quantecon) (0.43.0)
Requirement already satisfied: charset-normalizer<4,>=2 in /home/runner/miniconda3/envs/quantecon/lib/python3.12/site-packages (from requests->quantecon) (3.3.2)
Requirement already satisfied: idna<4,>=2.5 in /home/runner/miniconda3/envs/quantecon/lib/python3.12/site-packages (from requests->quantecon) (3.7)
Requirement already satisfied: urllib3<3,>=1.21.1 in /home/runner/miniconda3/envs/quantecon/lib/python3.12/site-packages (from requests->quantecon) (2.2.3)
Requirement already satisfied: certifi>=2017.4.17 in /home/runner/miniconda3/envs/quantecon/lib/python3.12/site-packages (from requests->quantecon) (2024.8.30)
Requirement already satisfied: mpmath<1.4,>=1.1.0 in /home/runner/miniconda3/envs/quantecon/lib/python3.12/site-packages (from sympy->quantecon) (1.3.0)

Let’s start with some standard imports:

import quantecon as qe
import numpy as np
import matplotlib.pyplot as plt

10.2. Two example specifications#

We’ll describe two possible specifications

  • In one, each period the government issues zero-coupon bonds of one- and two-period maturities and redeems them only when they mature – in this version, the maturity structure of government debt at each date is partly inherited from the past.

  • In the second, the government redesigns the maturity structure of the debt each period.

10.3. One- and Two-period Bonds but No Restructuring#

Let

  • Tt denote tax collections

  • β be a discount factor

  • bt,t+1 be time t+1 goods that the government promises to pay at t

  • bt,t+2 betime t+2 goods that the government promises to pay at time t

  • Gt be government purchases

  • pt,t+1 be the number of time t goods received per time t+1 goods promised

  • pt,t+2 be the number of time t goods received per time t+2 goods promised.

Evidently, pt,t+1,pt,t+2 are inversely related to appropriate corresponding gross interest rates on government debt.

In the spirit of [Barro, 1979], government expenditures are governed by an exogenous stochastic process.

Given initial conditions b2,0,b1,0,z0,i0, where i0 is the initial Markov state, the government chooses a contingency plan for {bt,t+1,bt,t+2,Tt}t=0 to maximize.

E0t=0βt[Tt2+c1(bt,t+1bt,t+2)2]

subject to the constraints

Tt=Gt+bt2,t+bt1,tpt,t+2bt,t+2pt,t+1bt,t+1Gt=Ug,stztzt+1=A22,stzt+C2,stwt+1[pt,t+1pt,t+2Ug,stA22,stC2,st]functions of Markov state with transition matrix Π

Here

  • wt+1N(0,I) and Πij is the probability that the Markov state moves from state i to state j in one period

  • Tt,bt,t+1,bt,t+2 are control variables chosen at time t

  • variables bt1,t,bt2,t are endogenous state variables inherited from the past at time t

  • pt,t+1,pt,t+2 are exogenous state variables at time t

The parameter c1 imposes a penalty on the government’s issuing different quantities of one and two-period debt.

This penalty deters the government from taking large “long-short” positions in debt of different maturities.

An example below will show the penalty in action.

As well as extending the model to allow for a maturity decision for government debt, we can also in principle allow the matrices Ug,st,A22,st,C2,st to depend on the Markov state st.

Below, we will often adopt the convention that for matrices appearing in a linear state space, AtAst,CtCst and so on, so that dependence on t is always intermediated through the Markov state st.

10.4. Mapping into an LQ Markov Jump Problem#

First, define

b^t=bt1,t+bt2,t,

which is debt due at time t.

Then define the endogenous part of the state:

b¯t=[b^tbt1,t+1]

and the complete state vector

xt=[b¯tzt]

and the control vector

ut=[bt,t+1bt,t+2]

The endogenous part of state vector follows the law of motion:

[b^t+1bt,t+2]=[0100][b^tbt1,t+1]+[1001][bt,t+1bt,t+2]

or

b¯t+1=A11b¯t+B1ut

Define the following functions of the state

Gt=SG,txt,b^t=S1xt

and

Mt=[pt,t+1pt,t+2]

where pt,t+1 is the discount on one period loans in the discrete Markov state at time t and pt,t+2 is the discount on two-period loans in the discrete Markov state.

Define

St=SG,t+S1

Note that in discrete Markov state i

Tt=Mtut+Stxt

It follows that

Tt2=xtStStxt+utMtMtut+2utMtStxt

or

Tt2=xtRtxt+utQtut+2utWtxt

where

Rt=StSt,Qt=MtMt,Wt=MtSt

Because the payoff function also includes the penalty parameter on issuing debt of different maturities, we have:

Tt2+c1(bt,t+1bt,t+2)2=xtRtxt+utQtut+2utWtxt+c1utQcut

where Qc=[1111].

Therefore, the appropriate Q matrix in the Markov jump LQ problem is:

Qtc=Qt+c1Qc

The law of motion of the state in all discrete Markov states i is

xt+1=Atxt+But+Ctwt+1

where

At=[A1100A22,t],B=[B10],Ct=[0C2,t]

Thus, in this problem all the matrices apart from B may depend on the Markov state at time t.

As shown in the previous lecture, when provided with appropriate A,B,C,R,Q,W matrices for each Markov state the LQMarkov class can solve Markov jump LQ problems.

The function below maps the primitive matrices and parameters from the above two-period model into the matrices that the LQMarkov class requires:

def LQ_markov_mapping(A22, C2, Ug, p1, p2, c1=0):

    """
    Function which takes A22, C2, Ug, p_{t, t+1}, p_{t, t+2} and penalty
    parameter c1, and returns the required matrices for the LQMarkov
    model: A, B, C, R, Q, W.
    This version uses the condensed version of the endogenous state.
    """

    # Make sure all matrices can be treated as 2D arrays
    A22 = np.atleast_2d(A22)
    C2 = np.atleast_2d(C2)
    Ug = np.atleast_2d(Ug)
    p1 = np.atleast_2d(p1)
    p2 = np.atleast_2d(p2)

    # Find the number of states (z) and shocks (w)
    nz, nw = C2.shape

    # Create A11, B1, S1, S2, Sg, S matrices
    A11 = np.zeros((2, 2))
    A11[0, 1] = 1

    B1 = np.eye(2)

    S1 = np.hstack((np.eye(1), np.zeros((1, nz+1))))
    Sg = np.hstack((np.zeros((1, 2)), Ug))
    S = S1 + Sg

    # Create M matrix
    M = np.hstack((-p1, -p2))

    # Create A, B, C matrices
    A_T = np.hstack((A11, np.zeros((2, nz))))
    A_B = np.hstack((np.zeros((nz, 2)), A22))
    A = np.vstack((A_T, A_B))

    B = np.vstack((B1, np.zeros((nz, 2))))

    C = np.vstack((np.zeros((2, nw)), C2))

    # Create Q^c matrix
    Qc = np.array([[1, -1], [-1, 1]])

    # Create R, Q, W matrices

    R = S.T @ S
    Q = M.T @ M + c1 * Qc
    W = M.T @ S

    return A, B, C, R, Q, W

With the above function, we can proceed to solve the model in two steps:

  1. Use LQ_markov_mapping to map Ug,t,A22,t,C2,t,pt,t+1,pt,t+2 into the A,B,C,R,Q,W matrices for each of the n Markov states.

  2. Use the LQMarkov class to solve the resulting n-state Markov jump LQ problem.

10.5. Penalty on Different Issues Across Maturities#

To implement a simple example of the two-period model, we assume that Gt follows an AR(1) process:

Gt+1=G¯+ρGt+σwt+1

To do this, we set zt=[1Gt], and consequently:

A22=[10G¯ρ],C2=[0σ],Ug=[01]

Therefore, in this example, A22,C2 and Ug are not time-varying.

We will assume that there are two Markov states, one with a flatter yield curve, and one with a steeper yield curve.

In state 1, prices are:

pt,t+11=β,pt,t+21=β20.02

and in state 2, prices are:

pt,t+12=β,pt,t+22=β2+0.02

We first solve the model with no penalty parameter on different issuance across maturities, i.e. c1=0.

We specify that the transition matrix for the Markov state is

Π=[0.90.10.10.9]

Thus, each Markov state is persistent, and there is an equal chance of moving from one to the other.

# Model parameters
β, Gbar, ρ, σ, c1 = 0.95, 5, 0.8, 1, 0
p1, p2, p3, p4 = β, β**2 - 0.02, β, β**2 + 0.02

# Basic model matrices
A22 = np.array([[1, 0], [Gbar, ρ] ,])
C_2 = np.array([[0], [σ]])
Ug = np.array([[0, 1]])

A1, B1, C1, R1, Q1, W1 = LQ_markov_mapping(A22, C_2, Ug, p1, p2, c1)
A2, B2, C2, R2, Q2, W2 = LQ_markov_mapping(A22, C_2, Ug, p3, p4, c1)

# Small penalties on debt required to implement no-Ponzi scheme
R1[0, 0] = R1[0, 0] + 1e-9
R2[0, 0] = R2[0, 0] + 1e-9

# Construct lists of matrices correspond to each state
As = [A1, A2]
Bs = [B1, B2]
Cs = [C1, C2]
Rs = [R1, R2]
Qs = [Q1, Q2]
Ws = [W1, W2]

Π = np.array([[0.9, 0.1],
              [0.1, 0.9]])

# Construct and solve the model using the LQMarkov class
lqm = qe.LQMarkov(Π, Qs, Rs, As, Bs, Cs=Cs, Ns=Ws, beta=β)
lqm.stationary_values()

# Simulate the model
x0 = np.array([[100, 50, 1, 10]])
x, u, w, t = lqm.compute_sequence(x0, ts_length=300)

# Plot of one and two-period debt issuance
fig, (ax1, ax2) = plt.subplots(1, 2, figsize=(12, 4))
ax1.plot(u[0, :])
ax1.set_title('One-period debt issuance')
ax1.set_xlabel('Time')
ax2.plot(u[1, :])
ax2.set_title('Two-period debt issuance')
ax2.set_xlabel('Time')
plt.show()
_images/28e7e7dc2a8044f8470188cacd666123a8ab02a064d047c9712063bfcfbfb4c5.png

The above simulations show that when no penalty is imposed on different issuances across maturities, the government has an incentive to take large “long-short” positions in debt of different maturities.

To prevent such outcomes, we set c1=0.01.

This penalty is big enough to motivate the government to issue positive quantities of both one- and two-period debt:

# Put small penalty on different issuance across maturities
c1 = 0.01

A1, B1, C1, R1, Q1, W1 = LQ_markov_mapping(A22, C_2, Ug, p1, p2, c1)
A2, B2, C2, R2, Q2, W2 = LQ_markov_mapping(A22, C_2, Ug, p3, p4, c1)

# Small penalties on debt required to implement no-Ponzi scheme
R1[0, 0] = R1[0, 0] + 1e-9
R2[0, 0] = R2[0, 0] + 1e-9

# Construct lists of matrices
As = [A1, A2]
Bs = [B1, B2]
Cs = [C1, C2]
Rs = [R1, R2]
Qs = [Q1, Q2]
Ws = [W1, W2]

# Construct and solve the model using the LQMarkov class
lqm2 = qe.LQMarkov(Π, Qs, Rs, As, Bs, Cs=Cs, Ns=Ws, beta=β)
lqm2.stationary_values()

# Simulate the model
x, u, w, t = lqm2.compute_sequence(x0, ts_length=300)

# Plot of one and two-period debt issuance
fig, (ax1, ax2) = plt.subplots(1, 2, figsize=(12, 4))
ax1.plot(u[0, :])
ax1.set_title('One-period debt issuance')
ax1.set_xlabel('Time')
ax2.plot(u[1, :])
ax2.set_title('Two-period debt issuance')
ax2.set_xlabel('Time')
plt.show()
_images/436312df4f117385d67b81b2948b63b1e73e9b6bd284b68344221189d64c92d1.png

10.6. A Model with Restructuring#

We now alter two features of the previous model:

  1. The maximum horizon of government debt is now extended to a general H periods.

  2. The government is able to redesign the maturity structure of debt every period.

We impose a cost on adjusting issuance of each maturity by amending the payoff function to become:

Tt2+j=0H1c2(bt+jt1bt+j+1t)2

The government’s budget constraint is now:

Tt+j=1Hpt,t+jbt+jt=btt1+j=1H1pt,t+jbt+jt1+Gt

To map this into the Markov Jump LQ framework, we define state and control variables.

Let:

b¯t=[btt1bt+1t1bt+H1t1],ut=[bt+1tbt+2tbt+Ht]

Thus, b¯t is the endogenous state (debt issued last period) and ut is the control (debt issued today).

As before, we will also have the exogenous state zt, which determines government spending.

Therefore, the full state is:

xt=[b¯tzt]

We also define a vector pt that contains the time t price of goods in period t+j:

pt=[pt,t+1pt,t+2pt,t+H]

Finally, we define three useful matrices Ss,Sx,S~x:

[pt,t+1pt,t+2pt,t+H1]=Sspt where Ss=[100001000010]
[bt+1t1bt+2t1bt+T1t1]=Sxb¯t where Sx=[010000100001]
btt1=S~xb¯t where S~x=[1000]

In terms of dimensions, the first two matrices defined above are (H1)×H.

The last is 1×H

We can now write the government’s budget constraint in matrix notation.

We can rearrange the government budget constraint to become

Tt=btt1+j=1H1pt+jtbt+jt1+Gtj=1Hpt+jtbt+jt

or

Tt=S~xb¯t+(Sspt)(Sxb¯t)+Ugztptut

To express Tt as a function of the full state, let

Tt=[(S~x+ptSsSx)Ug]xtptut

To simplify the notation, let St=[(S~x+ptSsSx)Ug].

Then

Tt=Stxtptut

Therefore

Tt2=xtRtxt+utQtut+2utWtxt

where

Rt=StSt,Qt=ptpt,Wt=ptSt

where to economize on notation we adopt the convention that for the linear state matrices RtRst,QtWst and so on.

We’ll use this convention for the linear state matrices A,B,W and so on below.

Because the payoff function also includes the penalty parameter for rescheduling, we have:

Tt2+j=0H1c2(bt+jt1bt+j+1t)2=Tt2+c2(b¯tut)(b¯tut)

Because the complete state is xt and not b¯t, we rewrite this as:

Tt2+c2(Scxtut)(Scxtut)

where Sc=[I0]

Multiplying this out gives:

Tt2+c2xtScScxt2c2utScxt+c2utut

Therefore, with the cost term, we must amend our R,Q,W matrices as follows:

Rtc=Rt+c2ScSc
Qtc=Qt+c2I
Wtc=Wtc2Sc

To finish mapping into the Markov jump LQ setup, we need to construct the law of motion for the full state.

This is simpler than in the previous setup, as we now have b¯t+1=ut.

Therefore:

xt+1[b¯t+1zt+1]=Atxt+But+Ctwt+1

where

At=[000A22,t],B=[I0],C=[0C2,t]

This completes the mapping into a Markov jump LQ problem.

10.7. Restructuring as a Markov Jump Linear Quadratic Control Problem#

We can define a function that maps the primitives of the model with restructuring into the matrices required by the LQMarkov class:

def LQ_markov_mapping_restruct(A22, C2, Ug, T, p_t, c=0):

    """
    Function which takes A22, C2, T, p_t, c and returns the
    required matrices for the LQMarkov model: A, B, C, R, Q, W
    Note, p_t should be a T by 1 matrix
    c is the rescheduling cost (a scalar)
    This version uses the condensed version of the endogenous state
    """

    # Make sure all matrices can be treated as 2D arrays
    A22 = np.atleast_2d(A22)
    C2 = np.atleast_2d(C2)
    Ug = np.atleast_2d(Ug)
    p_t = np.atleast_2d(p_t)

    # Find the number of states (z) and shocks (w)
    nz, nw = C2.shape

    # Create Sx, tSx, Ss, S_t matrices (tSx stands for \tilde S_x)
    Ss = np.hstack((np.eye(T-1), np.zeros((T-1, 1))))
    Sx = np.hstack((np.zeros((T-1, 1)), np.eye(T-1)))
    tSx = np.zeros((1, T))
    tSx[0, 0] = 1

    S_t = np.hstack((tSx + p_t.T @ Ss.T @ Sx, Ug))

    # Create A, B, C matrices
    A_T = np.hstack((np.zeros((T, T)), np.zeros((T, nz))))
    A_B = np.hstack((np.zeros((nz, T)), A22))
    A = np.vstack((A_T, A_B))

    B = np.vstack((np.eye(T), np.zeros((nz, T))))
    C = np.vstack((np.zeros((T, nw)), C2))

    # Create cost matrix Sc
    Sc = np.hstack((np.eye(T), np.zeros((T, nz))))

    # Create R_t, Q_t, W_t matrices

    R_c = S_t.T @ S_t + c * Sc.T @ Sc
    Q_c = p_t @ p_t.T + c * np.eye(T)
    W_c = -p_t @ S_t - c * Sc

    return A, B, C, R_c, Q_c, W_c

10.7.1. Example with Restructuring#

As an example let H=3.

Assume that there are two Markov states, one with a flatter yield curve, the other with a steeper yield curve.

In state 1, prices are:

pt,t+11=0.9695,pt,t+21=0.902,pt,t+31=0.8369

and in state 2, prices are:

pt,t+12=0.9295,pt,t+22=0.902,pt,t+32=0.8769

We specify the same transition matrix and Gt process that we used earlier.

# New model parameters
H = 3
p1 = np.array([[0.9695], [0.902], [0.8369]])
p2 = np.array([[0.9295], [0.902], [0.8769]])
Pi = np.array([[0.9, 0.1], [0.1, 0.9]])

# Put penalty on different issuance across maturities
c2 = 0.5

A1, B1, C1, R1, Q1, W1 = LQ_markov_mapping_restruct(A22, C_2, Ug, H, p1, c2)
A2, B2, C2, R2, Q2, W2 = LQ_markov_mapping_restruct(A22, C_2, Ug, H, p2, c2)

# Small penalties on debt required to implement no-Ponzi scheme
R1[0, 0] = R1[0, 0] + 1e-9
R1[1, 1] = R1[1, 1] + 1e-9
R1[2, 2] = R1[2, 2] + 1e-9
R2[0, 0] = R2[0, 0] + 1e-9
R2[1, 1] = R2[1, 1] + 1e-9
R2[2, 2] = R2[2, 2] + 1e-9

# Construct lists of matrices
As = [A1, A2]
Bs = [B1, B2]
Cs = [C1, C2]
Rs = [R1, R2]
Qs = [Q1, Q2]
Ws = [W1, W2]

# Construct and solve the model using the LQMarkov class
lqm3 = qe.LQMarkov(Π, Qs, Rs, As, Bs, Cs=Cs, Ns=Ws, beta=β)
lqm3.stationary_values()

x0 = np.array([[5000, 5000, 5000, 1, 10]])
x, u, w, t = lqm3.compute_sequence(x0, ts_length=300)
# Plots of different maturities debt issuance

fig, (ax1, ax2, ax3, ax4) = plt.subplots(1, 4, figsize=(11, 3))
ax1.plot(u[0, :])
ax1.set_title('One-period debt issuance')
ax1.set_xlabel('Time')
ax2.plot(u[1, :])
ax2.set_title('Two-period debt issuance')
ax2.set_xlabel('Time')
ax3.plot(u[2, :])
ax3.set_title('Three-period debt issuance')
ax3.set_xlabel('Time')
ax4.plot(u[0, :] + u[1, :] + u[2, :])
ax4.set_title('Total debt issuance')
ax4.set_xlabel('Time')
plt.tight_layout()
plt.show()
_images/a33a3f512fc5e6a055931429724edae12bdbf00b65db6a2cd2ea0da45a3fa642.png
# Plot share of debt issuance that is short-term

fig, ax = plt.subplots()
ax.plot((u[0, :] / (u[0, :] + u[1, :] + u[2, :])))
ax.set_title('One-period debt issuance share')
ax.set_xlabel('Time')
plt.show()
_images/a98075eaad29ba77e518ddcdc921e4111cd9ff0b2b004838613ee2449756772e.png