-
-
Notifications
You must be signed in to change notification settings - Fork 199
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
PortfolioFrameModel and FrameAgentType #865
Changes from 43 commits
c43ed7c
115990a
da7a5e4
f8538da
e54f861
0caef0e
cd3b516
e573b69
40ad034
f54109b
0c705a8
173e842
fa6070c
d04bd8b
b817448
9d0795f
0244022
5e65852
6cd15c9
8c9e534
146f83e
9862373
17ae983
37d8558
9108b57
220df3e
e6941ce
b74f0f4
277b9ef
804d619
705b8c4
11ff594
872bcb3
3b0286b
d1b5133
5d360b4
b91bc72
42a1c13
7451637
0755994
9bc011d
0b5e8a2
70b7533
95c58d6
126a404
b38de25
7aaad5a
06e74f4
29c7d97
29d8a8c
e44a5fd
8776deb
4d4056c
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,286 @@ | ||
""" | ||
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} # aggregate values | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I like what you have done here, fully specifying states, shocks, controls and aggregates. When creating new models, a frustration of mine has been the inconsistency with which that specification is done across different HARK agent types. Its sometimes done, sometimes not, sometimes inherited so one has to go hunting for the original class' attributes, sometimes fully specified. It is further complicated by two different objects There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. What I would like to do ultimately is move all of this information into the frame definitions. |
||
# -- handled differently because only one value each per AgentType | ||
self.shocks = {'Adjust' : None, 'PermShk' : None, 'Risky': None, 'TranShk' : None} | ||
self.controls = {'cNrm' : None, 'Share' : None} | ||
self.state_now = { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. was the There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. These are doing some logical work as per the current HARK simulation code. I can see how we could do an implementation where the state memory array was duplicated each period, and updated frame-by-frame. That's an implementation detail, as far as I'm concerned, not a matter of model logic. I'm not opposed to making the implementation consistent for shocks, controls, and states, as another PR. But there may be benefits to keeping state_prev. |
||
'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(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 = self.parameters['Rfree'] * np.ones(self.AgentCount) | ||
|
||
# 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] | ||
) | ||
|
||
self.controls["Share"] = ShareNow | ||
|
||
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] | ||
) | ||
|
||
# Store controls as attributes of self | ||
# redundant for now | ||
self.controls["cNrm"] = cNrmNow | ||
|
||
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"?!? | ||
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 = [ | ||
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( | ||
MeanOneLogNormal, | ||
{ | ||
'sigma' : 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 = IndexDistribution( | ||
# Need to discretize this "first" to add unemployed in | ||
# TODO: Encapsulate this widely used distribution form in the OO logic. | ||
lambda sigma, seed : add_discrete_outcome_constant_mean( | ||
MeanOneLogNormal(sigma = sigma, seed = seed).approx( | ||
init_portfolio['TranShkCount'], tail_N=0 | ||
), | ||
p = init_portfolio['UnempPrb'], x = init_portfolio['IncUnemp'] | ||
), | ||
{ | ||
'sigma' : init_portfolio['PermShkStd'] | ||
} | ||
), | ||
), | ||
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 | ||
) | ||
), | ||
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 | ||
), | ||
## TODO risk free return rate | ||
Frame( | ||
('pLvl', 'PlvlAgg', 'bNrm', 'mNrm'), | ||
('pLvl', 'aNrm', 'Rfree', '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 | ||
) | ||
] |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I am not familiar with this bit. Not sure what this is.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
pdb is the 'python debugger'. It looks like somebody (probably me) was debugging and accidentally got this breakpoint pushed into the master branch by mistake.
I could remove it in a separate PR, which would be cleaner, but did so here so I didn't forget.