Interactive online version: . Download notebook.
ConsumptionSaving model with Idiosyncratic Income Shocks and Different Interest Rates on Borrowing and Saving#
The KinkedRconsumerType
class
[1]:
# Initial imports and notebook setup, click arrow to show
import matplotlib.pyplot as plt
import numpy as np
from HARK.ConsumptionSaving.ConsIndShockModel import KinkedRconsumerType
from HARK.utilities import plot_funcs, plot_funcs_der
mystr = lambda number: f"{number:.4f}"
The module HARK.ConsumptionSaving.ConsIndShockModel
concerns consumptionsaving models with idiosyncratic shocks to (noncapital) income. All of the models assume CRRA utility with geometric discounting, no bequest motive, and income shocks are fully transitory or fully permanent.
ConsIndShockModel
currently includes three models: 1. A very basic “perfect foresight” model with no uncertainty. 2. A model with risk over transitory and permanent income shocks. 3. The model described in (2), with an interest rate for debt that differs from the interest rate for savings.
This notebook provides documentation for the third of these models. \(\newcommand{\CRRA}{\rho}\) \(\newcommand{\DiePrb}{\mathsf{D}}\) \(\newcommand{\PermGroFac}{\Gamma}\) \(\newcommand{\Rfree}{\mathsf{R}}\) \(\newcommand{\DiscFac}{\beta}\)
Statement of “kinked R” model#
Consider a small extension to the model faced by the IndShockConsumerType
: that the interest rate on borrowing (\(a_t < 0\)) is greater than the interest rate on saving (\(a_t > 0\)). Consumers who face this kind of problem are represented by the KinkedRconsumerType
class.
For a full theoretical treatment, this model analyzed in A Theory of the Consumption Function, With and Without Liquidity Constraints and its expanded edition.
Continuing to work with normalized variables (e.g. \(m_t\) represents the level of market resources divided by permanent income), the “kinked R” model can be stated as:
\begin{align*} v_t(m_t) &= \max_{c_t} u(c_t) + \DiscFac (1\DiePrb_{t+1}) \mathbb{E}_{t} \left[(\PermGroFac_{t+1}\psi_{t+1})^{1\CRRA} v_{t+1}(m_{t+1}) \right], \\ a_t &= m_t  c_t, \\ a_t &\geq \underline{a}, \\ m_{t+1} &= \Rfree_t/(\PermGroFac_{t+1} \psi_{t+1}) a_t + \theta_{t+1}, \\ \Rfree_t &= \begin{cases} \Rfree_{boro} & \text{if } a_t < 0\\ \Rfree_{save} & \text{if } a_t \geq 0, \end{cases}\\ \Rfree_{boro} &> \Rfree_{save}, \\ (\psi_{t+1},\theta_{t+1}) &\sim F_{t+1}, \\ \mathbb{E}[\psi]=\mathbb{E}[\theta] &= 1. \end{align*}
Solving the “kinked R” model#
The solution method for the “kinked R” model is nearly identical to that of the IndShockConsumerType
on which it is based, using the endogenous grid method; see the notebook for that model for more information. The only significant difference is that the interest factor varies by \(a_t\) across the exogenously chosen grid of endofperiod assets, with a discontinuity in \(\Rfree\) at \(a_t=0\).
To correctly handle this, the solveConsKinkedR
function inserts two instances of \(a_t=0\) into the grid of \(a_t\) values: the first corresponding to \(\Rfree_{boro}\) (\(a_t = 0\)) and the other corresponding to \(\Rfree_{save}\) (\(a_t = +0\)). The two consumption levels (and corresponding endogenous \(m_t\) gridpoints) represent points at which the agent’s first order condition is satisfied at exactly \(a_t=0\) at the two different interest factors.
In between these two points, the first order condition does not hold with equality: the consumer will end the period with exactly \(a_t=0\), consuming \(c_t=m_t\), but his marginal utility of consumption exceeds the marginal value of saving and is less than the marginal value of borrowing. This generates a consumption function with two kinks: two concave portions (for borrowing and saving) with a linear segment of slope 1 in between.
Example parameter values to construct an instance of KinkedRconsumerType#
The parameters required to create an instance of KinkedRconsumerType
are nearly identical to those for IndShockConsumerType
. The only difference is that the parameter \(\verb!Rfree!\) is replaced with \(\verb!Rboro!\) and \(\verb!Rsave!\).
While the parameter \(\verb!CubicBool!\) is required to create a valid KinkedRconsumerType
instance, it must be set to False
; cubic spline interpolation has not yet been implemented for this model. In the future, this restriction will be lifted.
Parameter 
Description 
Code 
Example value 
Timevarying? 

