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

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

38 

39 

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

46 

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

60 

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

67 

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) 

75 

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) 

85 

86 omega_vec = self.f(chi_vec) 

87 log_omega_vec = np.log(omega_vec) 

88 

89 # Construct the interpolant 

90 zFromLogOmegaFunc = LinearInterp(log_omega_vec, z_vec, lower_extrap=True) 

91 

92 # Store the function and limit as attributes 

93 self.func = zFromLogOmegaFunc 

94 self.limit = chi_limit 

95 

96 def __call__(self, omega): 

97 """ 

98 Calculate optimal values of chi = cNrm / aNrm from values of omega. 

99 

100 Parameters 

101 ---------- 

102 omega : np.array 

103 One or more values of omega = EndOfPrdvP / aNrm. 

104 

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) 

114 

115 

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 ) 

123 

124 

125############################################################################### 

126 

127 

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) 

131 

132 

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 

136 

137 

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) 

141 

142 

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 

146 

147 

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) 

152 

153 

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 

157 

158 if share == 0: 

159 return dudc 

160 else: 

161 duda = ufac * share / (a + intercept) 

162 

163 return dudc - duda 

164 

165 

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 

169 

170 dudcdc = ufac * (share - 1) * ((1 - CRRA) * (share - 1) + 1) / c**2 

171 dudadc = ufac * share * (share - 1) * (CRRA - 1) / (c * w) 

172 

173 return dudcdc - dudadc 

174 

175 

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) 

179 

180 

181def chi_ratio(c, a, intercept): 

182 return c / (a + intercept) 

183 

184 

185def chi_func(chi, CRRA, share): 

186 return chi ** (1 - share) * ( 

187 (1 - share) * chi ** (-share) - share * chi ** (1 - share) 

188 ) ** (-1 / CRRA) 

189 

190 

191def euler(c, a, CRRA, share, intercept, vp_a): 

192 dufac = du_diff(c, a, CRRA, share, intercept) 

193 return dufac - vp_a 

194 

195 

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 

198 

199 

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 ) 

206 

207 

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

214 

215 

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) 

224 

225 

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 

235 

236 # Calculate and return dvda 

237 return rport * dvdb_func(b_nrm) 

238 

239 

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 

249 

250 # Calculate and return dvds (second term is all zeros) 

251 return ex_ret * a_nrm * dvdb_func(b_nrm) 

252 

253 

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 

258 

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 

264 

265 

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 

274 

275 

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) 

282 

283 

284############################################################################### 

285 

286 

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. 

307 

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. 

342 

343 Returns 

344 ------- 

345 solution_now : TYPE 

346 DESCRIPTION. 

347 

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

353 

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

355 uFunc = UtilityFuncCRRA(CRRA) 

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

357 

358 # Unpack next period's solution for easier access 

359 vp_func_next = solution_next.vPfuncAdj 

360 v_func_next = solution_next.vFuncAdj 

361 

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 ) 

367 

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. 

372 

373 # Unpack the risky return shock distribution 

374 Risky_next = RiskyDstn.atoms 

375 RiskyMax = np.max(Risky_next) 

376 RiskyMin = np.min(Risky_next) 

377 

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) 

387 

388 # Get grid and shock sizes, for easier indexing 

389 aNrmCount = aNrmGrid.size 

390 

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

392 bNrmNext = bNrmGrid 

393 

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. 

397 

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) 

407 

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

410 

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) 

418 

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 

422 

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 

428 

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) 

441 

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 

446 

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 

451 

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] 

460 

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] 

469 

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 

472 

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) 

478 

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) 

484 

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) 

488 

489 # Construct the marginal value (of mNrm) function 

490 vPfuncNow = MargValueFuncCRRA(dudc_nvrs_func_now, CRRA) 

491 

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) 

499 

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 ) 

506 

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) 

511 

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) 

519 

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 

524 

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. 

528 

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) 

544 

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

546 vFuncNow = NullFunc() 

547 

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 

556 

557 

558############################################################################### 

559 

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} 

576 

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} 

589 

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} 

598 

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} 

605 

606WealthPortfolioConsumerType_ShareGrid_default = { 

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

608} 

609 

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} 

615 

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} 

622 

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} 

629 

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} 

672 

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 

699 

700############################################################################### 

701 

702 

703class WealthPortfolioConsumerType(PortfolioConsumerType): 

704 """ 

705 TODO: This docstring is missing and needs to be written. 

706 """ 

707 

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 } 

720 

721 def pre_solve(self): 

722 self.construct("solution_terminal") 

723 self.solution_terminal.ShareFunc = ConstantFunction(1.0)