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

1from copy import deepcopy 

2 

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 

41 

42 

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) 

46 

47 

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 

51 

52 

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"] 

59 

60 

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) 

69 

70 

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 

75 

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 

81 

82 

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 

91 

92 

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) 

99 

100 

101############################################################################### 

102 

103 

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. 

124 

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. 

170 

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!") 

181 

182 # Define the current period utility function and effective discount factor 

183 uFunc = UtilityFuncCRRA(CRRA) 

184 DiscFacEff = DiscFac * LivPrb # "effective" discount factor 

185 

186 # Unpack next period's solution for easier access 

187 vp_func_next = solution_next.vPfuncAdj 

188 v_func_next = solution_next.vFuncAdj 

189 

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 ) 

195 

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. 

200 

201 # Unpack the risky return shock distribution 

202 Risky_next = RiskyDstn.atoms 

203 RiskyMax = np.max(Risky_next) 

204 RiskyMin = np.min(Risky_next) 

205 

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) 

215 

216 # Get grid and shock sizes, for easier indexing 

217 aNrmCount = aNrmGrid.size 

218 

219 # Make tiled arrays to calculate future realizations of mNrm and Share when integrating over IncShkDstn 

220 bNrmNext = bNrmGrid 

221 

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. 

225 

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) 

235 

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") 

238 

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) 

246 

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 

250 

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 

256 

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) 

269 

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 

274 

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 

279 

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] 

288 

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] 

297 

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 

300 

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) 

306 

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) 

312 

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) 

316 

317 # Construct the marginal value (of mNrm) function 

318 vPfuncNow = MargValueFuncCRRA(dudc_nvrs_func_now, CRRA) 

319 

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) 

327 

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 ) 

334 

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) 

339 

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) 

347 

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 

352 

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. 

356 

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) 

372 

373 else: # If vFuncBool is False, fill in dummy values 

374 vFuncNow = NullFunc() 

375 

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 

384 

385 

386############################################################################### 

387 

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} 

404 

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} 

417 

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} 

426 

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} 

433 

434WealthPortfolioConsumerType_ShareGrid_default = { 

435 "ShareCount": 25 # Number of discrete points in the risky share approximation 

436} 

437 

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} 

443 

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} 

450 

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} 

457 

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} 

495 

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 

522 

523############################################################################### 

524 

525 

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. 

533 

534 The model is thus a combination of RiskyAssetConsumerType (or PortfolioConsumerType) 

535 with WealthUtilityConsumerType. 

536 

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}} 

547 

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 """ 

561 

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 } 

575 

576 def pre_solve(self): 

577 self.construct("solution_terminal") 

578 self.solution_terminal.ShareFunc = ConstantFunction(1.0)