\(\DiscFac\) 
Intertemporal discount factor 
\(\verb!DiscFac!\) 
\(0.96\) 

\(\CRRA\) 
Coefficient of relative risk aversion 
\(\verb!CRRA!\) 
\(2.0\) 

\(\Rfree_{boro}\) 
Risk free interest factor for borrowing 
\(\verb!Rboro!\) 
\(1.20\) 

\(\Rfree_{save}\) 
Risk free interest factor for saving 
\(\verb!Rsave!\) 
\(1.01\) 

\(1  \DiePrb_{t+1}\) 
Survival probability 
\(\verb!LivPrb!\) 
\([0.98]\) 
\(\surd\) 
\(\PermGroFac_{t+1}\) 
Permanent income growth factor 
\(\verb!PermGroFac!\) 
\([1.01]\) 
\(\surd\) 
\(\sigma_\psi\) 
Standard deviation of log permanent income shocks 
\(\verb!PermShkStd!\) 
\([0.1]\) 
\(\surd\) 
\(N_\psi\) 
Number of discrete permanent income shocks 
\(\verb!PermShkCount!\) 
\(7\) 

\(\sigma_\theta\) 
Standard deviation of log transitory income shocks 
\(\verb!TranShkStd!\) 
\([0.2]\) 
\(\surd\) 
\(N_\theta\) 
Number of discrete transitory income shocks 
\(\verb!TranShkCount!\) 
\(7\) 

\(\mho\) 
Probability of being unemployed and getting \(\theta=\underline{\theta}\) 
\(\verb!UnempPrb!\) 
\(0.05\) 

\(\underline{\theta}\) 
Transitory shock when unemployed 
\(\verb!IncUnemp!\) 
\(0.3\) 

\(\mho^{Ret}\) 
Probability of being “unemployed” when retired 
\(\verb!UnempPrbRet!\) 
\(0.0005\) 

\(\underline{\theta}^{Ret}\) 
Transitory shock when “unemployed” and retired 
\(\verb!IncUnempRet!\) 
\(0.0\) 

\((none)\) 
Period of the lifecycle model when retirement begins 
\(\verb!T_retire!\) 
\(0\) 

\((none)\) 
Minimum value in assetsaboveminimum grid 
\(\verb!aXtraMin!\) 
\(0.001\) 

\((none)\) 
Maximum value in assetsaboveminimum grid 
\(\verb!aXtraMax!\) 
\(20.0\) 

\((none)\) 
Number of points in base assetsaboveminimum grid 
\(\verb!aXtraCount!\) 
\(48\) 

\((none)\) 
Exponential nesting factor for base assetsaboveminimum grid 
\(\verb!aXtraNestFac!\) 
\(3\) 

\((none)\) 
Additional values to add to assetsaboveminimum grid 
\(\verb!aXtraExtra!\) 
\(None\) 

\(\underline{a}\) 
Artificial borrowing constraint (normalized) 
\(\verb!BoroCnstArt!\) 
\(None\) 

\((none)\) 
Indicator for whether \(\verb!vFunc!\) should be computed 
\(\verb!vFuncBool!\) 
\(True\) 

\((none)\) 
Indicator for whether \(\verb!cFunc!\) should use cubic splines 
\(\verb!CubicBool!\) 
\(False\) 

\(T\) 
Number of periods in this type’s “cycle” 
\(\verb!T_cycle!\) 
\(1\) 

