Interactive online version:
Portfolio Models in HARK#
In this notebook, we consider the solution and simulation of a number of microeconomic problems in the context of optimal portfolio choice.
The agents in this model are first defined using the dictionary from the PerfForesightConsumerType
class and additional attributes are added using the IndShockConsumerType
class.
From there, the ConsPortfolioDict
dictionary is introduced to create the PortfolioConsumerType
and each of the subseqeunt agent types using it.
[1]:
from copy import copy
from time import time
import matplotlib.pyplot as plt
import numpy as np
from HARK.ConsumptionSaving.ConsIndShockModel import (
dist_params,
income_params,
init_lifecycle,
liv_prb,
time_params,
)
from HARK.ConsumptionSaving.ConsPortfolioModel import (
PortfolioConsumerType,
)
from HARK.utilities import plot_funcs
1. The baseline model of optimal portfolio choice#
[2]:
# Use default parameters that are built into the AgentType subclass
ConsPortfolioDict = {}
[3]:
# Make and solve an example portfolio choice consumer type
print("Now solving an example portfolio choice problem; this might take a moment...")
MyType = PortfolioConsumerType(**ConsPortfolioDict)
MyType.cycles = 0
t0 = time()
MyType.solve()
t1 = time()
MyType.cFunc = [MyType.solution[t].cFuncAdj for t in range(MyType.T_cycle)]
MyType.ShareFunc = [MyType.solution[t].ShareFuncAdj for t in range(MyType.T_cycle)]
print(
"Solving an infinite horizon portfolio choice problem took "
+ str(t1 - t0)
+ " seconds.",
)
Now solving an example portfolio choice problem; this might take a moment...
Solving an infinite horizon portfolio choice problem took 9.946223974227905 seconds.
[4]:
# Plot the consumption and risky-share functions
print("Consumption function over market resources:")
plot_funcs(MyType.cFunc[0], 0.0, 20.0)
print("Risky asset share as a function of market resources:")
print("Optimal (blue) versus Theoretical Limit (orange)")
plt.xlabel("Normalized Market Resources")
plt.ylabel("Portfolio Share")
plt.ylim(0.0, 1.0)
# Since we are using a discretization of the lognormal distribution,
# the limit is numerically computed and slightly different from
# the analytical limit obtained by Merton and Samuelson for infinite wealth
plot_funcs(
[
MyType.ShareFunc[0],
lambda m: MyType.ShareLimit * np.ones_like(m),
],
0.0,
200.0,
)
Consumption function over market resources:

Risky asset share as a function of market resources:
Optimal (blue) versus Theoretical Limit (orange)

