Coverage for HARK / ConsumptionSaving / ConsWealthPortfolioModel.py: 96%

188 statements  

« prev     ^ index     » next       coverage.py v7.12.0, created at 2025-12-07 05:16 +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 calc_m_nrm_next(shocks, b_nrm, perm_gro_fac): 

139 """ 

140 Calculate future realizations of market resources mNrm from the income 

141 shock distribution S and normalized bank balances b. 

142 """ 

143 return b_nrm / (shocks["PermShk"] * perm_gro_fac) + shocks["TranShk"] 

144 

145 

146def calc_dvdm_next(shocks, b_nrm, perm_gro_fac, crra, vp_func): 

147 """ 

148 Evaluate realizations of marginal value of market resources next period, 

149 based on the income distribution S and values of bank balances bNrm 

150 """ 

151 m_nrm = calc_m_nrm_next(shocks, b_nrm, perm_gro_fac) 

152 perm_shk_fac = shocks["PermShk"] * perm_gro_fac 

153 return perm_shk_fac ** (-crra) * vp_func(m_nrm) 

154 

155 

156def calc_end_dvdx(shocks, a_nrm, share, rfree, dvdb_func): 

157 ex_ret = shocks - rfree # Excess returns 

158 rport = rfree + share * ex_ret # Portfolio return 

159 b_nrm = rport * a_nrm 

160 

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

162 dvdb = dvdb_func(b_nrm) 

163 dvda = rport * dvdb 

164 dvds = ex_ret * a_nrm * dvdb 

165 return dvda, dvds 

166 

167 

168def calc_med_v(shocks, b_nrm, perm_gro_fac, crra, v_func): 

169 """ 

170 Calculate "intermediate" value from next period's bank balances, the 

171 income shocks S, and the risky asset share. 

172 """ 

173 m_nrm = calc_m_nrm_next(shocks, b_nrm, perm_gro_fac) 

174 v_next = v_func(m_nrm) 

175 return (shocks["PermShk"] * perm_gro_fac) ** (1.0 - crra) * v_next 

176 

177 

178def calc_end_v(shocks, a_nrm, share, rfree, v_func): 

179 # Calculate future realizations of bank balances bNrm 

180 ex_ret = shocks - rfree 

181 rport = rfree + share * ex_ret 

182 b_nrm = rport * a_nrm 

183 return v_func(b_nrm) 

184 

185 

186############################################################################### 

187 

188 

189def solve_one_period_WealthPortfolio( 

190 solution_next, 

191 IncShkDstn, 

192 RiskyDstn, 

193 LivPrb, 

194 DiscFac, 

195 CRRA, 

196 Rfree, 

197 PermGroFac, 

198 BoroCnstArt, 

199 aXtraGrid, 

200 ShareGrid, 

201 ShareLimit, 

202 vFuncBool, 

203 WealthShare, 

204 WealthShift, 

205 ChiFunc, 

206): 

207 """ 

208 TODO: Fill in this missing docstring. 

209 

210 Parameters 

211 ---------- 

212 solution_next : TYPE 

213 DESCRIPTION. 

214 IncShkDstn : TYPE 

215 DESCRIPTION. 

216 RiskyDstn : TYPE 

217 DESCRIPTION. 

218 LivPrb : TYPE 

219 DESCRIPTION. 

220 DiscFac : TYPE 

221 DESCRIPTION. 

222 CRRA : TYPE 

223 DESCRIPTION. 

224 Rfree : TYPE 

225 DESCRIPTION. 

226 PermGroFac : TYPE 

227 DESCRIPTION. 

228 BoroCnstArt : TYPE 

229 DESCRIPTION. 

230 aXtraGrid : TYPE 

231 DESCRIPTION. 

232 ShareGrid : TYPE 

233 DESCRIPTION. 

234 ShareLimit : TYPE 

235 DESCRIPTION. 

236 vFuncBool : TYPE 

237 DESCRIPTION. 

238 WealthShare : TYPE 

239 DESCRIPTION. 

240 WealthShift : TYPE 

241 DESCRIPTION. 

242 ChiFunc : TYPE 

243 DESCRIPTION. 

244 

245 Returns 

246 ------- 

247 solution_now : TYPE 

248 DESCRIPTION. 

249 

250 """ 

251 # Make sure the individual is liquidity constrained. Allowing a consumer to 

252 # borrow *and* invest in an asset with unbounded (negative) returns is a bad mix. 

253 if BoroCnstArt != 0.0: 

254 raise ValueError("PortfolioConsumerType must have BoroCnstArt=0.0!") 

255 

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

257 uFunc = UtilityFuncCRRA(CRRA) 

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

259 

260 # Unpack next period's solution for easier access 

261 vp_func_next = solution_next.vPfuncAdj 

262 v_func_next = solution_next.vFuncAdj 

263 

264 # Set a flag for whether the natural borrowing constraint is zero, which 

265 # depends on whether the smallest transitory income shock is zero 

266 BoroCnstNat_iszero = (np.min(IncShkDstn.atoms[1]) == 0.0) or ( 

267 WealthShare != 0.0 and WealthShift == 0.0 

268 ) 

269 

270 # Prepare to calculate end-of-period marginal values by creating an array 

271 # of market resources that the agent could have next period, considering 

272 # the grid of end-of-period assets and the distribution of shocks he might 

273 # experience next period. 

274 

275 # Unpack the risky return shock distribution 

276 Risky_next = RiskyDstn.atoms 

277 RiskyMax = np.max(Risky_next) 

278 RiskyMin = np.min(Risky_next) 

279 

280 # bNrm represents R*a, balances after asset return shocks but before income. 

281 # This just uses the highest risky return as a rough shifter for the aXtraGrid. 

282 if BoroCnstNat_iszero: 

283 aNrmGrid = aXtraGrid 

284 bNrmGrid = np.insert(RiskyMax * aXtraGrid, 0, RiskyMin * aXtraGrid[0]) 

285 else: 

286 # Add an asset point at exactly zero 

287 aNrmGrid = np.insert(aXtraGrid, 0, 0.0) 

288 bNrmGrid = RiskyMax * np.insert(aXtraGrid, 0, 0.0) 

289 

290 # Get grid and shock sizes, for easier indexing 

291 aNrmCount = aNrmGrid.size 

292 

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

294 bNrmNext = bNrmGrid 

295 

296 # Calculate end-of-period marginal value of assets and shares at each point 

297 # in aNrm and ShareGrid. Does so by taking expectation of next period marginal 

298 # values across income and risky return shocks. 

299 

300 # Calculate intermediate marginal value of bank balances by taking expectations over income shocks 

301 med_dvdb = expected( 

302 calc_dvdm_next, 

303 IncShkDstn, 

304 args=(bNrmNext, PermGroFac, CRRA, vp_func_next), 

305 ) 

306 med_dvdb_nvrs = uFunc.derinv(med_dvdb, order=(1, 0)) 

307 med_dvdb_nvrs_func = LinearInterp(bNrmGrid, med_dvdb_nvrs) 

308 med_dvdb_func = MargValueFuncCRRA(med_dvdb_nvrs_func, CRRA) 

309 

310 # Make tiled arrays to calculate future realizations of bNrm and Share when integrating over RiskyDstn 

311 aNrmNow, ShareNext = np.meshgrid(aNrmGrid, ShareGrid, indexing="ij") 

312 

313 # Evaluate realizations of value and marginal value after asset returns are realized 

314 end_dvda, end_dvds = DiscFacEff * expected( 

315 calc_end_dvdx, 

316 RiskyDstn, 

317 args=(aNrmNow, ShareNext, Rfree, med_dvdb_func), 

318 ) 

319 end_dvda_nvrs = uFunc.derinv(end_dvda) 

320 

321 # Now find the optimal (continuous) risky share on [0,1] by solving the first 

322 # order condition end_dvds == 0. 

323 focs = end_dvds # Relabel for convenient typing 

324 

325 # For each value of aNrm, find the value of Share such that focs == 0 

326 crossing = np.logical_and(focs[:, 1:] <= 0.0, focs[:, :-1] >= 0.0) 

327 share_idx = np.argmax(crossing, axis=1) 

328 # This represents the index of the segment of the share grid where dvds flips 

329 # from positive to negative, indicating that there's a zero *on* the segment 

330 