\((none)\) 
Number of times the “cycle” occurs 
\(\verb!cycles!\) 
\(0\) 
These example parameters are almost identical to those used for IndShockExample
in the prior notebook, except that the interest rate on borrowing is 20% (like a credit card), and the interest rate on saving is 1%. Moreover, the artificial borrowing constraint has been set to None
. The cell below defines a parameter dictionary with these example values.
[2]:
KinkedRdict = { # Click the arrow to expand this parameter dictionary
# Parameters shared with the perfect foresight model
"CRRA": 2.0, # Coefficient of relative risk aversion
"DiscFac": 0.96, # Intertemporal discount factor
"LivPrb": [0.98], # Survival probability
"PermGroFac": [1.01], # Permanent income growth factor
"BoroCnstArt": None, # Artificial borrowing constraint; imposed minimum level of endof period assets
# New parameters unique to the "kinked R" model
"Rboro": 1.20, # Interest factor on borrowing (a < 0)
"Rsave": 1.01, # Interest factor on saving (a > 0)
# Parameters that specify the income distribution over the lifecycle (shared with IndShockConsumerType)
"PermShkStd": [0.1], # Standard deviation of log permanent shocks to income
"PermShkCount": 7, # Number of points in discrete approximation to permanent income shocks
"TranShkStd": [0.2], # Standard deviation of log transitory shocks to income
"TranShkCount": 7, # Number of points in discrete approximation to transitory income shocks
"UnempPrb": 0.05, # Probability of unemployment while working
"IncUnemp": 0.3, # Unemployment benefits replacement rate
"UnempPrbRet": 0.0005, # Probability of "unemployment" while retired
"IncUnempRet": 0.0, # "Unemployment" benefits when retired
"T_retire": 0, # Period of retirement (0 > no retirement)
"tax_rate": 0.0, # Flat income tax rate (legacy parameter, will be removed in future)
# Parameters for constructing the "assets above minimum" grid (shared with IndShockConsumerType)
"aXtraMin": 0.001, # Minimum endofperiod "assets above minimum" value
"aXtraMax": 20, # Maximum endofperiod "assets above minimum" value
"aXtraCount": 48, # Number of points in the base grid of "assets above minimum"
"aXtraNestFac": 3, # Exponential nesting factor when constructing "assets above minimum" grid
"aXtraExtra": None, # Additional values to add to aXtraGrid
# A few other paramaters (shared with IndShockConsumerType)
"vFuncBool": True, # Whether to calculate the value function during solution
"CubicBool": False, # Preference shocks currently only compatible with linear cFunc
"T_cycle": 1, # Number of periods in the cycle for this agent type
# Parameters only used in simulation (shared with PerfForesightConsumerType)
"AgentCount": 10000, # Number of agents of this type
"T_sim": 500, # Number of periods to simulate
"aNrmInitMean": 6.0, # Mean of log initial assets
"aNrmInitStd": 1.0, # Standard deviation of log initial assets
"pLvlInitMean": 0.0, # Mean of log initial permanent income
"pLvlInitStd": 0.0, # Standard deviation of log initial permanent income
"PermGroFacAgg": 1.0, # Aggregate permanent income growth factor
"T_age": None, # Age after which simulated agents are automatically killed
}
Solving and examining the solution of the “kinked R” model#
The cell below creates an infinite horizon instance of KinkedRconsumerType
and solves its model by calling its solve
method.
[3]:
KinkyExample = KinkedRconsumerType(**KinkedRdict)
KinkyExample.cycles = 0 # Make the example infinite horizon
KinkyExample.solve()
The calc_limiting_values method must be run before the calc_stable_points method.
An element of a KinkedRconsumerType
’s solution will have all the same attributes as that of a IndShockConsumerType
; see that notebook for details.
We can plot the consumption function of our “kinked R” example, as well as the MPC:
[4]:
print("Kinked R consumption function:")
plot_funcs(KinkyExample.solution[0].cFunc, KinkyExample.solution[0].mNrmMin, 5)
print("Kinked R marginal propensity to consume:")
plot_funcs_der(KinkyExample.solution[0].cFunc, KinkyExample.solution[0].mNrmMin, 5)
Kinked R consumption function:
Kinked R marginal propensity to consume:
Simulating the “kinked R” model#
In order to generate simulated data, an instance of KinkedRconsumerType
needs to know how many agents there are that share these particular parameters (and are thus ex ante homogeneous), the distribution of states for newly “born” agents, and how many periods to simulated. These simulation parameters are described in the table below, along with example values.
Description 
Code 
Example value 

Number of consumers of this type 
\(\verb!AgentCount!\) 
\(10000\) 
Number of periods to simulate 
\(\verb!T_sim!\) 
\(500\) 
Mean of initial log (normalized) assets 
\(\verb!aNrmInitMean!\) 
\(6.0\) 
Stdev of initial log (normalized) assets 
\(\verb!aNrmInitStd!\) 
\(1.0\) 
Mean of initial log permanent income 
\(\verb!pLvlInitMean!\) 
\(0.0\) 
Stdev of initial log permanent income 
\(\verb!pLvlInitStd!\) 
\(0.0\) 
Aggregrate productivity growth factor 
\(\verb!PermGroFacAgg!\) 
\(1.0\) 
Age after which consumers are automatically killed 
\(\verb!T_age!\) 
\(None\) 
Here, we will simulate 10,000 consumers for 500 periods. All newly born agents will start with permanent income of exactly \(P_t = 1.0 = \exp(\verb!pLvlInitMean!)\), as \(\verb!pLvlInitStd!\) has been set to zero; they will have essentially zero assets at birth, as \(\verb!aNrmInitMean!\) is \(6.0\); assets will be less than \(1\%\) of permanent income at birth.
These example parameter values were already passed as part of the parameter dictionary that we used to create KinkyExample
, so it is ready to simulate. We need to set the track_vars
attribute to indicate the variables for which we want to record a history.
[5]:
KinkyExample.track_vars = ["mNrm", "cNrm", "pLvl"]
KinkyExample.initialize_sim()
KinkyExample.simulate()
[5]:
{'mNrm': array([[ 1.0011068 , 1.00256882, 1.00101819, ..., 1.00156301,
1.0003737 , 1.00516688],
[ 1.05541172, 1.24364019, 0.35325187, ..., 1.44916311,
1.14101133, 0.79232874],
[ 1.04454599, 1.01089495, 0.12266971, ..., 1.15389701,
1.08795268, 0.81155032],
...,
[ 0.94584825, 1.27766769, 1.81605935, ..., 0.70862776,
0.57715925, 1.51861495],
[ 1.19825465, 1.32998498, 2.09897862, ..., 0.80801571,
0.96687507, 1.61109117],
[ 0.52495288, 1.36898553, 1.81975329, ..., 1.05192278,
1.1163099 , 1.71343907]]),
'cNrm': array([[0.95575478, 0.95614394, 0.95573119, ..., 0.95587621, 0.95555964,
0.9568355 ],
[0.97020975, 1.01645863, 0.70724361, ..., 1.05580478, 0.99276336,
0.84001699],
[0.96731749, 0.9583602 , 0.51853659, ..., 0.99615385, 0.97880253,
0.84591041],
...,
[0.94072175, 1.0235892 , 1.11996143, ..., 0.81404498, 0.77373932,
1.06803332],
[1.00694793, 1.03386482, 1.16209389, ..., 0.84482668, 0.94664292,
1.08454428],
[0.75797377, 1.04111067, 1.12056273, ..., 0.96928106, 0.9862639 ,
1.10309844]]),
'pLvl': array([[0.85893446, 1.08875607, 1.08875607, ..., 1.17807023, 1.17807023,
0.85893446],
[1.01188512, 1.01015813, 0.93517011, ..., 1.09302465, 1.14116783,
0.86324343],
[0.869143 , 0.86765963, 0.93986152, ..., 1.05878626, 1.18993864,
0.90013641],
...,
[0.96746751, 0.91267336, 0.48656521, ..., 0.58611995, 0.63318207,
2.49213392],
[0.97232095, 0.99367866, 0.48900614, ..., 0.54380761, 0.66024277,
2.71332592],
[0.83515997, 1.08187367, 0.57608357, ..., 0.56704866, 0.61257946,
2.33056914]])}
We can plot the average (normalized) market resources in each simulated period:
[6]:
plt.plot(np.mean(KinkyExample.history["mNrm"], axis=1))
plt.xlabel("Time")
plt.ylabel("Mean market resources")
plt.show()
Now let’s plot the distribution of (normalized) assets \(a_t\) for the current population, after simulating for \(500\) periods; this should be fairly close to the long run distribution:
[7]:
plt.plot(
np.sort(KinkyExample.state_now["aNrm"]),
np.linspace(0.0, 1.0, KinkyExample.AgentCount),
)
plt.xlabel("Endofperiod assets")
plt.ylabel("Cumulative distribution")
plt.ylim(0.01, 1.01)
plt.show()
We can see there’s a significant point mass of consumers with exactly \(a_t=0\); these are consumers who do not find it worthwhile to give up a bit of consumption to begin saving (because \(\Rfree_{save}\) is too low), and also are not willing to finance additional consumption by borrowing (because \(\Rfree_{boro}\) is too high).
The smaller point masses in this distribution are due to \(\verb!HARK!\) drawing simulated income shocks from the discretized distribution, rather than the “true” lognormal distributions of shocks. For consumers who ended \(t1\) with \(a_{t1}=0\) in assets, there are only 8 values the transitory shock \(\theta_{t}\) can take on, and thus only 8 values of \(m_t\) thus \(a_t\) they can achieve; the value of \(\psi_t\) is immaterial to \(m_t\) when \(a_{t1}=0\). You can verify this by changing \(\verb!TranShkCount!\) to some higher value, like 25, in the dictionary above, then running the subsequent cells; the smaller point masses will not be visible to the naked eye.