Coverage for HARK/ConsumptionSaving/ConsWealthPortfolioModel.py: 83%
235 statements
« prev ^ index » next coverage.py v7.11.0, created at 2025-11-02 05:14 +0000
« prev ^ index » next coverage.py v7.11.0, created at 2025-11-02 05:14 +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.ConsIndShockModel import (
33 make_lognormal_kNrm_init_dstn,
34 make_lognormal_pLvl_init_dstn,
35)
36from HARK.rewards import UtilityFuncCRRA
37from HARK.utilities import NullFunc, make_assets_grid
40class ChiFromOmegaFunction:
41 """
42 A class for representing a function that takes in values of omega = EndOfPrdvP / aNrm
43 and returns the corresponding optimal chi = cNrm / aNrm. The only parameters
44 that matter for this transformation are the coefficient of relative risk
45 aversion (rho) and the share of wealth in the Cobb-Douglas aggregator (delta).
47 Parameters
48 ----------
49 CRRA : float
50 Coefficient of relative risk aversion.
51 WealthShare : float
52 Share for wealth in the Cobb-Douglas aggregator in CRRA utility function.
53 N : int, optional
54 Number of interpolating gridpoints to use (default 501).
55 z_bound : float, optional
56 Absolute value on the auxiliary variable z's boundary (default 15).
57 z represents values that are input into a logit transformation
58 scaled by the upper bound of chi, which yields chi values.
59 """
61 def __init__(self, CRRA, WealthShare, N=501, z_bound=15):
62 self.CRRA = CRRA
63 self.WealthShare = WealthShare
64 self.N = N
65 self.z_bound = z_bound
66 self.update()
68 def f(self, x):
69 """
70 Define the relationship between chi and omega, and evaluate on the vector
71 """
72 r = self.CRRA
73 d = self.WealthShare
74 return x ** (1 - d) * ((1 - d) * x ** (-d) - d * x ** (1 - d)) ** (-1 / r)
76 def update(self):
77 """
78 Construct the underlying interpolation of log(omega) on z.
79 """
80 # Make vectors of chi and z
81 chi_limit = (1.0 - self.WealthShare) / self.WealthShare
82 z_vec = np.linspace(-self.z_bound, self.z_bound, self.N)
83 exp_z = np.exp(z_vec)
84 chi_vec = chi_limit * exp_z / (1 + exp_z)
86 omega_vec = self.f(chi_vec)
87 log_omega_vec = np.log(omega_vec)
89 # Construct the interpolant
90 zFromLogOmegaFunc = LinearInterp(log_omega_vec, z_vec, lower_extrap=True)
92 # Store the function and limit as attributes
93 self.func = zFromLogOmegaFunc
94 self.limit = chi_limit
96 def __call__(self, omega):
97 """
98 Calculate optimal values of chi = cNrm / aNrm from values of omega.
100 Parameters
101 ----------
102 omega : np.array
103 One or more values of omega = EndOfPrdvP / aNrm.
105 Returns
106 -------
107 chi : np.array
108 Identically shaped array with optimal chi values.
109 """
110 z = self.func(np.log(omega))
111 exp_z = np.exp(z)
112 chi = self.limit * exp_z / (1 + exp_z)
113 return np.nan_to_num(chi)
116# Trivial constructor function
117def make_ChiFromOmega_function(CRRA, WealthShare, ChiFromOmega_N, ChiFromOmega_bound):
118 if WealthShare == 0.0:
119 return NullFunc()
120 return ChiFromOmegaFunction(
121 CRRA, WealthShare, N=ChiFromOmega_N, z_bound=ChiFromOmega_bound
122 )
125###############################################################################
128def utility(c, a, CRRA, share=0.0, intercept=0.0):
129 w = a + intercept
130 return (c ** (1 - share) * w**share) ** (1 - CRRA) / (1 - CRRA)
133def dudc(c, a, CRRA, share=0.0, intercept=0.0):
134 u = utility(c, a, CRRA, share, intercept)
135 return u * (1 - CRRA) * (1 - share) / c
138def duda(c, a, CRRA, share=0.0, intercept=0.0):
139 u = utility(c, a, CRRA, share, intercept)
140 return u * (1 - CRRA) * share / (a + intercept)
143def du2dc2(c, a, CRRA, share=0.0, intercept=0.0):
144 u = utility(c, a, CRRA, share, intercept)
145 return u * (1 - CRRA) * (share - 1) * ((1 - CRRA) * (share - 1) + 1) / c**2
148def du2dadc(c, a, CRRA, share=0.0, intercept=0.0):
149 u = utility(c, a, CRRA, share, intercept)
150 w = a + intercept
151 return u * (1 - CRRA) * share * (share - 1) * (CRRA - 1) / (c * w)
154def du_diff(c, a, CRRA, share=0.0, intercept=0.0):
155 ufac = utility(c, a, CRRA, share, intercept) * (1 - CRRA)
156 dudc = ufac * (1 - share) / c
158 if share == 0:
159 return dudc
160 else:
161 duda = ufac * share / (a + intercept)
163 return dudc - duda
166def du2_diff(c, a=None, CRRA=None, share=None, intercept=None, vp_a=None):
167 ufac = utility(c, a, CRRA, share, intercept) * (1 - CRRA)
168 w = a + intercept
170 dudcdc = ufac * (share - 1) * ((1 - CRRA) * (share - 1) + 1) / c**2
171 dudadc = ufac * share * (share - 1) * (CRRA - 1) / (c * w)
173 return dudcdc - dudadc
176def du2_jac(c, a, CRRA, share, intercept, vp_a):
177 du2_diag = du2_diff(c, a, CRRA, share, intercept, vp_a)
178 return np.diag(du2_diag)
181def chi_ratio(c, a, intercept):
182 return c / (a + intercept)
185def chi_func(chi, CRRA, share):
186 return chi ** (1 - share) * (
187 (1 - share) * chi ** (-share) - share * chi ** (1 - share)
188 ) ** (-1 / CRRA)
191def euler(c, a, CRRA, share, intercept, vp_a):
192 dufac = du_diff(c, a, CRRA, share, intercept)
193 return dufac - vp_a
196def euler2(c, a=None, CRRA=None, share=None, intercept=None, vp_a=None):
197 return euler(c, a, CRRA, share, intercept, vp_a) ** 2
200def euler2_diff(c, a=None, CRRA=None, share=None, intercept=None, vp_a=None):
201 return (
202 2
203 * euler(c, a, CRRA, share, intercept, vp_a)
204 * du2_diff(c, a, CRRA, share, intercept)
205 )
208def calc_m_nrm_next(shocks, b_nrm, perm_gro_fac):
209 """
210 Calculate future realizations of market resources mNrm from the income
211 shock distribution S and normalized bank balances b.
212 """
213 return b_nrm / (shocks["PermShk"] * perm_gro_fac) + shocks["TranShk"]
216def calc_dvdm_next(shocks, b_nrm, perm_gro_fac, crra, vp_func):
217 """
218 Evaluate realizations of marginal value of market resources next period,
219 based on the income distribution S and values of bank balances bNrm
220 """
221 m_nrm = calc_m_nrm_next(shocks, b_nrm, perm_gro_fac)
222 perm_shk_fac = shocks["PermShk"] * perm_gro_fac
223 return perm_shk_fac ** (-crra) * vp_func(m_nrm)
226def calc_end_dvda(shocks, a_nrm, share, rfree, dvdb_func):
227 """
228 Compute end-of-period marginal value of assets at values a, conditional
229 on risky asset return S and risky share z.
230 """
231 # Calculate future realizations of bank balances bNrm
232 ex_ret = shocks - rfree # Excess returns
233 rport = rfree + share * ex_ret # Portfolio return
234 b_nrm = rport * a_nrm
236 # Calculate and return dvda
237 return rport * dvdb_func(b_nrm)
240def calc_end_dvds(shocks, a_nrm, share, rfree, dvdb_func):
241 """
242 Compute end-of-period marginal value of risky share at values a,
243 conditional on risky asset return S and risky share z.
244 """
245 # Calculate future realizations of bank balances bNrm
246 ex_ret = shocks - rfree # Excess returns
247 rport = rfree + share * ex_ret # Portfolio return
248 b_nrm = rport * a_nrm
250 # Calculate and return dvds (second term is all zeros)
251 return ex_ret * a_nrm * dvdb_func(b_nrm)
254def calc_end_dvdx(shocks, a_nrm, share, rfree, dvdb_func):
255 ex_ret = shocks - rfree # Excess returns
256 rport = rfree + share * ex_ret # Portfolio return
257 b_nrm = rport * a_nrm
259 # Calculate and return dvds (second term is all zeros)
260 dvdb = dvdb_func(b_nrm)
261 dvda = rport * dvdb
262 dvds = ex_ret * a_nrm * dvdb
263 return dvda, dvds
266def calc_med_v(shocks, b_nrm, perm_gro_fac, crra, v_func):
267 """
268 Calculate "intermediate" value from next period's bank balances, the
269 income shocks S, and the risky asset share.
270 """
271 m_nrm = calc_m_nrm_next(shocks, b_nrm, perm_gro_fac)
272 v_next = v_func(m_nrm)
273 return (shocks["PermShk"] * perm_gro_fac) ** (1.0 - crra) * v_next
276def calc_end_v(shocks, a_nrm, share, rfree, v_func):
277 # Calculate future realizations of bank balances bNrm
278 ex_ret = shocks - rfree
279 rport = rfree + share * ex_ret
280 b_nrm = rport * a_nrm
281 return v_func(b_nrm)
284###############################################################################
287def solve_one_period_WealthPortfolio(
288 solution_next,
289 IncShkDstn,
290 RiskyDstn,
291 LivPrb,
292 DiscFac,
293 CRRA,
294 Rfree,
295 PermGroFac,
296 BoroCnstArt,
297 aXtraGrid,
298 ShareGrid,
299 ShareLimit,
300 vFuncBool,
301 WealthShare,
302 WealthShift,
303 ChiFunc,
304):
305 """
306 TODO: Fill in this missing docstring.
308 Parameters
309 ----------
310 solution_next : TYPE
311 DESCRIPTION.
312 IncShkDstn : TYPE
313 DESCRIPTION.
314 RiskyDstn : TYPE
315 DESCRIPTION.
316 LivPrb : TYPE
317 DESCRIPTION.
318 DiscFac : TYPE
319 DESCRIPTION.
320 CRRA : TYPE
321 DESCRIPTION.
322 Rfree : TYPE
323 DESCRIPTION.
324 PermGroFac : TYPE
325 DESCRIPTION.
326 BoroCnstArt : TYPE
327 DESCRIPTION.
328 aXtraGrid : TYPE
329 DESCRIPTION.
330 ShareGrid : TYPE
331 DESCRIPTION.
332 ShareLimit : TYPE
333 DESCRIPTION.
334 vFuncBool : TYPE
335 DESCRIPTION.
336 WealthShare : TYPE
337 DESCRIPTION.
338 WealthShift : TYPE
339 DESCRIPTION.
340 ChiFunc : TYPE
341 DESCRIPTION.
343 Returns
344 -------
345 solution_now : TYPE
346 DESCRIPTION.
348 """
349 # Make sure the individual is liquidity constrained. Allowing a consumer to
350 # borrow *and* invest in an asset with unbounded (negative) returns is a bad mix.
351 if BoroCnstArt != 0.0:
352 raise ValueError("PortfolioConsumerType must have BoroCnstArt=0.0!")
354 # Define the current period utility function and effective discount factor
355 uFunc = UtilityFuncCRRA(CRRA)
356 DiscFacEff = DiscFac * LivPrb # "effective" discount factor
358 # Unpack next period's solution for easier access
359 vp_func_next = solution_next.vPfuncAdj
360 v_func_next = solution_next.vFuncAdj
362 # Set a flag for whether the natural borrowing constraint is zero, which
363 # depends on whether the smallest transitory income shock is zero
364 BoroCnstNat_iszero = (np.min(IncShkDstn.atoms[1]) == 0.0) or (
365 WealthShare != 0.0 and WealthShift == 0.0
366 )
368 # Prepare to calculate end-of-period marginal values by creating an array
369 # of market resources that the agent could have next period, considering
370 # the grid of end-of-period assets and the distribution of shocks he might
371 # experience next period.
373 # Unpack the risky return shock distribution
374 Risky_next = RiskyDstn.atoms
375 RiskyMax = np.max(Risky_next)
376 RiskyMin = np.min(Risky_next)
378 # bNrm represents R*a, balances after asset return shocks but before income.
379 # This just uses the highest risky return as a rough shifter for the aXtraGrid.
380 if BoroCnstNat_iszero:
381 aNrmGrid = aXtraGrid
382 bNrmGrid = np.insert(RiskyMax * aXtraGrid, 0, RiskyMin * aXtraGrid[0])
383 else:
384 # Add an asset point at exactly zero
385 aNrmGrid = np.insert(aXtraGrid, 0, 0.0)
386 bNrmGrid = RiskyMax * np.insert(aXtraGrid, 0, 0.0)
388 # Get grid and shock sizes, for easier indexing
389 aNrmCount = aNrmGrid.size
391 # Make tiled arrays to calculate future realizations of mNrm and Share when integrating over IncShkDstn
392 bNrmNext = bNrmGrid
394 # Calculate end-of-period marginal value of assets and shares at each point
395 # in aNrm and ShareGrid. Does so by taking expectation of next period marginal
396 # values across income and risky return shocks.
398 # Calculate intermediate marginal value of bank balances by taking expectations over income shocks
399 med_dvdb = expected(
400 calc_dvdm_next,
401 IncShkDstn,
402 args=(bNrmNext, PermGroFac, CRRA, vp_func_next),
403 )
404 med_dvdb_nvrs = uFunc.derinv(med_dvdb, order=(1, 0))
405 med_dvdb_nvrs_func = LinearInterp(bNrmGrid, med_dvdb_nvrs)
406 med_dvdb_func = MargValueFuncCRRA(med_dvdb_nvrs_func, CRRA)
408 # Make tiled arrays to calculate future realizations of bNrm and Share when integrating over RiskyDstn
409 aNrmNow, ShareNext = np.meshgrid(aNrmGrid, ShareGrid, indexing="ij")
411 # Evaluate realizations of value and marginal value after asset returns are realized
412 end_dvda, end_dvds = DiscFacEff * expected(
413 calc_end_dvdx,
414 RiskyDstn,
415 args=(aNrmNow, ShareNext, Rfree, med_dvdb_func),
416 )
417 end_dvda_nvrs = uFunc.derinv(end_dvda)
419 # Now find the optimal (continuous) risky share on [0,1] by solving the first
420 # order condition end_dvds == 0.
421 focs = end_dvds # Relabel for convenient typing
423 # For each value of aNrm, find the value of Share such that focs == 0
424 crossing = np.logical_and(focs[:, 1:] <= 0.0, focs[:, :-1] >= 0.0)
425 share_idx = np.argmax(crossing, axis=1)
426 # This represents the index of the segment of the share grid where dvds flips
427 # from positive to negative, indicating that there's a zero *on* the segment
429 # Calculate the fractional distance between those share gridpoints where the
430 # zero should be found, assuming a linear function; call it alpha
431 a_idx = np.arange(aNrmCount)
432 bot_s = ShareGrid[share_idx]
433 top_s = ShareGrid[share_idx + 1]
434 bot_f = focs[a_idx, share_idx]
435 top_f = focs[a_idx, share_idx + 1]
436 bot_c = end_dvda_nvrs[a_idx, share_idx]
437 top_c = end_dvda_nvrs[a_idx, share_idx + 1]
438 bot_dvda = end_dvda[a_idx, share_idx]
439 top_dvda = end_dvda[a_idx, share_idx + 1]
440 alpha = 1.0 - top_f / (top_f - bot_f)
442 # Calculate the continuous optimal risky share and optimal consumption
443 Share_now = (1.0 - alpha) * bot_s + alpha * top_s
444 end_dvda_nvrs_now = (1.0 - alpha) * bot_c + alpha * top_c
445 end_dvda_now = (1.0 - alpha) * bot_dvda + alpha * top_dvda
447 # If agent wants to put more than 100% into risky asset, he is constrained.
448 # Likewise if he wants to put less than 0% into risky asset, he is constrained.
449 constrained_top = focs[:, -1] > 0.0
450 constrained_bot = focs[:, 0] < 0.0
452 # Apply those constraints to both risky share and consumption (but lower
453 # constraint should never be relevant)
454 Share_now[constrained_top] = 1.0
455 Share_now[constrained_bot] = 0.0
456 end_dvda_nvrs_now[constrained_top] = end_dvda_nvrs[constrained_top, -1]
457 end_dvda_nvrs_now[constrained_bot] = end_dvda_nvrs[constrained_bot, 0]
458 end_dvda_now[constrained_top] = end_dvda[constrained_top, -1]
459 end_dvda_now[constrained_bot] = end_dvda[constrained_bot, 0]
461 # When the natural borrowing constraint is *not* zero, then aNrm=0 is in the
462 # grid, but there's no way to "optimize" the portfolio if a=0, and consumption
463 # can't depend on the risky share if it doesn't meaningfully exist. Apply
464 # a small fix to the bottom gridpoint (aNrm=0) when this happens.
465 if not BoroCnstNat_iszero:
466 Share_now[0] = 1.0
467 end_dvda_nvrs_now[0] = end_dvda_nvrs[0, -1]
468 end_dvda_now[0] = end_dvda[0, -1]
470 # Now this is where we look for optimal C
471 # for each a in the agrid find corresponding c that satisfies the euler equation
473 if WealthShare == 0.0:
474 cNrm_now = end_dvda_nvrs_now
475 else:
476 omega = end_dvda_nvrs_now / (aNrmGrid + WealthShift)
477 cNrm_now = ChiFunc(omega) * (aNrmGrid + WealthShift)
479 # Calculate the endogenous mNrm gridpoints when the agent adjusts his portfolio,
480 # then construct the consumption function when the agent can adjust his share
481 mNrm_now = np.insert(aNrmGrid + cNrm_now, 0, 0.0)
482 cNrm_now = np.insert(cNrm_now, 0, 0.0)
483 cFuncNow = LinearInterp(mNrm_now, cNrm_now)
485 dudc_now = dudc(cNrm_now, mNrm_now - cNrm_now, CRRA, WealthShare, WealthShift)
486 dudc_nvrs_now = uFunc.derinv(dudc_now, order=(1, 0))
487 dudc_nvrs_func_now = LinearInterp(mNrm_now, dudc_nvrs_now)
489 # Construct the marginal value (of mNrm) function
490 vPfuncNow = MargValueFuncCRRA(dudc_nvrs_func_now, CRRA)
492 # If the share choice is continuous, just make an ordinary interpolating function
493 if BoroCnstNat_iszero:
494 Share_lower_bound = ShareLimit
495 else:
496 Share_lower_bound = 1.0
497 Share_now = np.insert(Share_now, 0, Share_lower_bound)
498 ShareFuncNow = LinearInterp(mNrm_now, Share_now, ShareLimit, 0.0)
500 # Add the value function if requested
501 if vFuncBool:
502 # Calculate intermediate value by taking expectations over income shocks
503 med_v = expected(
504 calc_med_v, IncShkDstn, args=(bNrmNext, PermGroFac, CRRA, v_func_next)
505 )
507 # Construct the "intermediate value function" for this period
508 med_v_nvrs = uFunc.inv(med_v)
509 med_v_nvrs_func = LinearInterp(bNrmGrid, med_v_nvrs)
510 med_v_func = ValueFuncCRRA(med_v_nvrs_func, CRRA)
512 # Calculate end-of-period value by taking expectations
513 end_v = DiscFacEff * expected(
514 calc_end_v,
515 RiskyDstn,
516 args=(aNrmNow, ShareNext, Rfree, med_v_func),
517 )
518 end_v_nvrs = uFunc.inv(end_v)
520 # Now make an end-of-period value function over aNrm and Share
521 end_v_nvrs_func = BilinearInterp(end_v_nvrs, aNrmGrid, ShareGrid)
522 end_v_func = ValueFuncCRRA(end_v_nvrs_func, CRRA)
523 # This will be used later to make the value function for this period
525 # Create the value functions for this period, defined over market resources
526 # mNrm when agent can adjust his portfolio, and over market resources and
527 # fixed share when agent can not adjust his portfolio.
529 # Construct the value function
530 mNrm_temp = aXtraGrid # Just use aXtraGrid as our grid of mNrm values
531 cNrm_temp = cFuncNow(mNrm_temp)
532 aNrm_temp = np.maximum(mNrm_temp - cNrm_temp, 0.0) # Fix tiny violations
533 Share_temp = ShareFuncNow(mNrm_temp)
534 v_temp = uFunc(cNrm_temp) + end_v_func(aNrm_temp, Share_temp)
535 vNvrs_temp = uFunc.inv(v_temp)
536 vNvrsP_temp = uFunc.der(cNrm_temp) * uFunc.inverse(v_temp, order=(0, 1))
537 vNvrsFunc = CubicInterp(
538 np.insert(mNrm_temp, 0, 0.0), # x_list
539 np.insert(vNvrs_temp, 0, 0.0), # f_list
540 np.insert(vNvrsP_temp, 0, vNvrsP_temp[0]), # dfdx_list
541 )
542 # Re-curve the pseudo-inverse value function
543 vFuncNow = ValueFuncCRRA(vNvrsFunc, CRRA)
545 else: # If vFuncBool is False, fill in dummy values
546 vFuncNow = NullFunc()
548 # Package and return the solution
549 solution_now = PortfolioSolution(
550 cFuncAdj=cFuncNow,
551 ShareFuncAdj=ShareFuncNow,
552 vPfuncAdj=vPfuncNow,
553 vFuncAdj=vFuncNow,
554 )
555 return solution_now
558###############################################################################
560# Make a dictionary of constructors for the wealth-in-utility portfolio choice consumer type
561WealthPortfolioConsumerType_constructors_default = {
562 "IncShkDstn": construct_lognormal_income_process_unemployment,
563 "PermShkDstn": get_PermShkDstn_from_IncShkDstn,
564 "TranShkDstn": get_TranShkDstn_from_IncShkDstn,
565 "aXtraGrid": make_assets_grid,
566 "RiskyDstn": make_lognormal_RiskyDstn,
567 "ShockDstn": combine_IncShkDstn_and_RiskyDstn,
568 "ShareLimit": calc_ShareLimit_for_CRRA,
569 "ShareGrid": make_simple_ShareGrid,
570 "ChiFunc": make_ChiFromOmega_function,
571 "AdjustDstn": make_AdjustDstn,
572 "kNrmInitDstn": make_lognormal_kNrm_init_dstn,
573 "pLvlInitDstn": make_lognormal_pLvl_init_dstn,
574 "solution_terminal": make_portfolio_solution_terminal,
575}
577# Default parameters to make IncShkDstn using construct_lognormal_income_process_unemployment
578WealthPortfolioConsumerType_IncShkDstn_default = {
579 "PermShkStd": [0.1], # Standard deviation of log permanent income shocks
580 "PermShkCount": 7, # Number of points in discrete approximation to permanent income shocks
581 "TranShkStd": [0.1], # Standard deviation of log transitory income shocks
582 "TranShkCount": 7, # Number of points in discrete approximation to transitory income shocks
583 "UnempPrb": 0.05, # Probability of unemployment while working
584 "IncUnemp": 0.3, # Unemployment benefits replacement rate while working
585 "T_retire": 0, # Period of retirement (0 --> no retirement)
586 "UnempPrbRet": 0.005, # Probability of "unemployment" while retired
587 "IncUnempRet": 0.0, # "Unemployment" benefits when retired
588}
590# Default parameters to make aXtraGrid using make_assets_grid
591WealthPortfolioConsumerType_aXtraGrid_default = {
592 "aXtraMin": 0.001, # Minimum end-of-period "assets above minimum" value
593 "aXtraMax": 100, # Maximum end-of-period "assets above minimum" value
594 "aXtraNestFac": 1, # Exponential nesting factor for aXtraGrid
595 "aXtraCount": 200, # Number of points in the grid of "assets above minimum"
596 "aXtraExtra": None, # Additional other values to add in grid (optional)
597}
599# Default parameters to make RiskyDstn with make_lognormal_RiskyDstn (and uniform ShareGrid)
600WealthPortfolioConsumerType_RiskyDstn_default = {
601 "RiskyAvg": 1.08, # Mean return factor of risky asset
602 "RiskyStd": 0.18362634887, # Stdev of log returns on risky asset
603 "RiskyCount": 5, # Number of integration nodes to use in approximation of risky returns
604}
606WealthPortfolioConsumerType_ShareGrid_default = {
607 "ShareCount": 25 # Number of discrete points in the risky share approximation
608}
610# Default parameters to make ChiFunc with make_ChiFromOmega_function
611WealthPortfolioConsumerType_ChiFunc_default = {
612 "ChiFromOmega_N": 501, # Number of gridpoints in chi-from-omega function
613 "ChiFromOmega_bound": 15, # Highest gridpoint to use for it
614}
616# Make a dictionary with parameters for the default constructor for kNrmInitDstn
617WealthPortfolioConsumerType_kNrmInitDstn_default = {
618 "kLogInitMean": -12.0, # Mean of log initial capital
619 "kLogInitStd": 0.0, # Stdev of log initial capital
620 "kNrmInitCount": 15, # Number of points in initial capital discretization
621}
623# Make a dictionary with parameters for the default constructor for pLvlInitDstn
624WealthPortfolioConsumerType_pLvlInitDstn_default = {
625 "pLogInitMean": 0.0, # Mean of log permanent income
626 "pLogInitStd": 0.0, # Stdev of log permanent income
627 "pLvlInitCount": 15, # Number of points in initial capital discretization
628}
630# Make a dictionary to specify a risky asset consumer type
631WealthPortfolioConsumerType_solving_default = {
632 # BASIC HARK PARAMETERS REQUIRED TO SOLVE THE MODEL
633 "cycles": 1, # Finite, non-cyclic model
634 "T_cycle": 1, # Number of periods in the cycle for this agent type
635 "constructors": WealthPortfolioConsumerType_constructors_default, # See dictionary above
636 # PRIMITIVE RAW PARAMETERS REQUIRED TO SOLVE THE MODEL
637 "CRRA": 5.0, # Coefficient of relative risk aversion
638 "Rfree": [1.03], # Return factor on risk free asset
639 "DiscFac": 0.90, # Intertemporal discount factor
640 "LivPrb": [0.98], # Survival probability after each period
641 "PermGroFac": [1.01], # Permanent income growth factor
642 "BoroCnstArt": 0.0, # Artificial borrowing constraint
643 "WealthShare": 0.5, # Share of wealth in Cobb-Douglas aggregator in utility function
644 "WealthShift": 0.1, # Shifter for wealth in utility function
645 "DiscreteShareBool": False, # Whether risky asset share is restricted to discrete values
646 "PortfolioBool": True, # Whether there is portfolio choice
647 "PortfolioBisect": False, # This is a mystery parameter
648 "IndepDstnBool": True, # Whether income and return shocks are independent
649 "vFuncBool": False, # Whether to calculate the value function during solution
650 "CubicBool": False, # Whether to use cubic spline interpolation when True
651 # (Uses linear spline interpolation for cFunc when False)
652 "AdjustPrb": 1.0, # Probability that the agent can update their risky portfolio share each period
653 "RiskyShareFixed": None, # This just needs to exist because of inheritance, does nothing
654 "sim_common_Rrisky": True, # Whether risky returns have a shared/common value across agents
655}
656WealthPortfolioConsumerType_simulation_default = {
657 # PARAMETERS REQUIRED TO SIMULATE THE MODEL
658 "AgentCount": 10000, # Number of agents of this type
659 "T_age": None, # Age after which simulated agents are automatically killed
660 "aNrmInitMean": 0.0, # Mean of log initial assets
661 "aNrmInitStd": 1.0, # Standard deviation of log initial assets
662 "pLvlInitMean": 0.0, # Mean of log initial permanent income
663 "pLvlInitStd": 0.0, # Standard deviation of log initial permanent income
664 "PermGroFacAgg": 1.0, # Aggregate permanent income growth factor
665 # (The portion of PermGroFac attributable to aggregate productivity growth)
666 "NewbornTransShk": False, # Whether Newborns have transitory shock
667 # ADDITIONAL OPTIONAL PARAMETERS
668 "PerfMITShk": False, # Do Perfect Foresight MIT Shock
669 # (Forces Newborns to follow solution path of the agent they replaced if True)
670 "neutral_measure": False, # Whether to use permanent income neutral measure (see Harmenberg 2021)
671}
673# Assemble the default dictionary
674WealthPortfolioConsumerType_default = {}
675WealthPortfolioConsumerType_default.update(WealthPortfolioConsumerType_solving_default)
676WealthPortfolioConsumerType_default.update(
677 WealthPortfolioConsumerType_simulation_default
678)
679WealthPortfolioConsumerType_default.update(
680 WealthPortfolioConsumerType_aXtraGrid_default
681)
682WealthPortfolioConsumerType_default.update(
683 WealthPortfolioConsumerType_ShareGrid_default
684)
685WealthPortfolioConsumerType_default.update(
686 WealthPortfolioConsumerType_IncShkDstn_default
687)
688WealthPortfolioConsumerType_default.update(
689 WealthPortfolioConsumerType_RiskyDstn_default
690)
691WealthPortfolioConsumerType_default.update(WealthPortfolioConsumerType_ChiFunc_default)
692WealthPortfolioConsumerType_default.update(
693 WealthPortfolioConsumerType_kNrmInitDstn_default
694)
695WealthPortfolioConsumerType_default.update(
696 WealthPortfolioConsumerType_pLvlInitDstn_default
697)
698init_wealth_portfolio = WealthPortfolioConsumerType_default
700###############################################################################
703class WealthPortfolioConsumerType(PortfolioConsumerType):
704 """
705 TODO: This docstring is missing and needs to be written.
706 """
708 time_inv_ = deepcopy(PortfolioConsumerType.time_inv_)
709 time_inv_ = time_inv_ + [
710 "WealthShare",
711 "WealthShift",
712 "ChiFunc",
713 "RiskyDstn",
714 ]
715 default_ = {
716 "params": init_wealth_portfolio,
717 "solver": solve_one_period_WealthPortfolio,
718 "model": "ConsRiskyAsset.yaml",
719 }
721 def pre_solve(self):
722 self.construct("solution_terminal")
723 self.solution_terminal.ShareFunc = ConstantFunction(1.0)