Skip to content

Commit

Permalink
Merge pull request #1012 from Mv77/ContribMod/RiskyAssetClass
Browse files Browse the repository at this point in the history
Class for risky-asset consumers
  • Loading branch information
llorracc authored Jun 3, 2021
2 parents e1ea781 + 5274471 commit 00ce66a
Show file tree
Hide file tree
Showing 3 changed files with 279 additions and 155 deletions.
1 change: 1 addition & 0 deletions Documentation/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ Release Data: TBD
#### Minor Changes

* Fix bug in DCEGM's primary kink finder due to numpy no longer accepting NaN in integer arrays [#990](https://github.com/econ-ark/HARK/pull/990).
* Add a general class for consumers who can save using a risky asset [#1012](https://github.com/econ-ark/HARK/pull/1012/).

### 0.11.0

Expand Down
163 changes: 8 additions & 155 deletions HARK/ConsumptionSaving/ConsPortfolioModel.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@
init_idiosyncratic_shocks # Baseline dictionary to build on
)

from HARK.ConsumptionSaving.ConsRiskyAssetModel import RiskyAssetConsumerType

from HARK.distribution import combine_indep_dstns
from HARK.distribution import Lognormal, Bernoulli # Random draws for simulating agents
from HARK.interpolation import (
Expand Down Expand Up @@ -118,7 +120,7 @@ def __init__(
self.dvdsFuncFxd = dvdsFuncFxd


class PortfolioConsumerType(IndShockConsumerType):
class PortfolioConsumerType(RiskyAssetConsumerType):
"""
A consumer type with a portfolio choice. This agent type has log-normal return
factors. Their problem is defined by a coefficient of relative risk aversion,
Expand All @@ -130,7 +132,7 @@ class PortfolioConsumerType(IndShockConsumerType):
of the risky asset's return distribution must also be specified.
"""

time_inv_ = deepcopy(IndShockConsumerType.time_inv_)
time_inv_ = deepcopy(RiskyAssetConsumerType.time_inv_)
time_inv_ = time_inv_ + ["AdjustPrb", "DiscreteShareBool"]

def __init__(self, cycles=1, verbose=False, quiet=False, **kwds):
Expand All @@ -139,12 +141,10 @@ def __init__(self, cycles=1, verbose=False, quiet=False, **kwds):
kwds = params

# Initialize a basic consumer type
IndShockConsumerType.__init__(
RiskyAssetConsumerType.__init__(
self, cycles=cycles, verbose=verbose, quiet=quiet, **kwds
)

shock_vars = ['PermShk', 'TranShk','Adjust','Risky']

# Set the solver for the portfolio model, and update various constructed attributes
self.solve_one_period = solveConsPortfolio
self.update()
Expand All @@ -154,9 +154,8 @@ def pre_solve(self):
self.update_solution_terminal()

def update(self):
IndShockConsumerType.update(self)
self.update_RiskyDstn()
self.update_ShockDstn()

RiskyAssetConsumerType.update(self)
self.update_ShareGrid()
self.update_ShareLimit()

Expand Down Expand Up @@ -206,86 +205,6 @@ def update_solution_terminal(self):
dvdsFuncFxd=dvdsFuncFxd_terminal,
)

def update_RiskyDstn(self):
"""
Creates the attributes RiskyDstn from the primitive attributes RiskyAvg,
RiskyStd, and RiskyCount, approximating the (perceived) distribution of
returns in each period of the cycle.
Parameters
----------
None
Returns
-------
None
"""
# Determine whether this instance has time-varying risk perceptions
if (
(type(self.RiskyAvg) is list)
and (type(self.RiskyStd) is list)
and (len(self.RiskyAvg) == len(self.RiskyStd))
and (len(self.RiskyAvg) == self.T_cycle)
):
self.add_to_time_vary("RiskyAvg", "RiskyStd")
elif (type(self.RiskyStd) is list) or (type(self.RiskyAvg) is list):
raise AttributeError(
"If RiskyAvg is time-varying, then RiskyStd must be as well, and they must both have length of T_cycle!"
)
else:
self.add_to_time_inv("RiskyAvg", "RiskyStd")

# Generate a discrete approximation to the risky return distribution if the
# agent has age-varying beliefs about the risky asset
if "RiskyAvg" in self.time_vary:
self.RiskyDstn = []
for t in range(self.T_cycle):
self.RiskyDstn.append(
Lognormal.from_mean_std(
self.RiskyAvg[t],
self.RiskyStd[t]
).approx(self.RiskyCount)
)
self.add_to_time_vary("RiskyDstn")

# Generate a discrete approximation to the risky return distribution if the
# agent does *not* have age-varying beliefs about the risky asset (base case)
else:
self.RiskyDstn = Lognormal.from_mean_std(
self.RiskyAvg,
self.RiskyStd,
).approx(self.RiskyCount)
self.add_to_time_inv("RiskyDstn")

def update_ShockDstn(self):
"""
Combine the income shock distribution (over PermShk and TranShk) with the
risky return distribution (RiskyDstn) to make a new attribute called ShockDstn.
Parameters
----------
None
Returns
-------
None
"""
if "RiskyDstn" in self.time_vary:
self.ShockDstn = [
combine_indep_dstns(self.IncShkDstn[t], self.RiskyDstn[t])
for t in range(self.T_cycle)
]
else:
self.ShockDstn = [
combine_indep_dstns(self.IncShkDstn[t], self.RiskyDstn)
for t in range(self.T_cycle)
]
self.add_to_time_vary("ShockDstn")

# Mark whether the risky returns and income shocks are independent (they are)
self.IndepDstnBool = True
self.add_to_time_inv("IndepDstnBool")

def update_ShareGrid(self):
"""
Creates the attribute ShareGrid as an evenly spaced grid on [0.,1.], using
Expand Down Expand Up @@ -337,53 +256,6 @@ def update_ShareLimit(self):
self.ShareLimit = SharePF
self.add_to_time_inv("ShareLimit")

def get_Risky(self):
"""
Sets the shock RiskyNow as a single draw from a lognormal distribution.
Uses the attributes RiskyAvgTrue and RiskyStdTrue if RiskyAvg is time-varying,
else just uses the single values from RiskyAvg and RiskyStd.
Parameters
----------
None
Returns
-------
None
"""
if "RiskyDstn" in self.time_vary:
RiskyAvg = self.RiskyAvgTrue
RiskyStd = self.RiskyStdTrue
else:
RiskyAvg = self.RiskyAvg
RiskyStd = self.RiskyStd
RiskyAvgSqrd = RiskyAvg ** 2
RiskyVar = RiskyStd ** 2

mu = np.log(RiskyAvg / (np.sqrt(1.0 + RiskyVar / RiskyAvgSqrd)))
sigma = np.sqrt(np.log(1.0 + RiskyVar / RiskyAvgSqrd))
self.shocks['Risky'] = Lognormal(
mu, sigma, seed=self.RNG.randint(0, 2 ** 31 - 1)
).draw(1)

def get_Adjust(self):
"""
Sets the attribute AdjustNow as a boolean array of size AgentCount, indicating
whether each agent is able to adjust their risky portfolio share this period.
Uses the attribute AdjustPrb to draw from a Bernoulli distribution.
Parameters
----------
None
Returns
-------
None
"""
self.shocks['Adjust'] = Bernoulli(
self.AdjustPrb, seed=self.RNG.randint(0, 2 ** 31 - 1)
).draw(self.AgentCount)

def get_Rfree(self):
"""
Calculates realized return factor for each agent, using the attributes Rfree,
Expand Down Expand Up @@ -422,8 +294,7 @@ def initialize_sim(self):
# these need to be set because "post states",
# but are a control variable and shock, respectively
self.controls["Share"] = np.zeros(self.AgentCount)
self.shocks['Adjust'] = np.zeros(self.AgentCount, dtype=bool)
IndShockConsumerType.initialize_sim(self)
RiskyAssetConsumerType.initialize_sim(self)

def sim_birth(self, which_agents):
"""
Expand All @@ -445,24 +316,6 @@ def sim_birth(self, which_agents):
# here a shock is being used as a 'post state'
self.shocks['Adjust'][which_agents] = False

def get_shocks(self):
"""
Draw idiosyncratic income shocks, just as for IndShockConsumerType, then draw
a single common value for the risky asset return. Also draws whether each
agent is able to update their risky asset share this period.
Parameters
----------
None
Returns
-------
None
"""
IndShockConsumerType.get_shocks(self)
self.get_Risky()
self.get_Adjust()

def get_controls(self):
"""
Calculates consumption cNrmNow and risky portfolio share ShareNow using
Expand Down
Loading

0 comments on commit 00ce66a

Please sign in to comment.