Coverage for HARK / ConsumptionSaving / ConsWealthPortfolioModel.py: 99%
159 statements
« prev ^ index » next coverage.py v7.13.5, created at 2026-04-26 06:00 +0000
« prev ^ index » next coverage.py v7.13.5, created at 2026-04-26 06:00 +0000
1from copy import deepcopy
3import numpy as np
4from HARK.ConsumptionSaving.ConsPortfolioModel import (
5 PortfolioConsumerType,
6 PortfolioSolution,
7 make_portfolio_solution_terminal,
8)
9from HARK.distributions import expected
10from HARK.interpolation import (
11 BilinearInterp,
12 ConstantFunction,
13 CubicInterp,
14 LinearInterp,
15 MargValueFuncCRRA,
16 ValueFuncCRRA,
17)
18from HARK.Calibration.Assets.AssetProcesses import (
19 make_lognormal_RiskyDstn,
20 combine_IncShkDstn_and_RiskyDstn,
21 calc_ShareLimit_for_CRRA,
22)
23from HARK.Calibration.Income.IncomeProcesses import (
24 construct_lognormal_income_process_unemployment,
25 get_PermShkDstn_from_IncShkDstn,
26 get_TranShkDstn_from_IncShkDstn,
27)
28from HARK.ConsumptionSaving.ConsRiskyAssetModel import (
29 make_simple_ShareGrid,
30 make_AdjustDstn,
31)
32from HARK.ConsumptionSaving.ConsWealthUtilityModel import (
33 make_ChiFromOmega_function,
34)
35from HARK.ConsumptionSaving.ConsIndShockModel import (
36 make_lognormal_kNrm_init_dstn,
37 make_lognormal_pLvl_init_dstn,
38)
39from HARK.rewards import UtilityFuncCRRA
40from HARK.utilities import NullFunc, make_assets_grid
43def utility(c, a, CRRA, share=0.0, intercept=0.0):
44 w = a + intercept
45 return (c ** (1 - share) * w**share) ** (1 - CRRA) / (1 - CRRA)
48def dudc(c, a, CRRA, share=0.0, intercept=0.0):
49 u = utility(c, a, CRRA, share, intercept)
50 return u * (1 - CRRA) * (1 - share) / c
53def calc_m_nrm_next(shocks, b_nrm, perm_gro_fac):
54 """
55 Calculate future realizations of market resources mNrm from the income
56 shock distribution S and normalized bank balances b.
57 """
58 return b_nrm / (shocks["PermShk"] * perm_gro_fac) + shocks["TranShk"]
61def calc_dvdm_next(shocks, b_nrm, perm_gro_fac, crra, vp_func):
62 """
63 Evaluate realizations of marginal value of market resources next period,
64 based on the income distribution S and values of bank balances bNrm
65 """
66 m_nrm = calc_m_nrm_next(shocks, b_nrm, perm_gro_fac)
67 perm_shk_fac = shocks["PermShk"] * perm_gro_fac
68 return perm_shk_fac ** (-crra) * vp_func(m_nrm)
71def calc_end_dvdx(shocks, a_nrm, share, rfree, dvdb_func):
72 ex_ret = shocks - rfree # Excess returns
73 rport = rfree + share * ex_ret # Portfolio return
74 b_nrm = rport * a_nrm
76 # Calculate and return dvds (second term is all zeros)
77 dvdb = dvdb_func(b_nrm)
78 dvda = rport * dvdb
79 dvds = ex_ret * a_nrm * dvdb
80 return dvda, dvds
83def calc_med_v(shocks, b_nrm, perm_gro_fac, crra, v_func):
84 """
85 Calculate "intermediate" value from next period's bank balances, the
86 income shocks S, and the risky asset share.
87 """
88 m_nrm = calc_m_nrm_next(shocks, b_nrm, perm_gro_fac)
89 v_next = v_func(m_nrm)
90 return (shocks["PermShk"] * perm_gro_fac) ** (1.0 - crra) * v_next
93def calc_end_v(shocks, a_nrm, share, rfree, v_func):
94 # Calculate future realizations of bank balances bNrm
95 ex_ret = shocks - rfree
96 rport = rfree + share * ex_ret
97 b_nrm = rport * a_nrm
98 return v_func(b_nrm)
101###############################################################################
104def solve_one_period_WealthPortfolio(
105 solution_next,
106 IncShkDstn,
107 RiskyDstn,
108 LivPrb,
109 DiscFac,
110 CRRA,
111 Rfree,
112 PermGroFac,
113 BoroCnstArt,
114 aXtraGrid,
115 ShareGrid,
116 ShareLimit,
117 vFuncBool,
118 WealthShare,
119 WealthShift,
120 ChiFunc,
121):
122 """
123 Solves one period of the wealth-in-utility model with portfolio choice.
125 Parameters
126 ----------
127 solution_next : PortfolioSolution
128 Representation of the succeeding period's solution.
129 IncShkDstn : Distribution
130 Discrete distribution of permanent income shocks and transitory income
131 shocks. This is only used if the input IndepDstnBool is True, indicating
132 that income and return distributions are independent.
133 RiskyDstn : Distribution
134 Distribution of risky asset returns. This is only used if the input
135 IndepDstnBool is True, indicating that income and return distributions
136 are independent.
137 LivPrb : float
138 Survival probability; likelihood of being alive at the beginning of
139 the succeeding period.
140 DiscFac : float
141 Intertemporal discount factor for future utility.
142 CRRA : float
143 Coefficient of relative risk aversion.
144 Rfree : float
145 Risk free interest factor on end-of-period assets.
146 PermGroFac : float
147 Expected permanent income growth factor at the end of this period.
148 BoroCnstArt: float or None
149 Borrowing constraint for the minimum allowable assets to end the
150 period with. In this model, it is *required* to be zero.
151 aXtraGrid: np.array
152 Array of "extra" end-of-period asset values-- assets above the
153 absolute minimum acceptable level.
154 ShareGrid : np.array
155 Array of risky portfolio shares on which to define the interpolation
156 of the consumption function when Share is fixed. Also used when the
157 risky share choice is specified as discrete rather than continuous.
158 ShareLimit : float
159 Limiting lower bound of risky portfolio share as mNrm approaches infinity.
160 vFuncBool: bool
161 An indicator for whether the value function should be computed and
162 included in the reported solution.
163 WealthShare : float
164 Cobb-Douglas share for wealth (assets a_t) in the utility function.
165 Complementary share is for consumption.
166 WealthShift : float
167 Non-negative additive shifter for wealth in the utility function.
168 ChiFunc : function
169 Mapping from omega = EndOfPrdvPnvrs / aNrm to the optimal chi = cNrm / aNrm.
171 Returns
172 -------
173 solution_now : PortfolioSolution
174 Representation of the solution to this period's problem, including the
175 consumption function cFuncAdj and the risky share function ShareFuncAdj.
176 """
177 # Make sure the individual is liquidity constrained. Allowing a consumer to
178 # borrow *and* invest in an asset with unbounded (negative) returns is a bad mix.
179 if BoroCnstArt != 0.0:
180 raise ValueError("PortfolioConsumerType must have BoroCnstArt=0.0!")
182 # Define the current period utility function and effective discount factor
183 uFunc = UtilityFuncCRRA(CRRA)
184 DiscFacEff = DiscFac * LivPrb # "effective" discount factor
186 # Unpack next period's solution for easier access
187 vp_func_next = solution_next.vPfuncAdj
188 v_func_next = solution_next.vFuncAdj
190 # Set a flag for whether the natural borrowing constraint is zero, which
191 # depends on whether the smallest transitory income shock is zero
192 BoroCnstNat_iszero = (np.min(IncShkDstn.atoms[1]) == 0.0) or (
193 WealthShare != 0.0 and WealthShift == 0.0
194 )
196 # Prepare to calculate end-of-period marginal values by creating an array
197 # of market resources that the agent could have next period, considering
198 # the grid of end-of-period assets and the distribution of shocks he might
199 # experience next period.
201 # Unpack the risky return shock distribution
202 Risky_next = RiskyDstn.atoms
203 RiskyMax = np.max(Risky_next)
204 RiskyMin = np.min(Risky_next)
206 # bNrm represents R*a, balances after asset return shocks but before income.
207 # This just uses the highest risky return as a rough shifter for the aXtraGrid.
208 if BoroCnstNat_iszero:
209 aNrmGrid = aXtraGrid
210 bNrmGrid = np.insert(RiskyMax * aXtraGrid, 0, RiskyMin * aXtraGrid[0])
211 else:
212 # Add an asset point at exactly zero
213 aNrmGrid = np.insert(aXtraGrid, 0, 0.0)
214 bNrmGrid = RiskyMax * np.insert(aXtraGrid, 0, 0.0)
216 # Get grid and shock sizes, for easier indexing
217 aNrmCount = aNrmGrid.size
219 # Make tiled arrays to calculate future realizations of mNrm and Share when integrating over IncShkDstn
220 bNrmNext = bNrmGrid
222 # Calculate end-of-period marginal value of assets and shares at each point
223 # in aNrm and ShareGrid. Does so by taking expectation of next period marginal
224 # values across income and risky return shocks.
226 # Calculate intermediate marginal value of bank balances by taking expectations over income shocks
227 med_dvdb = expected(
228 calc_dvdm_next,
229 IncShkDstn,
230 args=(bNrmNext, PermGroFac, CRRA, vp_func_next),
231 )
232 med_dvdb_nvrs = uFunc.derinv(med_dvdb, order=(1, 0))
233 med_dvdb_nvrs_func = LinearInterp(bNrmGrid, med_dvdb_nvrs)
234 med_dvdb_func = MargValueFuncCRRA(med_dvdb_nvrs_func, CRRA)
236 # Make tiled arrays to calculate future realizations of bNrm and Share when integrating over RiskyDstn
237 aNrmNow, ShareNext = np.meshgrid(aNrmGrid, ShareGrid, indexing="ij")
239 # Evaluate realizations of value and marginal value after asset returns are realized
240 end_dvda, end_dvds = DiscFacEff * expected(
241 calc_end_dvdx,
242 RiskyDstn,
243 args=(aNrmNow, ShareNext, Rfree, med_dvdb_func),
244 )
245 end_dvda_nvrs = uFunc.derinv(end_dvda)
247 # Now find the optimal (continuous) risky share on [0,1] by solving the first
248 # order condition end_dvds == 0.
249 focs = end_dvds # Relabel for convenient typing
251 # For each value of aNrm, find the value of Share such that focs == 0
252 crossing = np.logical_and(focs[:, 1:] <= 0.0, focs[:, :-1] >= 0.0)
253 share_idx = np.argmax(crossing, axis=1)
254 # This represents the index of the segment of the share grid where dvds flips
255 # from positive to negative, indicating that there's a zero *on* the segment
257 # Calculate the fractional distance between those share gridpoints where the
258 # zero should be found, assuming a linear function; call it alpha
259 a_idx = np.arange(aNrmCount)
260 bot_s = ShareGrid[share_idx]
261 top_s = ShareGrid[share_idx + 1]
262 bot_f = focs[a_idx, share_idx]
263 top_f = focs[a_idx, share_idx + 1]
264 bot_c = end_dvda_nvrs[a_idx, share_idx]
265 top_c = end_dvda_nvrs[a_idx, share_idx + 1]
266 bot_dvda = end_dvda[a_idx, share_idx]
267 top_dvda = end_dvda[a_idx, share_idx + 1]
268 alpha = 1.0 - top_f / (top_f - bot_f)
270 # Calculate the continuous optimal risky share and optimal consumption
271 Share_now = (1.0 - alpha) * bot_s + alpha * top_s
272 end_dvda_nvrs_now = (1.0 - alpha) * bot_c + alpha * top_c
273 end_dvda_now = (1.0 - alpha) * bot_dvda + alpha * top_dvda
275 # If agent wants to put more than 100% into risky asset, he is constrained.
276 # Likewise if he wants to put less than 0% into risky asset, he is constrained.
277 constrained_top = focs[:, -1] > 0.0
278 constrained_bot = focs[:, 0] < 0.0
280 # Apply those constraints to both risky share and consumption (but lower
281 # constraint should never be relevant)
282 Share_now[constrained_top] = 1.0
283 Share_now[constrained_bot] = 0.0
284 end_dvda_nvrs_now[constrained_top] = end_dvda_nvrs[constrained_top, -1]
285 end_dvda_nvrs_now[constrained_bot] = end_dvda_nvrs[constrained_bot, 0]
286 end_dvda_now[constrained_top] = end_dvda[constrained_top, -1]
287 end_dvda_now[constrained_bot] = end_dvda[constrained_bot, 0]
289 # When the natural borrowing constraint is *not* zero, then aNrm=0 is in the
290 # grid, but there's no way to "optimize" the portfolio if a=0, and consumption
291 # can't depend on the risky share if it doesn't meaningfully exist. Apply
292 # a small fix to the bottom gridpoint (aNrm=0) when this happens.
293 if not BoroCnstNat_iszero:
294 Share_now[0] = 1.0
295 end_dvda_nvrs_now[0] = end_dvda_nvrs[0, -1]
296 end_dvda_now[0] = end_dvda[0, -1]
298 # Now this is where we look for optimal C
299 # for each a in the agrid find corresponding c that satisfies the euler equation
301 if WealthShare == 0.0:
302 cNrm_now = end_dvda_nvrs_now
303 else:
304 omega = end_dvda_nvrs_now / (aNrmGrid + WealthShift)
305 cNrm_now = ChiFunc(omega) * (aNrmGrid + WealthShift)
307 # Calculate the endogenous mNrm gridpoints when the agent adjusts his portfolio,
308 # then construct the consumption function when the agent can adjust his share
309 mNrm_now = np.insert(aNrmGrid + cNrm_now, 0, 0.0)
310 cNrm_now = np.insert(cNrm_now, 0, 0.0)
311 cFuncNow = LinearInterp(mNrm_now, cNrm_now)
313 dudc_now = dudc(cNrm_now, mNrm_now - cNrm_now, CRRA, WealthShare, WealthShift)
314 dudc_nvrs_now = uFunc.derinv(dudc_now, order=(1, 0))
315 dudc_nvrs_func_now = LinearInterp(mNrm_now, dudc_nvrs_now)
317 # Construct the marginal value (of mNrm) function
318 vPfuncNow = MargValueFuncCRRA(dudc_nvrs_func_now, CRRA)
320 # If the share choice is continuous, just make an ordinary interpolating function
321 if BoroCnstNat_iszero:
322 Share_lower_bound = ShareLimit
323 else:
324 Share_lower_bound = 1.0
325 Share_now = np.insert(Share_now, 0, Share_lower_bound)
326 ShareFuncNow = LinearInterp(mNrm_now, Share_now, ShareLimit, 0.0)
328 # Add the value function if requested
329 if vFuncBool:
330 # Calculate intermediate value by taking expectations over income shocks
331 med_v = expected(
332 calc_med_v, IncShkDstn, args=(bNrmNext, PermGroFac, CRRA, v_func_next)
333 )
335 # Construct the "intermediate value function" for this period
336 med_v_nvrs = uFunc.inv(med_v)
337 med_v_nvrs_func = LinearInterp(bNrmGrid, med_v_nvrs)
338 med_v_func = ValueFuncCRRA(med_v_nvrs_func, CRRA)
340 # Calculate end-of-period value by taking expectations
341 end_v = DiscFacEff * expected(
342 calc_end_v,
343 RiskyDstn,
344 args=(aNrmNow, ShareNext, Rfree, med_v_func),
345 )
346 end_v_nvrs = uFunc.inv(end_v)
348 # Now make an end-of-period value function over aNrm and Share
349 end_v_nvrs_func = BilinearInterp(end_v_nvrs, aNrmGrid, ShareGrid)
350 end_v_func = ValueFuncCRRA(end_v_nvrs_func, CRRA)
351 # This will be used later to make the value function for this period
353 # Create the value functions for this period, defined over market resources
354 # mNrm when agent can adjust his portfolio, and over market resources and
355 # fixed share when agent can not adjust his portfolio.
357 # Construct the value function
358 mNrm_temp = aXtraGrid # Just use aXtraGrid as our grid of mNrm values
359 cNrm_temp = cFuncNow(mNrm_temp)
360 aNrm_temp = np.maximum(mNrm_temp - cNrm_temp, 0.0) # Fix tiny violations
361 Share_temp = ShareFuncNow(mNrm_temp)
362 v_temp = uFunc(cNrm_temp) + end_v_func(aNrm_temp, Share_temp)
363 vNvrs_temp = uFunc.inv(v_temp)
364 vNvrsP_temp = uFunc.der(cNrm_temp) * uFunc.inverse(v_temp, order=(0, 1))
365 vNvrsFunc = CubicInterp(
366 np.insert(mNrm_temp, 0, 0.0), # x_list
367 np.insert(vNvrs_temp, 0, 0.0), # f_list
368 np.insert(vNvrsP_temp, 0, vNvrsP_temp[0]), # dfdx_list
369 )
370 # Re-curve the pseudo-inverse value function
371 vFuncNow = ValueFuncCRRA(vNvrsFunc, CRRA)
373 else: # If vFuncBool is False, fill in dummy values
374 vFuncNow = NullFunc()
376 # Package and return the solution
377 solution_now = PortfolioSolution(
378 cFuncAdj=cFuncNow,
379 ShareFuncAdj=ShareFuncNow,
380 vPfuncAdj=vPfuncNow,
381 vFuncAdj=vFuncNow,
382 )
383 return solution_now
386###############################################################################
388# Make a dictionary of constructors for the wealth-in-utility portfolio choice consumer type
389WealthPortfolioConsumerType_constructors_default = {
390 "IncShkDstn": construct_lognormal_income_process_unemployment,
391 "PermShkDstn": get_PermShkDstn_from_IncShkDstn,
392 "TranShkDstn": get_TranShkDstn_from_IncShkDstn,
393 "aXtraGrid": make_assets_grid,
394 "RiskyDstn": make_lognormal_RiskyDstn,
395 "ShockDstn": combine_IncShkDstn_and_RiskyDstn,
396 "ShareLimit": calc_ShareLimit_for_CRRA,
397 "ShareGrid": make_simple_ShareGrid,
398 "ChiFunc": make_ChiFromOmega_function,
399 "AdjustDstn": make_AdjustDstn,
400 "kNrmInitDstn": make_lognormal_kNrm_init_dstn,
401 "pLvlInitDstn": make_lognormal_pLvl_init_dstn,
402 "solution_terminal": make_portfolio_solution_terminal,
403}
405# Default parameters to make IncShkDstn using construct_lognormal_income_process_unemployment
406WealthPortfolioConsumerType_IncShkDstn_default = {
407 "PermShkStd": [0.1], # Standard deviation of log permanent income shocks
408 "PermShkCount": 7, # Number of points in discrete approximation to permanent income shocks
409 "TranShkStd": [0.1], # Standard deviation of log transitory income shocks
410 "TranShkCount": 7, # Number of points in discrete approximation to transitory income shocks
411 "UnempPrb": 0.05, # Probability of unemployment while working
412 "IncUnemp": 0.3, # Unemployment benefits replacement rate while working
413 "T_retire": 0, # Period of retirement (0 --> no retirement)
414 "UnempPrbRet": 0.005, # Probability of "unemployment" while retired
415 "IncUnempRet": 0.0, # "Unemployment" benefits when retired
416}
418# Default parameters to make aXtraGrid using make_assets_grid
419WealthPortfolioConsumerType_aXtraGrid_default = {
420 "aXtraMin": 0.001, # Minimum end-of-period "assets above minimum" value
421 "aXtraMax": 100, # Maximum end-of-period "assets above minimum" value
422 "aXtraNestFac": 1, # Exponential nesting factor for aXtraGrid
423 "aXtraCount": 200, # Number of points in the grid of "assets above minimum"
424 "aXtraExtra": None, # Additional other values to add in grid (optional)
425}
427# Default parameters to make RiskyDstn with make_lognormal_RiskyDstn (and uniform ShareGrid)
428WealthPortfolioConsumerType_RiskyDstn_default = {
429 "RiskyAvg": 1.08, # Mean return factor of risky asset
430 "RiskyStd": 0.18362634887, # Stdev of log returns on risky asset
431 "RiskyCount": 5, # Number of integration nodes to use in approximation of risky returns
432}
434WealthPortfolioConsumerType_ShareGrid_default = {
435 "ShareCount": 25 # Number of discrete points in the risky share approximation
436}
438# Default parameters to make ChiFunc with make_ChiFromOmega_function
439WealthPortfolioConsumerType_ChiFunc_default = {
440 "ChiFromOmega_N": 501, # Number of gridpoints in chi-from-omega function
441 "ChiFromOmega_bound": 15.0, # Highest gridpoint to use for it
442}
444# Make a dictionary with parameters for the default constructor for kNrmInitDstn
445WealthPortfolioConsumerType_kNrmInitDstn_default = {
446 "kLogInitMean": -12.0, # Mean of log initial capital
447 "kLogInitStd": 0.0, # Stdev of log initial capital
448 "kNrmInitCount": 15, # Number of points in initial capital discretization
449}
451# Make a dictionary with parameters for the default constructor for pLvlInitDstn
452WealthPortfolioConsumerType_pLvlInitDstn_default = {
453 "pLogInitMean": 0.0, # Mean of log permanent income
454 "pLogInitStd": 0.0, # Stdev of log permanent income
455 "pLvlInitCount": 15, # Number of points in initial capital discretization
456}
458# Make a dictionary to specify a risky asset consumer type
459WealthPortfolioConsumerType_solving_default = {
460 # BASIC HARK PARAMETERS REQUIRED TO SOLVE THE MODEL
461 "cycles": 1, # Finite, non-cyclic model
462 "T_cycle": 1, # Number of periods in the cycle for this agent type
463 "constructors": WealthPortfolioConsumerType_constructors_default, # See dictionary above
464 # PRIMITIVE RAW PARAMETERS REQUIRED TO SOLVE THE MODEL
465 "CRRA": 5.0, # Coefficient of relative risk aversion
466 "Rfree": [1.03], # Return factor on risk free asset
467 "DiscFac": 0.90, # Intertemporal discount factor
468 "LivPrb": [0.98], # Survival probability after each period
469 "PermGroFac": [1.01], # Permanent income growth factor
470 "BoroCnstArt": 0.0, # Artificial borrowing constraint
471 "WealthShare": 0.5, # Share of wealth in Cobb-Douglas aggregator in utility function
472 "WealthShift": 0.1, # Shifter for wealth in utility function
473 "DiscreteShareBool": False, # Whether risky asset share is restricted to discrete values
474 "PortfolioBisect": False, # This is a mystery parameter
475 "IndepDstnBool": True, # Whether income and return shocks are independent
476 "vFuncBool": False, # Whether to calculate the value function during solution
477 "CubicBool": False, # Whether to use cubic spline interpolation
478 "AdjustPrb": 1.0, # Probability that the agent can update their risky portfolio share each period
479 "ShareAugFac": 0, # Number of times to "zoom in" for an "augmented" search for optimal risky share
480 "RiskyShareFixed": None, # This just needs to exist because of inheritance, does nothing
481 "sim_common_Rrisky": True, # Whether risky returns have a shared/common value across agents
482}
483WealthPortfolioConsumerType_simulation_default = {
484 # PARAMETERS REQUIRED TO SIMULATE THE MODEL
485 "AgentCount": 10000, # Number of agents of this type
486 "T_age": None, # Age after which simulated agents are automatically killed
487 "PermGroFacAgg": 1.0, # Aggregate permanent income growth factor
488 # (The portion of PermGroFac attributable to aggregate productivity growth)
489 "NewbornTransShk": False, # Whether Newborns have transitory shock
490 # ADDITIONAL OPTIONAL PARAMETERS
491 "PerfMITShk": False, # Do Perfect Foresight MIT Shock
492 # (Forces Newborns to follow solution path of the agent they replaced if True)
493 "neutral_measure": False, # Whether to use permanent income neutral measure (see Harmenberg 2021)
494}
496# Assemble the default dictionary
497WealthPortfolioConsumerType_default = {}
498WealthPortfolioConsumerType_default.update(WealthPortfolioConsumerType_solving_default)
499WealthPortfolioConsumerType_default.update(
500 WealthPortfolioConsumerType_simulation_default
501)
502WealthPortfolioConsumerType_default.update(
503 WealthPortfolioConsumerType_aXtraGrid_default
504)
505WealthPortfolioConsumerType_default.update(
506 WealthPortfolioConsumerType_ShareGrid_default
507)
508WealthPortfolioConsumerType_default.update(
509 WealthPortfolioConsumerType_IncShkDstn_default
510)
511WealthPortfolioConsumerType_default.update(
512 WealthPortfolioConsumerType_RiskyDstn_default
513)
514WealthPortfolioConsumerType_default.update(WealthPortfolioConsumerType_ChiFunc_default)
515WealthPortfolioConsumerType_default.update(
516 WealthPortfolioConsumerType_kNrmInitDstn_default
517)
518WealthPortfolioConsumerType_default.update(
519 WealthPortfolioConsumerType_pLvlInitDstn_default
520)
521init_wealth_portfolio = WealthPortfolioConsumerType_default
523###############################################################################
526class WealthPortfolioConsumerType(PortfolioConsumerType):
527 r"""
528 A class for representing consumers who face idiosyncratic shocks to their labor
529 income (permanent and transitory) and can invest in a risky and a risk-free asset,
530 allocating their wealth as they choose. Unlike most HARK models, these consumers
531 have wealth as an argument directly in their utility function, as a Cobb-Douglas
532 aggregation with consumption.
534 The model is thus a combination of RiskyAssetConsumerType (or PortfolioConsumerType)
535 with WealthUtilityConsumerType.
537 .. math::
538 \newcommand{\CRRA}{\rho}
539 \newcommand{\LivPrb}{\mathsf{S}}
540 \newcommand{\PermGroFac}{\Gamma}
541 \newcommand{\Rfree}{\mathsf{R}}
542 \newcommand{\DiscFac}{\beta}
543 \newcommand{\WealthShare}{\alpha}
544 \newcommand{\WealthShift}{\xi}
545 \newcommand{\Rfree}{\mathsf{R}}
546 \newcommand{\Risky}{\mathfrak{R}}
548 \begin{align*}
549 \text{v}_t(m_t) &= \max_{c_t, s_t} \frac{c_t^{1-\CRRA}}{1-\CRRA} + \LivPrb_t \DiscFac \mathbb{E} \left[ (\PermGroFac_{t+1} \psi_{t+1})^{1-\CRRA}\text{v}_{t+1}(m_{t+1}) \right] \\
550 &\text{s.t.} \\
551 a_t &= m_t - c_t, \\
552 a_t &\geq 0, \\
553 s_t &\in [0,1], \\
554 m_{t+1} &= a_t R_{t+1}/(\PermGroFac_{t+1} \psi_{t+1}) + \theta_{t+1}, \\
555 R_{t+1} &= s_t \Risky_{t+1} + (1-s_t) \Rfree_{t+1}, \\
556 (\psi_{t+1},\theta_{t+1}) &\sim F_{t+1}, \\
557 \Risky_{t+1} &\sim G, \\
558 \mathbb{E}[\psi] &= 1. \\
559 \end{align*}
560 """
562 time_inv_ = deepcopy(PortfolioConsumerType.time_inv_)
563 time_inv_ = time_inv_ + [
564 "WealthShare",
565 "WealthShift",
566 "ChiFunc",
567 "RiskyDstn",
568 ]
569 default_ = {
570 "params": init_wealth_portfolio,
571 "solver": solve_one_period_WealthPortfolio,
572 "model": "ConsRiskyAsset.yaml",
573 "track_vars": ["aNrm", "cNrm", "mNrm", "Share", "pLvl"],
574 }
576 def pre_solve(self):
577 self.construct("solution_terminal")
578 self.solution_terminal.ShareFunc = ConstantFunction(1.0)