[5]:
# Now simulate this consumer type
MyType.track_vars = ["cNrm", "Share", "aNrm", "t_age"]
MyType.T_sim = 100
MyType.initialize_sim()
MyType.simulate()
[5]:
{'cNrm': array([[0.94449375, 1.05100456, 0.9356256 , ..., 0.98425683, 0.85612338,
1.17989959],
[0.92584352, 1.04320533, 0.70324769, ..., 1.00654201, 0.9036357 ,
1.10969382],
[0.93661413, 1.01547995, 0.39485473, ..., 0.96439858, 0.90337007,
1.05355431],
...,
[0.93032901, 1.66761254, 1.15575218, ..., 1.32799353, 1.05324817,
1.13988003],
[0.93197068, 1.49486035, 1.08335149, ..., 1.24391658, 1.00136163,
1.08439969],
[0.9545978 , 1.53367694, 1.08795946, ..., 1.31940141, 1.03070202,
1.10826285]]),
'Share': array([[1. , 1. , 1. , ..., 1. , 1. ,
1. ],
[1. , 1. , 1. , ..., 1. , 1. ,
1. ],
[1. , 1. , 1. , ..., 1. , 1. ,
1. ],
...,
[1. , 0.86978448, 1. , ..., 1. , 1. ,
1. ],
[1. , 1. , 1. , ..., 1. , 1. ,
1. ],
[1. , 0.96980028, 1. , ..., 1. , 1. ,
1. ]]),
'aNrm': array([[0.57340032, 1.15100405, 0.5408086 , ..., 0.74710998, 0.31873697,
2.23779631],
[0.50702748, 1.09691301, 0.11566926, ..., 0.86648847, 0.43800078,
1.60717581],
[0.54430854, 0.91879238, 0. , ..., 0.65458599, 0.43723903,
1.16920216],
...,
[0.52205593, 7.87694693, 2.01268514, ..., 3.75606929, 1.16701567,
1.8690813 ],
[0.52786829, 5.68343548, 1.39267564, ..., 2.86797932, 0.83741291,
1.40090496],
[0.61315991, 6.16146305, 1.42910594, ..., 3.6625533 , 1.01374387,
1.59510094]]),
't_age': array([[ 1., 1., 1., ..., 1., 1., 1.],
[ 2., 2., 2., ..., 2., 2., 2.],
[ 3., 3., 3., ..., 3., 3., 3.],
...,
[ 4., 15., 32., ..., 75., 12., 76.],
[ 5., 16., 33., ..., 76., 13., 77.],
[ 6., 17., 34., ..., 77., 14., 78.]])}
[6]:
print("\n\n\n")
print("For derivation of the numerical limiting portfolio share")
print("as market resources approach infinity, see")
print(
"https://www.econ2.jhu.edu/people/ccarroll/public/lecturenotes/AssetPricing/Portfolio-CRRA/",
)
For derivation of the numerical limiting portfolio share
as market resources approach infinity, see
https://www.econ2.jhu.edu/people/ccarroll/public/lecturenotes/AssetPricing/Portfolio-CRRA/
2. Discrete portfolio choice#
[7]:
# Make another example type, but this one optimizes risky portfolio share only
# on the discrete grid of values implicitly chosen by RiskyCount, using explicit
# value maximization.
DiscConsPortfolioDict = ConsPortfolioDict.copy()
DiscConsPortfolioDict["DiscreteShareBool"] = True
# Have to actually construct value function for this to work
DiscConsPortfolioDict["vFuncBool"] = True
[8]:
# Create the discrete type using the dictionary, then change relevant attributes
DiscreteType = PortfolioConsumerType(**DiscConsPortfolioDict)
DiscreteType.cycles = 0
print("Now solving a discrete choice portfolio problem; this might take a minute...")
t0 = time()
DiscreteType.solve()
t1 = time()
DiscreteType.cFunc = [
DiscreteType.solution[t].cFuncAdj for t in range(DiscreteType.T_cycle)
]
DiscreteType.ShareFunc = [
DiscreteType.solution[t].ShareFuncAdj for t in range(DiscreteType.T_cycle)
]
print(
"Solving an infinite horizon discrete portfolio choice problem took "
+ str(t1 - t0)
+ " seconds.",
)
Now solving a discrete choice portfolio problem; this might take a minute...
Solving an infinite horizon discrete portfolio choice problem took 21.976933240890503 seconds.
[9]:
# Plot the consumption and risky-share functions
print("Consumption function over market resources:")
plot_funcs(DiscreteType.cFunc[0], 0.0, 50.0)
print("Risky asset share as a function of market resources:")
print("Optimal (blue) versus Theoretical Limit (orange)")
plt.xlabel("Normalized Market Resources")
plt.ylabel("Portfolio Share")
plt.ylim(0.0, 1.0)
# Since we are using a discretization of the lognormal distribution,
# the limit is numerically computed and slightly different from
# the analytical limit obtained by Merton and Samuelson for infinite wealth
plot_funcs(
[DiscreteType.ShareFunc[0], lambda m: DiscreteType.ShareLimit * np.ones_like(m)],
0.0,
200.0,
)
Consumption function over market resources:

Risky asset share as a function of market resources:
Optimal (blue) versus Theoretical Limit (orange)

[10]:
print("\n\n\n")
3. A model of “sticky” portfolio choice#
[11]:
# Make another example type, but this one can only update their risky portfolio
# share in any particular period with 15% probability.
StickyConsPortfolioDict = ConsPortfolioDict.copy()
StickyConsPortfolioDict["AdjustPrb"] = 0.15
[12]:
# Make and solve a discrete portfolio choice consumer type
print(
'Now solving a portfolio choice problem with "sticky" portfolio shares; this might take a moment...',
)
StickyType = PortfolioConsumerType(**StickyConsPortfolioDict)
StickyType.cycles = 0
t0 = time()
StickyType.solve()
t1 = time()
StickyType.cFuncAdj = [
StickyType.solution[t].cFuncAdj for t in range(StickyType.T_cycle)
]
StickyType.cFuncFxd = [
StickyType.solution[t].cFuncFxd for t in range(StickyType.T_cycle)
]
StickyType.ShareFunc = [
StickyType.solution[t].ShareFuncAdj for t in range(StickyType.T_cycle)
]
print(
"Solving an infinite horizon sticky portfolio choice problem took "
+ str(t1 - t0)
+ " seconds.",
)
Now solving a portfolio choice problem with "sticky" portfolio shares; this might take a moment...
Solving an infinite horizon sticky portfolio choice problem took 25.93682074546814 seconds.
[13]:
# Plot the consumption and risky-share functions
print(
"Consumption function over market resources when the agent can adjust his portfolio:",
)
plot_funcs(StickyType.cFuncAdj[0], 0.0, 50.0)
Consumption function over market resources when the agent can adjust his portfolio:

[14]:
print(
"Consumption function over market resources when the agent CAN'T adjust, by current share:",
)
M = np.linspace(0.0, 50.0, 200)
for s in np.linspace(0.0, 1.0, 21):
C = StickyType.cFuncFxd[0](M, s * np.ones_like(M))
plt.plot(M, C)
plt.xlim(0.0, 50.0)
plt.ylim(0.0, None)
plt.show()
Consumption function over market resources when the agent CAN'T adjust, by current share:

[15]:
print("Risky asset share function over market resources (when possible to adjust):")
print("Optimal (blue) versus Theoretical Limit (orange)")
plt.xlabel("Normalized Market Resources")
plt.ylabel("Portfolio Share")
plt.ylim(0.0, 1.0)
plot_funcs(
[StickyType.ShareFunc[0], lambda m: StickyType.ShareLimit * np.ones_like(m)],
0.0,
200.0,
)
Risky asset share function over market resources (when possible to adjust):
Optimal (blue) versus Theoretical Limit (orange)

Notice the wiggle in the blue line. This reflects the fact that the maximum grid point for which the solution is calculated is \(a=100\) and the (incorrect) assumption built into the model that the portfolio share asymptotes to the frictionless analytical case. An alternative (not yet implemented) would be to calculate the implicit limit defined by the rate of geometric decay among the last grid points and assume that this is the limit.
The difference between the two is likely due to the agent’s inability to adjust their portfolio.
4. Life-cycle model of portfolio choice#
[16]:
LC_ConsPortfolioDict = copy(ConsPortfolioDict)
LC_ConsPortfolioDict.update(time_params)
LC_ConsPortfolioDict.update(dist_params)
# Note the income specification overrides the pLvlInitMean from the SCF.
LC_ConsPortfolioDict.update(income_params)
LC_ConsPortfolioDict.update({"LivPrb": liv_prb})
LC_ConsPortfolioDict["RiskyAvg"] = [1.08] * init_lifecycle["T_cycle"]
LC_ConsPortfolioDict["RiskyStd"] = list(
np.linspace(0.20, 0.30, init_lifecycle["T_cycle"]),
)
LC_ConsPortfolioDict["RiskyAvgTrue"] = 1.08
LC_ConsPortfolioDict["RiskyStdTrue"] = 0.20
AgeVaryingRiskPercType = PortfolioConsumerType(**LC_ConsPortfolioDict)
AgeVaryingRiskPercType.cycles = 1
[17]:
# Solve the agent type with age-varying risk perceptions
# print('Now solving a portfolio choice problem with age-varying risk perceptions...')
t0 = time()
AgeVaryingRiskPercType.solve()
AgeVaryingRiskPercType.cFunc = [
AgeVaryingRiskPercType.solution[t].cFuncAdj
for t in range(AgeVaryingRiskPercType.T_cycle)
]
AgeVaryingRiskPercType.ShareFunc = [
AgeVaryingRiskPercType.solution[t].ShareFuncAdj
for t in range(AgeVaryingRiskPercType.T_cycle)
]
t1 = time()
print(
"Solving a "
+ str(AgeVaryingRiskPercType.T_cycle)
+ " period portfolio choice problem with age-varying risk perceptions took "
+ str(t1 - t0)
+ " seconds.",
)
Solving a 65 period portfolio choice problem with age-varying risk perceptions took 2.648599147796631 seconds.
[18]:
# Plot the consumption and risky-share functions
print("Consumption function over market resources in each lifecycle period:")
plot_funcs(AgeVaryingRiskPercType.cFunc, 0.0, 20.0)
print("Risky asset share function over market resources in each lifecycle period:")
plot_funcs(AgeVaryingRiskPercType.ShareFunc, 0.0, 200.0)
Consumption function over market resources in each lifecycle period:

Risky asset share function over market resources in each lifecycle period:
