Skip to content

Commit

Permalink
Merge pull request #865 from sbenthall/i862
Browse files Browse the repository at this point in the history
PortfolioFrameModel and FrameAgentType
  • Loading branch information
sbenthall authored Sep 2, 2021
2 parents 7592b71 + 4d4056c commit f59ffdd
Show file tree
Hide file tree
Showing 9 changed files with 1,494 additions and 18 deletions.
2 changes: 2 additions & 0 deletions Documentation/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@ Release Data: TBD

#### Major Changes

* FrameAgentType for modular definitions of agents [#865](https://github.com/econ-ark/HARK/pull/865)
* PortfolioConsumerFrameType, a port of PortfolioConsumerType to use Frames [#865](https://github.com/econ-ark/HARK/pull/865)
* Input parameters for cyclical models now indexed by t [#1039](https://github.com/econ-ark/HARK/pull/1039)
* A IndexDistribution class for representing time-indexed probability distributions [#1018](https://github.com/econ-ark/pull/1018/).
* Adds new consumption-savings-portfolio model `RiskyContrib`, which represents an agent who can save in risky and risk-free assets but faces
Expand Down
3 changes: 0 additions & 3 deletions HARK/ConsumptionSaving/ConsAggShockModel.py
Original file line number Diff line number Diff line change
Expand Up @@ -1022,9 +1022,6 @@ def get_shocks(self):
# TODO: replace poststate_vars functionality with shocks here
EmpNow = self.state_now["EmpNow"]

if employed.sum() != emp_permute.size:
import pdb; pdb.set_trace()

# It's really this permutation that is the shock...
# This apparatus is trying to 'exact match' the 'internal' Markov process.
EmpNow[employed] = self.RNG.permutation(emp_permute)
Expand Down
294 changes: 294 additions & 0 deletions HARK/ConsumptionSaving/ConsPortfolioFrameModel.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,294 @@
"""
This file contains classes and functions for representing,
solving, and simulating agents who must allocate their resources
among consumption, saving in a risk-free asset (with a low return),
and saving in a risky asset (with higher average return).
This file also demonstrates a "frame" model architecture.
"""
import numpy as np
from scipy.optimize import minimize_scalar
from copy import deepcopy
from HARK import NullFunc, Frame, FrameAgentType # Basic HARK features
from HARK.ConsumptionSaving.ConsIndShockModel import (
IndShockConsumerType, # PortfolioConsumerType inherits from it
utility, # CRRA utility function
utility_inv, # Inverse CRRA utility function
utilityP, # CRRA marginal utility function
utility_invP, # Derivative of inverse CRRA utility function
utilityP_inv, # Inverse CRRA marginal utility function
init_idiosyncratic_shocks, # Baseline dictionary to build on
)
from HARK.ConsumptionSaving.ConsRiskyAssetModel import (
RiskyAssetConsumerType
)
from HARK.ConsumptionSaving.ConsPortfolioModel import (
init_portfolio,
solveConsPortfolio,
PortfolioConsumerType,
PortfolioSolution
)

from HARK.distribution import combine_indep_dstns, add_discrete_outcome_constant_mean
from HARK.distribution import (
IndexDistribution,
Lognormal,
MeanOneLogNormal,
Bernoulli # Random draws for simulating agents
)
from HARK.interpolation import (

LinearInterp, # Piecewise linear interpolation
CubicInterp, # Piecewise cubic interpolation
LinearInterpOnInterp1D, # Interpolator over 1D interpolations
BilinearInterp, # 2D interpolator
ConstantFunction, # Interpolator-like class that returns constant value
IdentityFunction, # Interpolator-like class that returns one of its arguments
ValueFuncCRRA,
MargValueFuncCRRA,
MargMargValueFuncCRRA
)

class PortfolioConsumerFrameType(FrameAgentType, PortfolioConsumerType):
"""
A consumer type with a portfolio choice, using Frame architecture.
A subclass of PortfolioConsumerType for now.
This is mainly to keep the _solver_ logic intact.
"""

def __init__(self, **kwds):
params = init_portfolio.copy()
params.update(kwds)
kwds = params

# Initialize a basic consumer type
PortfolioConsumerType.__init__(
self, **kwds
)

# Set the solver for the portfolio model, and update various constructed attributes
self.solve_one_period = solveConsPortfolio
self.update()

## TODO: Should be defined in the configuration.
self.aggs = {'PermShkAggNow' : None, 'PlvlAgg' : None, 'Risky' : None} # aggregate values
# -- handled differently because only one value each per AgentType
self.shocks = {'Adjust' : None, 'PermShk' : None, 'TranShk' : None}
self.controls = {'cNrm' : None, 'Share' : None}
self.state_now = {
'Rport' : None,
'aLvl' : None,
'aNrm' : None,
'bNrm' : None,
'mNrm' : None,
'pLvl' : None
}

# TODO: streamline this so it can draw the parameters from context
def birth_aNrmNow(self, N):
"""
Birth value for aNrmNow
"""
return Lognormal(
mu=self.aNrmInitMean,
sigma=self.aNrmInitStd,
seed=self.RNG.randint(0, 2 ** 31 - 1),
).draw(N)

# TODO: streamline this so it can draw the parameters from context
def birth_pLvlNow(self, N):
"""
Birth value for pLvlNow
"""
pLvlInitMeanNow = self.pLvlInitMean + np.log(
self.aggs["PlvlAgg"]
) # Account for newer cohorts having higher permanent income

return Lognormal(
pLvlInitMeanNow,
self.pLvlInitStd,
seed=self.RNG.randint(0, 2 ** 31 - 1)
).draw(N)

def transition_Rport(self, **context):

Rport = (
context["Share"] * context["Risky"]
+ (1.0 - context["Share"]) * self.parameters['Rfree']
)
return Rport

def transition(self, **context):
pLvlPrev = context['pLvl']
aNrmPrev = context['aNrm']

# This should be computed separately in its own transition
# Using IndShock get_Rfree instead of generic.
RfreeNow = context['Rport']

# Calculate new states: normalized market resources and permanent income level
pLvlNow = pLvlPrev * context['PermShk'] # Updated permanent income level

# Updated aggregate permanent productivity level
PlvlAggNow = context['PlvlAgg'] * context['PermShkAggNow']
# "Effective" interest factor on normalized assets
ReffNow = RfreeNow / context['PermShk']
bNrmNow = ReffNow * aNrmPrev # Bank balances before labor income
mNrmNow = bNrmNow + context['TranShk'] # Market resources after income

return pLvlNow, PlvlAggNow, bNrmNow, mNrmNow

def transition_ShareNow(self, **context):
"""
Transition method for ShareNow.
"""
## Changed from HARK. See #1049. Should be added to context.
ShareNow = self.controls['Share'].copy()

# Loop over each period of the cycle, getting controls separately depending on "age"
for t in range(self.T_cycle):
these = t == self.t_cycle

# Get controls for agents who *can* adjust their portfolio share
those = np.logical_and(these, context['Adjust'])

ShareNow[those] = self.solution[t].ShareFuncAdj(context['mNrm'][those])

# Get Controls for agents who *can't* adjust their portfolio share
those = np.logical_and(
these,
np.logical_not(context['Adjust']))
ShareNow[those] = self.solution[t].ShareFuncFxd(
context['mNrm'][those], ShareNow[those]
)

return ShareNow,

def transition_cNrmNow(self, **context):
"""
Transition method for cNrmNow.
"""
cNrmNow = np.zeros(self.AgentCount) + np.nan
ShareNow = context["Share"]

# Loop over each period of the cycle, getting controls separately depending on "age"
for t in range(self.T_cycle):
these = t == self.t_cycle

# Get controls for agents who *can* adjust their portfolio share
those = np.logical_and(these, context['Adjust'])
cNrmNow[those] = self.solution[t].cFuncAdj(context['mNrm'][those])

# Get Controls for agents who *can't* adjust their portfolio share
those = np.logical_and(
these,
np.logical_not(context['Adjust']))
cNrmNow[those] = self.solution[t].cFuncFxd(
context['mNrm'][those], ShareNow[those]
)

return cNrmNow,

def transition_poststates(self, **context):
"""
Calculates end-of-period assets for each consumer of this type.
Parameters
----------
None
Returns
-------
None
"""
# should this be "Now", or "Prev"?!?
# todo: don't store on self
self.state_now['aNrm'] = context['mNrm'] - context['cNrm']
# Useful in some cases to precalculate asset level
self.state_now['aLvl'] = context['aNrm'] * context['pLvl']

return (self.state_now['aNrm'], self.state_now['aLvl'])

# maybe replace reference to init_portfolio to self.parameters?
frames = [
# todo : make an aggegrate value
Frame(('PermShkAggNow',), ('PermGroFacAgg',),
transition = lambda self, PermGroFacAgg : (PermGroFacAgg,)
),
Frame(
('PermShk'), None,
default = {'PermShk' : 1.0}, # maybe this is unnecessary because the shock gets sampled at t = 0
# this is discretized before it's sampled
transition = IndexDistribution(
Lognormal.from_mean_std,
{
'mean' : init_portfolio['PermGroFac'],
'std' : init_portfolio['PermShkStd']
}
).approx(
init_portfolio['PermShkCount'], tail_N=0
),
),
Frame(
('TranShk'), None,
default = {'TranShk' : 1.0}, # maybe this is unnecessary because the shock gets sampled at t = 0
transition = add_discrete_outcome_constant_mean(
IndexDistribution(
MeanOneLogNormal,
{
'sigma' : init_portfolio['TranShkStd']
}).approx(
init_portfolio['TranShkCount'], tail_N=0
),
p = init_portfolio['UnempPrb'], x = init_portfolio['IncUnemp']
)
),
Frame( ## TODO: Handle Risky as an Aggregate value
('Risky'),None,
transition = IndexDistribution(
Lognormal.from_mean_std,
{
'mean' : init_portfolio['RiskyAvg'],
'std' : init_portfolio['RiskyStd']
}
# seed=self.RNG.randint(0, 2 ** 31 - 1) : TODO: Seed logic
).approx(
init_portfolio['RiskyCount']
)
),
Frame(
('Adjust'),None,
default = {'Adjust' : False},
transition = IndexDistribution(
Bernoulli,
{'p' : init_portfolio['AdjustPrb']},
# seed=self.RNG.randint(0, 2 ** 31 - 1) : TODO: Seed logic
) # self.t_cycle input implied
),
Frame(
('Rport'), ('Share', 'Risky'),
transition = transition_Rport
),
## TODO risk free return rate
Frame(
('pLvl', 'PlvlAgg', 'bNrm', 'mNrm'),
('pLvl', 'aNrm', 'Rport', 'PlvlAgg', 'PermShk', 'TranShk', 'PermShkAggNow'),
default = {'pLvl' : birth_pLvlNow, 'PlvlAgg' : 1.0},
transition = transition
),
Frame(
('Share'), ('Adjust', 'mNrm'),
default = {'Share' : 0},
transition = transition_ShareNow
),
Frame(
('cNrm'), ('Adjust','mNrm','Share'),
transition = transition_cNrmNow
),
Frame(
('aNrm', 'aLvl'), ('aNrm', 'cNrm', 'mNrm', 'pLvl'),
default = {'aNrm' : birth_aNrmNow},
transition = transition_poststates
)
]
1 change: 0 additions & 1 deletion HARK/ConsumptionSaving/ConsPortfolioModel.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@

import numpy as np
from scipy.optimize import minimize_scalar

from HARK import MetricObject, NullFunc, AgentType # Basic HARK features
from HARK.ConsumptionSaving.ConsIndShockModel import (
IndShockConsumerType, # PortfolioConsumerType inherits from it
Expand Down
Loading

0 comments on commit f59ffdd

Please sign in to comment.