331 # Calculate the fractional distance between those share gridpoints where the 

332 # zero should be found, assuming a linear function; call it alpha 

333 a_idx = np.arange(aNrmCount) 

334 bot_s = ShareGrid[share_idx] 

335 top_s = ShareGrid[share_idx + 1] 

336 bot_f = focs[a_idx, share_idx] 

337 top_f = focs[a_idx, share_idx + 1] 

338 bot_c = end_dvda_nvrs[a_idx, share_idx] 

339 top_c = end_dvda_nvrs[a_idx, share_idx + 1] 

340 bot_dvda = end_dvda[a_idx, share_idx] 

341 top_dvda = end_dvda[a_idx, share_idx + 1] 

342 alpha = 1.0 - top_f / (top_f - bot_f) 

343 

344 # Calculate the continuous optimal risky share and optimal consumption 

345 Share_now = (1.0 - alpha) * bot_s + alpha * top_s 

346 end_dvda_nvrs_now = (1.0 - alpha) * bot_c + alpha * top_c 

347 end_dvda_now = (1.0 - alpha) * bot_dvda + alpha * top_dvda 

348 

349 # If agent wants to put more than 100% into risky asset, he is constrained. 

350 # Likewise if he wants to put less than 0% into risky asset, he is constrained. 

351 constrained_top = focs[:, -1] > 0.0 

352 constrained_bot = focs[:, 0] < 0.0 

353 

354 # Apply those constraints to both risky share and consumption (but lower 

355 # constraint should never be relevant) 

356 Share_now[constrained_top] = 1.0 

357 Share_now[constrained_bot] = 0.0 

358 end_dvda_nvrs_now[constrained_top] = end_dvda_nvrs[constrained_top, -1] 

359 end_dvda_nvrs_now[constrained_bot] = end_dvda_nvrs[constrained_bot, 0] 

360 end_dvda_now[constrained_top] = end_dvda[constrained_top, -1] 

361 end_dvda_now[constrained_bot] = end_dvda[constrained_bot, 0] 

362 

363 # When the natural borrowing constraint is *not* zero, then aNrm=0 is in the 

364 # grid, but there's no way to "optimize" the portfolio if a=0, and consumption 

365 # can't depend on the risky share if it doesn't meaningfully exist. Apply 

366 # a small fix to the bottom gridpoint (aNrm=0) when this happens. 

367 if not BoroCnstNat_iszero: 

368 Share_now[0] = 1.0 

369 end_dvda_nvrs_now[0] = end_dvda_nvrs[0, -1] 

370 end_dvda_now[0] = end_dvda[0, -1] 

371 

372 # Now this is where we look for optimal C 

373 # for each a in the agrid find corresponding c that satisfies the euler equation 

374 

375 if WealthShare == 0.0: 

376 cNrm_now = end_dvda_nvrs_now 

377 else: 

378 omega = end_dvda_nvrs_now / (aNrmGrid + WealthShift) 

379 cNrm_now = ChiFunc(omega) * (aNrmGrid + WealthShift) 

380 

381 # Calculate the endogenous mNrm gridpoints when the agent adjusts his portfolio, 

382 # then construct the consumption function when the agent can adjust his share 

383 mNrm_now = np.insert(aNrmGrid + cNrm_now, 0, 0.0) 

384 cNrm_now = np.insert(cNrm_now, 0, 0.0) 

385 cFuncNow = LinearInterp(mNrm_now, cNrm_now) 

386 

387 dudc_now = dudc(cNrm_now, mNrm_now - cNrm_now, CRRA, WealthShare, WealthShift) 

388 dudc_nvrs_now = uFunc.derinv(dudc_now, order=(1, 0)) 

389 dudc_nvrs_func_now = LinearInterp(mNrm_now, dudc_nvrs_now) 

390 

391 # Construct the marginal value (of mNrm) function 

392 vPfuncNow = MargValueFuncCRRA(dudc_nvrs_func_now, CRRA) 

393 

394 # If the share choice is continuous, just make an ordinary interpolating function 

395 if BoroCnstNat_iszero: 

396 Share_lower_bound = ShareLimit 

397 else: 

398 Share_lower_bound = 1.0 

