Consumption-Saving model with Idiosyncratic Income Shocks and Different Interest Rates on Borrowing and Saving#

The KinkedRconsumerType class

# 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_der, plot_funcs

mystr = lambda number: "{:.4f}".format(number)

The module HARK.ConsumptionSaving.ConsIndShockModel concerns consumption-saving models with idiosyncratic shocks to (non-capital) 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 end-of-period 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.




Example value



Intertemporal discount factor




Coefficient of relative risk aversion




Risk free interest factor for borrowing




Risk free interest factor for saving



\(1 - \DiePrb_{t+1}\)

Survival probability





Permanent income growth factor





Standard deviation of log permanent income shocks





Number of discrete permanent income shocks




Standard deviation of log transitory income shocks





Number of discrete transitory income shocks




Probability of being unemployed and getting \(\theta=\underline{\theta}\)




Transitory shock when unemployed




Probability of being “unemployed” when retired




Transitory shock when “unemployed” and retired




Period of the lifecycle model when retirement begins




Minimum value in assets-above-minimum grid




Maximum value in assets-above-minimum grid




Number of points in base assets-above-minimum grid




Exponential nesting factor for base assets-above-minimum grid




Additional values to add to assets-above-minimum grid




Artificial borrowing constraint (normalized)




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




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




Number of periods in this type’s “cycle”




Number of times the “cycle” occurs



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.

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 end-of 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 end-of-period "assets above minimum" value
    "aXtraMax": 20,  # Maximum end-of-period "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.

KinkyExample = KinkedRconsumerType(**KinkedRdict)
KinkyExample.cycles = 0  # Make the example infinite horizon

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:

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.



Example value

Number of consumers of this type



Number of periods to simulate



Mean of initial log (normalized) assets



Stdev of initial log (normalized) assets



Mean of initial log permanent income



Stdev of initial log permanent income



Aggregrate productivity growth factor



Age after which consumers are automatically killed



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.

KinkyExample.track_vars = ["mNrm", "cNrm", "pLvl"]
{'mNrm': array([[1.00087317, 1.00256882, 1.00119482, ..., 1.00169123, 1.0003737 ,
        [1.13302033, 0.79429517, 0.99000089, ..., 1.44926553, 1.1327061 ,
        [1.22634734, 1.13318775, 0.90522013, ..., 1.80965641, 1.22613254,
        [1.26119232, 1.83660642, 1.34165692, ..., 0.87710388, 1.32535358,
        [1.64167997, 2.14315942, 1.27855146, ..., 0.94435908, 1.22752974,
        [1.54481246, 2.17309355, 1.16278819, ..., 1.02002071, 1.26869299,
 'cNrm': array([[0.95569259, 0.95614394, 0.95577821, ..., 0.95591034, 0.95555964,
        [0.99066076, 0.8406199 , 0.95279859, ..., 1.05582281, 0.99057808,
        [1.01283485, 0.99070482, 0.90522013, ..., 1.11891917, 1.01278984,
        [1.02013673, 1.12330606, 1.03603333, ..., 0.87710388, 1.03300437,
        [1.09008958, 1.16800389, 1.0237744 , ..., 0.94028736, 1.01308263,
        [1.07264597, 1.17200813, 0.99849331, ..., 0.96078931, 1.02170853,
 'pLvl': array([[1.08875607, 1.08875607, 0.92780942, ..., 1.08875607, 1.17807023,
        [1.28263111, 1.01015813, 0.93246391, ..., 1.01015813, 1.38784946,
        [1.3964724 , 0.93723423, 0.90325499, ..., 0.97851549, 1.51102952,
        [0.97203001, 3.11901254, 0.31172034, ..., 0.88846645, 0.52675374,
        [0.97690634, 3.02131119, 0.36722845, ..., 0.82432754, 0.54926595,
        [0.9063829 , 3.03646804, 0.43262091, ..., 0.97111573, 0.47178345,

We can plot the average (normalized) market resources in each simulated period:

plt.plot(np.mean(KinkyExample.history["mNrm"], axis=1))
plt.ylabel("Mean market resources")

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:

    np.linspace(0.0, 1.0, KinkyExample.AgentCount),
plt.xlabel("End-of-period assets")
plt.ylabel("Cumulative distribution")
plt.ylim(-0.01, 1.01)

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 \(t-1\) with \(a_{t-1}=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_{t-1}=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.