399 Share_now = np.insert(Share_now, 0, Share_lower_bound) 

400 ShareFuncNow = LinearInterp(mNrm_now, Share_now, ShareLimit, 0.0) 

401 

402 # Add the value function if requested 

403 if vFuncBool: 

404 # Calculate intermediate value by taking expectations over income shocks 

405 med_v = expected( 

406 calc_med_v, IncShkDstn, args=(bNrmNext, PermGroFac, CRRA, v_func_next) 

407 ) 

408 

409 # Construct the "intermediate value function" for this period 

410 med_v_nvrs = uFunc.inv(med_v) 

411 med_v_nvrs_func = LinearInterp(bNrmGrid, med_v_nvrs) 

412 med_v_func = ValueFuncCRRA(med_v_nvrs_func, CRRA) 

413 

414 # Calculate end-of-period value by taking expectations 

415 end_v = DiscFacEff * expected( 

416 calc_end_v, 

417 RiskyDstn, 

418 args=(aNrmNow, ShareNext, Rfree, med_v_func), 

419 ) 

420 end_v_nvrs = uFunc.inv(end_v) 

421 

422 # Now make an end-of-period value function over aNrm and Share 

423 end_v_nvrs_func = BilinearInterp(end_v_nvrs, aNrmGrid, ShareGrid) 

424 end_v_func = ValueFuncCRRA(end_v_nvrs_func, CRRA) 

425 # This will be used later to make the value function for this period 

426 

427 # Create the value functions for this period, defined over market resources 

428 # mNrm when agent can adjust his portfolio, and over market resources and 

429 # fixed share when agent can not adjust his portfolio. 

430 

431 # Construct the value function 

432 mNrm_temp = aXtraGrid # Just use aXtraGrid as our grid of mNrm values 

433 cNrm_temp = cFuncNow(mNrm_temp) 

434 aNrm_temp = np.maximum(mNrm_temp - cNrm_temp, 0.0) # Fix tiny violations 

435 Share_temp = ShareFuncNow(mNrm_temp) 

436 v_temp = uFunc(cNrm_temp) + end_v_func(aNrm_temp, Share_temp) 

437 vNvrs_temp = uFunc.inv(v_temp) 

438 vNvrsP_temp = uFunc.der(cNrm_temp) * uFunc.inverse(v_temp, order=(0, 1)) 

439 vNvrsFunc = CubicInterp( 

440 np.insert(mNrm_temp, 0, 0.0), # x_list 

441 np.insert(vNvrs_temp, 0, 0.0), # f_list 

442 np.insert(vNvrsP_temp, 0, vNvrsP_temp[0]), # dfdx_list 

443 ) 

444 # Re-curve the pseudo-inverse value function 

445 vFuncNow = ValueFuncCRRA(vNvrsFunc, CRRA) 

446 

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

448 vFuncNow = NullFunc() 

449 

450 # Package and return the solution 

451 solution_now = PortfolioSolution( 

452 cFuncAdj=cFuncNow, 

453 ShareFuncAdj=ShareFuncNow, 

454 vPfuncAdj=vPfuncNow, 

455 vFuncAdj=vFuncNow, 

456 ) 

457 return solution_now 

458 

459 

460############################################################################### 

461 

462# Make a dictionary of constructors for the wealth-in-utility portfolio choice consumer type 

463WealthPortfolioConsumerType_constructors_default = { 

464 "IncShkDstn": construct_lognormal_income_process_unemployment, 

465 "PermShkDstn": get_PermShkDstn_from_IncShkDstn, 

466 "TranShkDstn": get_TranShkDstn_from_IncShkDstn, 

467 "aXtraGrid": make_assets_grid, 

468 "RiskyDstn": make_lognormal_RiskyDstn, 

469 "ShockDstn": combine_IncShkDstn_and_RiskyDstn, 

470 "ShareLimit": calc_ShareLimit_for_CRRA, 

471 "ShareGrid": make_simple_ShareGrid, 

472 "ChiFunc": make_ChiFromOmega_function, 

473 "AdjustDstn": make_AdjustDstn, 

474 "kNrmInitDstn": make_lognormal_kNrm_init_dstn, 

475 "pLvlInitDstn": make_lognormal_pLvl_init_dstn, 

476 "solution_terminal": make_portfolio_solution_terminal, 

477} 

478 

479# Default parameters to make IncShkDstn using construct_lognormal_income_process_unemployment 

480WealthPortfolioConsumerType_IncShkDstn_default = { 

481 "PermShkStd": [0.1], # Standard deviation of log permanent income shocks 

482 "PermShkCount": 7, # Number of points in discrete approximation to permanent income shocks 

483 "TranShkStd": [0.1], # Standard deviation of log transitory income shocks 

484 "TranShkCount": 7, # Number of points in discrete approximation to transitory income shocks 

485 "UnempPrb": 0.05, # Probability of unemployment while working 

486 "IncUnemp": 0.3, # Unemployment benefits replacement rate while working 

487 "T_retire": 0, # Period of retirement (0 --> no retirement) 

488 "UnempPrbRet": 0.005, # Probability of "unemployment" while retired 

489 "IncUnempRet": 0.0, # "Unemployment" benefits when retired 

490} 

491 

492# Default parameters to make aXtraGrid using make_assets_grid 

493WealthPortfolioConsumerType_aXtraGrid_default = { 

494 "aXtraMin": 0.001, # Minimum end-of-period "assets above minimum" value 

495 "aXtraMax": 100, # Maximum end-of-period "assets above minimum" value 

496 "aXtraNestFac": 1, # Exponential nesting factor for aXtraGrid 

497 "aXtraCount": 200, # Number of points in the grid of "assets above minimum" 

498 "aXtraExtra": None, # Additional other values to add in grid (optional) 

499} 

500 

501# Default parameters to make RiskyDstn with make_lognormal_RiskyDstn (and uniform ShareGrid) 

502WealthPortfolioConsumerType_RiskyDstn_default = { 

503 "RiskyAvg": 1.08, # Mean return factor of risky asset 

504 "RiskyStd": 0.18362634887, # Stdev of log returns on risky asset 

505 "RiskyCount": 5, # Number of integration nodes to use in approximation of risky returns 

506} 

507 

508WealthPortfolioConsumerType_ShareGrid_default = { 

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

510} 

511 

512# Default parameters to make ChiFunc with make_ChiFromOmega_function 

513WealthPortfolioConsumerType_ChiFunc_default = { 

514 "ChiFromOmega_N": 501, # Number of gridpoints in chi-from-omega function 

515 "ChiFromOmega_bound": 15, # Highest gridpoint to use for it 

516} 

517 

518# Make a dictionary with parameters for the default constructor for kNrmInitDstn 

519WealthPortfolioConsumerType_kNrmInitDstn_default = { 

520 "kLogInitMean": -12.0, # Mean of log initial capital 

521 "kLogInitStd": 0.0, # Stdev of log initial capital 

522 "kNrmInitCount": 15, # Number of points in initial capital discretization 

523} 

524 

525# Make a dictionary with parameters for the default constructor for pLvlInitDstn 

526WealthPortfolioConsumerType_pLvlInitDstn_default = { 

527 "pLogInitMean": 0.0, # Mean of log permanent income 

528 "pLogInitStd": 0.0, # Stdev of log permanent income 

529 "pLvlInitCount": 15, # Number of points in initial capital discretization 

530} 

531 

532# Make a dictionary to specify a risky asset consumer type 

533WealthPortfolioConsumerType_solving_default = { 

534 # BASIC HARK PARAMETERS REQUIRED TO SOLVE THE MODEL 

535 "cycles": 1, # Finite, non-cyclic model 

536 "T_cycle": 1, # Number of periods in the cycle for this agent type 

537 "constructors": WealthPortfolioConsumerType_constructors_default, # See dictionary above 

538 # PRIMITIVE RAW PARAMETERS REQUIRED TO SOLVE THE MODEL 

539 "CRRA": 5.0, # Coefficient of relative risk aversion 

540 "Rfree": [1.03], # Return factor on risk free asset 

541 "DiscFac": 0.90, # Intertemporal discount factor 

542 "LivPrb": [0.98], # Survival probability after each period 

543 "PermGroFac": [1.01], # Permanent income growth factor 

544 "BoroCnstArt": 0.0, # Artificial borrowing constraint 

545 "WealthShare": 0.5, # Share of wealth in Cobb-Douglas aggregator in utility function 

546 "WealthShift": 0.1, # Shifter for wealth in utility function 

547 "DiscreteShareBool": False, # Whether risky asset share is restricted to discrete values 

548 "PortfolioBool": True, # Whether there is portfolio choice 

549 "PortfolioBisect": False, # This is a mystery parameter 

550 "IndepDstnBool": True, # Whether income and return shocks are independent 

551 "vFuncBool": False, # Whether to calculate the value function during solution 

552 "CubicBool": False, # Whether to use cubic spline interpolation when True 

553 # (Uses linear spline interpolation for cFunc when False) 

554 "AdjustPrb": 1.0, # Probability that the agent can update their risky portfolio share each period 

555 "RiskyShareFixed": None, # This just needs to exist because of inheritance, does nothing 

556 "sim_common_Rrisky": True, # Whether risky returns have a shared/common value across agents 

557} 

558WealthPortfolioConsumerType_simulation_default = { 

559 # PARAMETERS REQUIRED TO SIMULATE THE MODEL 

560 "AgentCount": 10000, # Number of agents of this type 

561 "T_age": None, # Age after which simulated agents are automatically killed 

562 "aNrmInitMean": 0.0, # Mean of log initial assets 

563 "aNrmInitStd": 1.0, # Standard deviation of log initial assets 

564 "pLvlInitMean": 0.0, # Mean of log initial permanent income 

565 "pLvlInitStd": 0.0, # Standard deviation of log initial permanent income 

566 "PermGroFacAgg": 1.0, # Aggregate permanent income growth factor 

567 # (The portion of PermGroFac attributable to aggregate productivity growth) 

568 "NewbornTransShk": False, # Whether Newborns have transitory shock 

569 # ADDITIONAL OPTIONAL PARAMETERS 

570 "PerfMITShk": False, # Do Perfect Foresight MIT Shock 

571 # (Forces Newborns to follow solution path of the agent they replaced if True) 

572 "neutral_measure": False, # Whether to use permanent income neutral measure (see Harmenberg 2021) 

573} 

574 

575# Assemble the default dictionary 

576WealthPortfolioConsumerType_default = {} 

577WealthPortfolioConsumerType_default.update(WealthPortfolioConsumerType_solving_default) 

578WealthPortfolioConsumerType_default.update( 

579 WealthPortfolioConsumerType_simulation_default 

580) 

581WealthPortfolioConsumerType_default.update( 

582 WealthPortfolioConsumerType_aXtraGrid_default 

583) 

584WealthPortfolioConsumerType_default.update( 

585 WealthPortfolioConsumerType_ShareGrid_default 

586) 

587WealthPortfolioConsumerType_default.update( 

588 WealthPortfolioConsumerType_IncShkDstn_default 

589) 

590WealthPortfolioConsumerType_default.update( 

591 WealthPortfolioConsumerType_RiskyDstn_default 

592) 

593WealthPortfolioConsumerType_default.update(WealthPortfolioConsumerType_ChiFunc_default) 

594WealthPortfolioConsumerType_default.update( 

595 WealthPortfolioConsumerType_kNrmInitDstn_default 

596) 

597WealthPortfolioConsumerType_default.update( 

598 WealthPortfolioConsumerType_pLvlInitDstn_default 

599) 

600init_wealth_portfolio = WealthPortfolioConsumerType_default 

601 

602############################################################################### 

603 

604 

605class WealthPortfolioConsumerType(PortfolioConsumerType): 

606 """ 

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

608 """ 

609 

610 time_inv_ = deepcopy(PortfolioConsumerType.time_inv_) 

611 time_inv_ = time_inv_ + [ 

612 "WealthShare", 

613 "WealthShift", 

614 "ChiFunc", 

615 "RiskyDstn", 

616 ] 

617 default_ = { 

618 "params": init_wealth_portfolio, 

619 "solver": solve_one_period_WealthPortfolio, 

620 "model": "ConsRiskyAsset.yaml", 

621 } 

622 

623 def pre_solve(self): 

624 self.construct("solution_terminal") 

625 self.solution_terminal.ShareFunc = ConstantFunction(1.0)