Coverage for HARK/ConsumptionSaving/ConsPortfolioModel.py: 94%

336 statements  

« prev     ^ index     » next       coverage.py v7.11.0, created at 2025-11-02 05:14 +0000

1""" 

2This file contains classes and functions for representing, solving, and simulating 

3agents who must allocate their resources among consumption, saving in a risk-free 

4asset (with a low return), and saving in a risky asset (with higher average return). 

5""" 

6 

7from copy import deepcopy 

8 

9import numpy as np 

10 

11from HARK import NullFunc 

12from HARK.ConsumptionSaving.ConsIndShockModel import ( 

13 IndShockConsumerType, 

14 make_lognormal_pLvl_init_dstn, 

15 make_lognormal_kNrm_init_dstn, 

16) 

17from HARK.Calibration.Assets.AssetProcesses import ( 

18 make_lognormal_RiskyDstn, 

19 combine_IncShkDstn_and_RiskyDstn, 

20 calc_ShareLimit_for_CRRA, 

21) 

22from HARK.Calibration.Income.IncomeProcesses import ( 

23 construct_lognormal_income_process_unemployment, 

24 get_PermShkDstn_from_IncShkDstn, 

25 get_TranShkDstn_from_IncShkDstn, 

26) 

27from HARK.ConsumptionSaving.ConsRiskyAssetModel import ( 

28 RiskyAssetConsumerType, 

29 make_simple_ShareGrid, 

30 make_AdjustDstn, 

31) 

32from HARK.distributions import expected 

33from HARK.interpolation import ( 

34 BilinearInterp, 

35 ConstantFunction, 

36 CubicInterp, 

37 IdentityFunction, 

38 LinearInterp, 

39 LinearInterpOnInterp1D, 

40 MargValueFuncCRRA, 

41 ValueFuncCRRA, 

42) 

43from HARK.metric import MetricObject 

44from HARK.rewards import UtilityFuncCRRA 

45from HARK.utilities import make_assets_grid 

46 

47__all__ = [ 

48 "PortfolioSolution", 

49 "PortfolioConsumerType", 

50] 

51 

52 

53# Define a class to represent the single period solution of the portfolio choice problem 

54class PortfolioSolution(MetricObject): 

55 r""" 

56 A class for representing the single period solution of the portfolio choice model. 

57 

58 Parameters 

59 ---------- 

60 cFuncAdj : Interp1D 

61 Consumption function over normalized market resources when the agent is able 

62 to adjust their portfolio shares: :math:`c_t=\text{cFuncAdj} (m_t)`. 

63 ShareFuncAdj : Interp1D 

64 Risky share function over normalized market resources when the agent is able 

65 to adjust their portfolio shares: :math:`S_t=\text{ShareFuncAdj} (m_t)`. 

66 vFuncAdj : ValueFuncCRRA 

67 Value function over normalized market resources when the agent is able to 

68 adjust their portfolio shares: :math:`v_t=\text{vFuncAdj} (m_t)`. 

69 vPfuncAdj : MargValueFuncCRRA 

70 Marginal value function over normalized market resources when the agent is able 

71 to adjust their portfolio shares: :math:`v'_t=\text{vPFuncAdj} (m_t)`. 

72 cFuncFxd : Interp2D 

73 Consumption function over normalized market resources and risky portfolio share 

74 when the agent is NOT able to adjust their portfolio shares, so they are fixed: 

75 :math:`c_t=\text{cFuncFxd} (m_t,S_t)`. 

76 ShareFuncFxd : Interp2D 

77 Risky share function over normalized market resources and risky portfolio share 

78 when the agent is NOT able to adjust their portfolio shares, so they are fixed. 

79 This should always be an IdentityFunc, by definition. 

80 vFuncFxd : ValueFuncCRRA 

81 Value function over normalized market resources and risky portfolio share when 

82 the agent is NOT able to adjust their portfolio shares, so they are fixed: 

83 :math:`v_t=\text{vFuncFxd}(m_t,S_t)`. 

84 dvdmFuncFxd : MargValueFuncCRRA 

85 The derivative of the value function with respect to normalized market 

86 resources when the agent is Not able to adjust their portfolio shares, 

87 so they are fixed: :math:`\frac{dv_t}{dm_t}=\text{vFuncFxd}(m_t,S_t)`. 

88 dvdsFuncFxd : MargValueFuncCRRA 

89 The derivative of the value function with respect to risky asset share 

90 when the agent is Not able to adjust their portfolio shares,so they are 

91 fixed: :math:`\frac{dv_t}{dS_t}=\text{vFuncFxd}(m_t,S_t)`. 

92 aGrid: np.array 

93 End-of-period-assets grid used to find the solution. 

94 Share_adj: np.array 

95 Optimal portfolio share associated with each aGrid point: :math:`S^{*}_t=\text{vFuncFxd}(m_t)`. 

96 EndOfPrddvda_adj: np.array 

97 Marginal value of end-of-period resources associated with each aGrid 

98 point. 

99 ShareGrid: np.array 

100 Grid for the portfolio share that is used to solve the model. 

101 EndOfPrddvda_fxd: np.array 

102 Marginal value of end-of-period resources associated with each 

103 (aGrid x sharegrid) combination, for the agent who can not adjust his 

104 portfolio. 

105 AdjustPrb: float 

106 Probability that the agent will be able to adjust his portfolio 

107 next period. 

108 """ 

109 

110 distance_criteria = ["vPfuncAdj"] 

111 

112 def __init__( 

113 self, 

114 cFuncAdj=None, 

115 ShareFuncAdj=None, 

116 vFuncAdj=None, 

117 vPfuncAdj=None, 

118 cFuncFxd=None, 

119 ShareFuncFxd=None, 

120 vFuncFxd=None, 

121 dvdmFuncFxd=None, 

122 dvdsFuncFxd=None, 

123 aGrid=None, 

124 Share_adj=None, 

125 EndOfPrddvda_adj=None, 

126 ShareGrid=None, 

127 EndOfPrddvda_fxd=None, 

128 EndOfPrddvds_fxd=None, 

129 AdjPrb=None, 

130 ): 

131 # Change any missing function inputs to NullFunc 

132 if cFuncAdj is None: 

133 cFuncAdj = NullFunc() 

134 if cFuncFxd is None: 

135 cFuncFxd = NullFunc() 

136 if ShareFuncAdj is None: 

137 ShareFuncAdj = NullFunc() 

138 if ShareFuncFxd is None: 

139 ShareFuncFxd = NullFunc() 

140 if vFuncAdj is None: 

141 vFuncAdj = NullFunc() 

142 if vFuncFxd is None: 

143 vFuncFxd = NullFunc() 

144 if vPfuncAdj is None: 

145 vPfuncAdj = NullFunc() 

146 if dvdmFuncFxd is None: 

147 dvdmFuncFxd = NullFunc() 

148 if dvdsFuncFxd is None: 

149 dvdsFuncFxd = NullFunc() 

150 

151 # Set attributes of self 

152 self.cFuncAdj = cFuncAdj 

153 self.cFuncFxd = cFuncFxd 

154 self.ShareFuncAdj = ShareFuncAdj 

155 self.ShareFuncFxd = ShareFuncFxd 

156 self.vFuncAdj = vFuncAdj 

157 self.vFuncFxd = vFuncFxd 

158 self.vPfuncAdj = vPfuncAdj 

159 self.dvdmFuncFxd = dvdmFuncFxd 

160 self.dvdsFuncFxd = dvdsFuncFxd 

161 self.aGrid = aGrid 

162 self.Share_adj = Share_adj 

163 self.EndOfPrddvda_adj = EndOfPrddvda_adj 

164 self.ShareGrid = ShareGrid 

165 self.EndOfPrddvda_fxd = EndOfPrddvda_fxd 

166 self.EndOfPrddvds_fxd = EndOfPrddvds_fxd 

167 self.AdjPrb = AdjPrb 

168 

169 

170############################################################################### 

171 

172 

173def make_portfolio_solution_terminal(CRRA): 

174 """ 

175 Solves the terminal period of the portfolio choice problem. The solution is 

176 trivial, as usual: consume all market resources, and put nothing in the risky 

177 asset (because you have nothing anyway). 

178 

179 Parameters 

180 ---------- 

181 CRRA : float 

182 Coefficient of relative risk aversion. 

183 

184 Returns 

185 ------- 

186 solution_terminal : PortfolioSolution 

187 Terminal period solution for a consumption-saving problem with portfolio 

188 choice and CRRA utility. 

189 """ 

190 # Consume all market resources: c_T = m_T 

191 cFuncAdj_terminal = IdentityFunction() 

192 cFuncFxd_terminal = IdentityFunction(i_dim=0, n_dims=2) 

193 

194 # Risky share is irrelevant-- no end-of-period assets; set to zero 

195 ShareFuncAdj_terminal = ConstantFunction(0.0) 

196 ShareFuncFxd_terminal = IdentityFunction(i_dim=1, n_dims=2) 

197 

198 # Value function is simply utility from consuming market resources 

199 vFuncAdj_terminal = ValueFuncCRRA(cFuncAdj_terminal, CRRA) 

200 vFuncFxd_terminal = ValueFuncCRRA(cFuncFxd_terminal, CRRA) 

201 

202 # Marginal value of market resources is marg utility at the consumption function 

203 vPfuncAdj_terminal = MargValueFuncCRRA(cFuncAdj_terminal, CRRA) 

204 dvdmFuncFxd_terminal = MargValueFuncCRRA(cFuncFxd_terminal, CRRA) 

205 dvdsFuncFxd_terminal = ConstantFunction(0.0) # No future, no marg value of Share 

206 

207 # Construct the terminal period solution 

208 solution_terminal = PortfolioSolution( 

209 cFuncAdj=cFuncAdj_terminal, 

210 ShareFuncAdj=ShareFuncAdj_terminal, 

211 vFuncAdj=vFuncAdj_terminal, 

212 vPfuncAdj=vPfuncAdj_terminal, 

213 cFuncFxd=cFuncFxd_terminal, 

214 ShareFuncFxd=ShareFuncFxd_terminal, 

215 vFuncFxd=vFuncFxd_terminal, 

216 dvdmFuncFxd=dvdmFuncFxd_terminal, 

217 dvdsFuncFxd=dvdsFuncFxd_terminal, 

218 ) 

219 solution_terminal.hNrm = 0.0 

220 solution_terminal.MPCmin = 1.0 

221 return solution_terminal 

222 

223 

224def calc_radj(shock, share_limit, rfree, crra): 

225 """Expected rate of return adjusted by CRRA 

226 

227 Args: 

228 shock (DiscreteDistribution): Distribution of risky asset returns 

229 share_limit (float): limiting lower bound of risky portfolio share 

230 rfree (float): Risk free interest rate 

231 crra (float): Coefficient of relative risk aversion 

232 """ 

233 rport = share_limit * shock + (1.0 - share_limit) * rfree 

234 return rport ** (1.0 - crra) 

235 

236 

237def calc_human_wealth(shocks, perm_gro_fac, share_limit, rfree, crra, h_nrm_next): 

238 """Calculate human wealth this period given human wealth next period. 

239 

240 Args: 

241 shocks (DiscreteDistribution): Joint distribution of shocks to income and returns. 

242 perm_gro_fac (float): Permanent income growth factor 

243 share_limit (float): limiting lower bound of risky portfolio share 

244 rfree (float): Risk free interest rate 

245 crra (float): Coefficient of relative risk aversion 

246 h_nrm_next (float): Human wealth next period 

247 """ 

248 perm_shk_fac = perm_gro_fac * shocks["PermShk"] 

249 rport = share_limit * shocks["Risky"] + (1.0 - share_limit) * rfree 

250 hNrm = (perm_shk_fac / rport**crra) * (shocks["TranShk"] + h_nrm_next) 

251 return hNrm 

252 

253 

254def calc_m_nrm_next(shocks, b_nrm, perm_gro_fac): 

255 """ 

256 Calculate future realizations of market resources mNrm from the income 

257 shock distribution "shocks" and normalized bank balances b. 

258 """ 

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

260 

261 

262def calc_dvdx_next( 

263 shocks, 

264 b_nrm, 

265 share, 

266 adjust_prob, 

267 perm_gro_fac, 

268 crra, 

269 vp_func_adj, 

270 dvdm_func_fxd, 

271 dvds_func_fxd, 

272): 

273 """ 

274 Evaluate realizations of marginal values next period, based 

275 on the income distribution "shocks", values of bank balances bNrm, and values of 

276 the risky share z. 

277 """ 

278 m_nrm = calc_m_nrm_next(shocks, b_nrm, perm_gro_fac) 

279 dvdm_adj = vp_func_adj(m_nrm) 

280 # No marginal value of shockshare if it's a free choice! 

281 dvds_adj = np.zeros_like(m_nrm) 

282 

283 if adjust_prob < 1.0: 

284 # Expand to the same dimensions as mNrm 

285 share_exp = np.full_like(m_nrm, share) 

286 dvdm_fxd = dvdm_func_fxd(m_nrm, share_exp) 

287 dvds_fxd = dvds_func_fxd(m_nrm, share_exp) 

288 # Combine by adjustment probability 

289 dvdm = adjust_prob * dvdm_adj + (1.0 - adjust_prob) * dvdm_fxd 

290 dvds = adjust_prob * dvds_adj + (1.0 - adjust_prob) * dvds_fxd 

291 else: # Don't bother evaluating if there's no chance that portfolio share is fixed 

292 dvdm = dvdm_adj 

293 dvds = dvds_adj 

294 

295 perm_shk_fac = shocks["PermShk"] * perm_gro_fac 

296 dvdm = perm_shk_fac ** (-crra) * dvdm 

297 dvds = perm_shk_fac ** (1.0 - crra) * dvds 

298 

299 return dvdm, dvds 

300 

301 

302def calc_end_of_prd_dvdx(shocks, a_nrm, share, rfree, dvdb_func, dvds_func): 

303 """ 

304 Compute end-of-period marginal values at values a, conditional 

305 on risky asset return shocks and risky share z. 

306 """ 

307 # Calculate future realizations of bank balances bNrm 

308 ex_ret = shocks - rfree # Excess returns 

309 r_port = rfree + share * ex_ret # Portfolio return 

310 b_nrm = r_port * a_nrm 

311 # Ensure shape concordance 

312 share_exp = np.full_like(b_nrm, share) 

313 

314 # Calculate and return dvda, dvds 

315 dvda = r_port * dvdb_func(b_nrm, share_exp) 

316 dvds = ex_ret * a_nrm * dvdb_func(b_nrm, share_exp) + dvds_func(b_nrm, share_exp) 

317 return dvda, dvds 

318 

319 

320def calc_v_intermed( 

321 shocks, b_nrm, share, adjust_prob, perm_gro_fac, crra, v_func_adj, v_func_fxd 

322): 

323 """ 

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

325 income shocks shocks, and the risky asset share. 

326 """ 

327 m_nrm = calc_m_nrm_next(shocks, b_nrm, perm_gro_fac) 

328 

329 v_adj = v_func_adj(m_nrm) 

330 if adjust_prob < 1.0: 

331 v_fxd = v_func_fxd(m_nrm, share) 

332 # Combine by adjustment probability 

333 v_next = adjust_prob * v_adj + (1.0 - adjust_prob) * v_fxd 

334 else: # Don't bother evaluating if there's no chance that portfolio share is fixed 

335 v_next = v_adj 

336 

337 v_intermed = (shocks["PermShk"] * perm_gro_fac) ** (1.0 - crra) * v_next 

338 return v_intermed 

339 

340 

341def calc_end_of_prd_v(shocks, a_nrm, share, rfree, v_func): 

342 """Compute end-of-period values.""" 

343 # Calculate future realizations of bank balances bNrm 

344 ex_ret = shocks - rfree 

345 r_port = rfree + share * ex_ret 

346 b_rnm = r_port * a_nrm 

347 

348 # Make an extended share_next of the same dimension as b_nrm so 

349 # that the function can be vectorized 

350 share_exp = np.full_like(b_rnm, share) 

351 

352 return v_func(b_rnm, share_exp) 

353 

354 

355def calc_m_nrm_next_joint(shocks, a_nrm, share, rfree, perm_gro_fac): 

356 """ 

357 Calculate future realizations of market resources mNrm from the shock 

358 distribution shocks, normalized end-of-period assets a, and risky share z. 

359 """ 

360 # Calculate future realizations of bank balances bNrm 

361 ex_ret = shocks["Risky"] - rfree 

362 r_port = rfree + share * ex_ret 

363 b_nrm = r_port * a_nrm 

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

365 

366 

367def calc_end_of_prd_dvdx_joint( 

368 shocks, 

369 a_nrm, 

370 share, 

371 rfree, 

372 adjust_prob, 

373 perm_gro_fac, 

374 crra, 

375 vp_func_adj, 

376 dvdm_func_fxd, 

377 dvds_func_fxd, 

378): 

379 """ 

380 Evaluate end-of-period marginal value of assets and risky share based 

381 on the shock distribution S, values of bend of period assets a, and 

382 risky share z. 

383 """ 

384 m_nrm = calc_m_nrm_next_joint(shocks, a_nrm, share, rfree, perm_gro_fac) 

385 ex_ret = shocks["Risky"] - rfree 

386 r_port = rfree + share * ex_ret 

387 dvdm_adj = vp_func_adj(m_nrm) 

388 # No marginal value of Share if it's a free choice! 

389 dvds_adj = np.zeros_like(m_nrm) 

390 

391 if adjust_prob < 1.0: 

392 # Expand to the same dimensions as mNrm 

393 share_exp = np.full_like(m_nrm, share) 

394 dvdm_fxd = dvdm_func_fxd(m_nrm, share_exp) 

395 dvds_fxd = dvds_func_fxd(m_nrm, share_exp) 

396 # Combine by adjustment probability 

397 dvdm_next = adjust_prob * dvdm_adj + (1.0 - adjust_prob) * dvdm_fxd 

398 dvds_next = adjust_prob * dvds_adj + (1.0 - adjust_prob) * dvds_fxd 

399 else: # Don't bother evaluating if there's no chance that portfolio share is fixed 

400 dvdm_next = dvdm_adj 

401 dvds_next = dvds_adj 

402 

403 perm_shk_fac = shocks["PermShk"] * perm_gro_fac 

404 temp_fac = perm_shk_fac ** (-crra) * dvdm_next 

405 eop_dvda = r_port * temp_fac 

406 eop_dvds = ex_ret * a_nrm * temp_fac + perm_shk_fac ** (1 - crra) * dvds_next 

407 

408 return eop_dvda, eop_dvds 

409 

410 

411def calc_end_of_prd_v_joint( 

412 shocks, a_nrm, share, rfree, adjust_prob, perm_gro_fac, crra, v_func_adj, v_func_fxd 

413): 

414 """ 

415 Evaluate end-of-period value, based on the shock distribution S, values 

416 of bank balances bNrm, and values of the risky share z. 

417 """ 

418 m_nrm = calc_m_nrm_next_joint(shocks, a_nrm, share, rfree, perm_gro_fac) 

419 v_adj = v_func_adj(m_nrm) 

420 

421 if adjust_prob < 1.0: 

422 # Expand to the same dimensions as mNrm 

423 share_exp = np.full_like(m_nrm, share) 

424 v_fxd = v_func_fxd(m_nrm, share_exp) 

425 # Combine by adjustment probability 

426 v_next = adjust_prob * v_adj + (1.0 - adjust_prob) * v_fxd 

427 else: # Don't bother evaluating if there's no chance that portfolio share is fixed 

428 v_next = v_adj 

429 

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

431 

432 

433def solve_one_period_ConsPortfolio( 

434 solution_next, 

435 ShockDstn, 

436 IncShkDstn, 

437 RiskyDstn, 

438 LivPrb, 

439 DiscFac, 

440 CRRA, 

441 Rfree, 

442 PermGroFac, 

443 BoroCnstArt, 

444 aXtraGrid, 

445 ShareGrid, 

446 AdjustPrb, 

447 ShareLimit, 

448 vFuncBool, 

449 DiscreteShareBool, 

450 IndepDstnBool, 

451): 

452 """ 

453 Solve one period of a consumption-saving problem with portfolio allocation 

454 between a riskless and risky asset. This function handles various sub-cases 

455 or variations on the problem, including the possibility that the agent does 

456 not necessarily get to update their portfolio share in every period, or that 

457 they must choose a discrete rather than continuous risky share. 

458 

459 Parameters 

460 ---------- 

461 solution_next : PortfolioSolution 

462 Solution to next period's problem. 

463 ShockDstn : Distribution 

464 Joint distribution of permanent income shocks, transitory income shocks, 

465 and risky returns. This is only used if the input IndepDstnBool is False, 

466 indicating that income and return distributions can't be assumed to be 

467 independent. 

468 IncShkDstn : Distribution 

469 Discrete distribution of permanent income shocks and transitory income 

470 shocks. This is only used if the input IndepDstnBool is True, indicating 

471 that income and return distributions are independent. 

472 RiskyDstn : Distribution 

473 Distribution of risky asset returns. This is only used if the input 

474 IndepDstnBool is True, indicating that income and return distributions 

475 are independent. 

476 LivPrb : float 

477 Survival probability; likelihood of being alive at the beginning of 

478 the succeeding period. 

479 DiscFac : float 

480 Intertemporal discount factor for future utility. 

481 CRRA : float 

482 Coefficient of relative risk aversion. 

483 Rfree : float 

484 Risk free interest factor on end-of-period assets. 

485 PermGroFac : float 

486 Expected permanent income growth factor at the end of this period. 

487 BoroCnstArt: float or None 

488 Borrowing constraint for the minimum allowable assets to end the 

489 period with. In this model, it is *required* to be zero. 

490 aXtraGrid: np.array 

491 Array of "extra" end-of-period asset values-- assets above the 

492 absolute minimum acceptable level. 

493 ShareGrid : np.array 

494 Array of risky portfolio shares on which to define the interpolation 

495 of the consumption function when Share is fixed. Also used when the 

496 risky share choice is specified as discrete rather than continuous. 

497 AdjustPrb : float 

498 Probability that the agent will be able to update his portfolio share. 

499 ShareLimit : float 

500 Limiting lower bound of risky portfolio share as mNrm approaches infinity. 

501 vFuncBool: boolean 

502 An indicator for whether the value function should be computed and 

503 included in the reported solution. 

504 DiscreteShareBool : bool 

505 Indicator for whether risky portfolio share should be optimized on the 

506 continuous [0,1] interval using the FOC (False), or instead only selected 

507 from the discrete set of values in ShareGrid (True). If True, then 

508 vFuncBool must also be True. 

509 IndepDstnBool : bool 

510 Indicator for whether the income and risky return distributions are in- 

511 dependent of each other, which can speed up the expectations step. 

512 

513 Returns 

514 ------- 

515 solution_now : PortfolioSolution 

516 Solution to this period's problem. 

517 """ 

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

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

520 if BoroCnstArt != 0.0: 

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

522 

523 # Make sure that if risky portfolio share is optimized only discretely, then 

524 # the value function is also constructed (else this task would be impossible). 

525 if DiscreteShareBool and (not vFuncBool): 

526 raise ValueError( 

527 "PortfolioConsumerType requires vFuncBool to be True when DiscreteShareBool is True!" 

528 ) 

529 

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

531 uFunc = UtilityFuncCRRA(CRRA) 

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

533 

534 # Unpack next period's solution for easier access 

535 vPfuncAdj_next = solution_next.vPfuncAdj 

536 dvdmFuncFxd_next = solution_next.dvdmFuncFxd 

537 dvdsFuncFxd_next = solution_next.dvdsFuncFxd 

538 vFuncAdj_next = solution_next.vFuncAdj 

539 vFuncFxd_next = solution_next.vFuncFxd 

540 

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

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

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

544 

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

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

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

548 # experience next period. 

549 

550 # Unpack the risky return shock distribution 

551 Risky_next = RiskyDstn.atoms 

552 RiskyMax = np.max(Risky_next) 

553 RiskyMin = np.min(Risky_next) 

554 

555 # Perform an alternate calculation of the absolute patience factor when 

556 # returns are risky. This uses the Merton-Samuelson limiting risky share, 

557 # which is what's relevant as mNrm goes to infinity. 

558 

559 R_adj = expected(calc_radj, RiskyDstn, args=(ShareLimit, Rfree, CRRA))[0] 

560 PatFac = (DiscFacEff * R_adj) ** (1.0 / CRRA) 

561 MPCminNow = 1.0 / (1.0 + PatFac / solution_next.MPCmin) 

562 

563 # Also perform an alternate calculation for human wealth under risky returns 

564 

565 # This correctly accounts for risky returns and risk aversion 

566 hNrmNow = ( 

567 expected( 

568 calc_human_wealth, 

569 ShockDstn, 

570 args=(PermGroFac, ShareLimit, Rfree, CRRA, solution_next.hNrm), 

571 ) 

572 / R_adj 

573 ) 

574 

575 # Set the terms of the limiting linear consumption function as mNrm goes to infinity 

576 cFuncLimitIntercept = MPCminNow * hNrmNow 

577 cFuncLimitSlope = MPCminNow 

578 

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

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

581 if BoroCnstNat_iszero: 

582 aNrmGrid = aXtraGrid 

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

584 else: 

585 # Add an asset point at exactly zero 

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

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

588 

589 # Get grid and shock sizes, for easier indexing 

590 aNrmCount = aNrmGrid.size 

591 ShareCount = ShareGrid.size 

592 

593 # If the income shock distribution is independent from the risky return distribution, 

594 # then taking end-of-period expectations can proceed in a two part process: First, 

595 # construct an "intermediate" value function by integrating out next period's income 

596 # shocks, *then* compute end-of-period expectations by integrating out return shocks. 

597 # This method is lengthy to code, but can be significantly faster. 

598 if IndepDstnBool: 

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

600 bNrmNext, ShareNext = np.meshgrid(bNrmGrid, ShareGrid, indexing="ij") 

601 

602 # Define functions that are used internally to evaluate future realizations 

603 

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

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

606 # values across income and risky return shocks. 

607 

608 # Calculate intermediate marginal value of bank balances and risky portfolio share 

609 # by taking expectations over income shocks 

610 

611 dvdb_intermed, dvds_intermed = expected( 

612 calc_dvdx_next, 

613 IncShkDstn, 

614 args=( 

615 bNrmNext, 

616 ShareNext, 

617 AdjustPrb, 

618 PermGroFac, 

619 CRRA, 

620 vPfuncAdj_next, 

621 dvdmFuncFxd_next, 

622 dvdsFuncFxd_next, 

623 ), 

624 ) 

625 

626 dvdbNvrs_intermed = uFunc.derinv(dvdb_intermed, order=(1, 0)) 

627 dvdbNvrsFunc_intermed = BilinearInterp(dvdbNvrs_intermed, bNrmGrid, ShareGrid) 

628 dvdbFunc_intermed = MargValueFuncCRRA(dvdbNvrsFunc_intermed, CRRA) 

629 

630 dvdsFunc_intermed = BilinearInterp(dvds_intermed, bNrmGrid, ShareGrid) 

631 

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

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

634 

635 # Define functions for calculating end-of-period marginal value 

636 

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

638 

639 # Calculate end-of-period marginal value of assets and risky portfolio share 

640 # by taking expectations 

641 

642 EndOfPrd_dvda, EndOfPrd_dvds = DiscFacEff * expected( 

643 calc_end_of_prd_dvdx, 

644 RiskyDstn, 

645 args=(aNrmNow, ShareNext, Rfree, dvdbFunc_intermed, dvdsFunc_intermed), 

646 ) 

647 

648 EndOfPrd_dvdaNvrs = uFunc.derinv(EndOfPrd_dvda) 

649 

650 # Make the end-of-period value function if the value function is requested 

651 if vFuncBool: 

652 # Calculate intermediate value by taking expectations over income shocks 

653 v_intermed = expected( 

654 calc_v_intermed, 

655 IncShkDstn, 

656 args=( 

657 bNrmNext, 

658 ShareNext, 

659 AdjustPrb, 

660 PermGroFac, 

661 CRRA, 

662 vFuncAdj_next, 

663 vFuncFxd_next, 

664 ), 

665 ) 

666 

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

668 vNvrs_intermed = uFunc.inv(v_intermed) 

669 vNvrsFunc_intermed = BilinearInterp(vNvrs_intermed, bNrmGrid, ShareGrid) 

670 vFunc_intermed = ValueFuncCRRA(vNvrsFunc_intermed, CRRA) 

671 

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

673 EndOfPrd_v = DiscFacEff * expected( 

674 calc_end_of_prd_v, 

675 RiskyDstn, 

676 args=(aNrmNow, ShareNext, Rfree, vFunc_intermed), 

677 ) 

678 EndOfPrd_vNvrs = uFunc.inv(EndOfPrd_v) 

679 

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

681 EndOfPrd_vNvrsFunc = BilinearInterp(EndOfPrd_vNvrs, aNrmGrid, ShareGrid) 

682 EndOfPrd_vFunc = ValueFuncCRRA(EndOfPrd_vNvrsFunc, CRRA) 

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

684 

685 # If the income shock distribution and risky return distribution are *NOT* 

686 # independent, then computation of end-of-period expectations are simpler in 

687 # code, but might take longer to execute 

688 else: 

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

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

691 

692 # Define functions that are used internally to evaluate future realizations 

693 

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

695 

696 # Calculate end-of-period marginal value of assets and risky share by taking expectations 

697 EndOfPrd_dvda, EndOfPrd_dvds = DiscFacEff * expected( 

698 calc_end_of_prd_dvdx_joint, 

699 ShockDstn, 

700 args=( 

701 aNrmNow, 

702 ShareNext, 

703 Rfree, 

704 AdjustPrb, 

705 PermGroFac, 

706 CRRA, 

707 vPfuncAdj_next, 

708 dvdmFuncFxd_next, 

709 dvdsFuncFxd_next, 

710 ), 

711 ) 

712 EndOfPrd_dvdaNvrs = uFunc.derinv(EndOfPrd_dvda) 

713 

714 # Construct the end-of-period value function if requested 

715 if vFuncBool: 

716 # Calculate end-of-period value, its derivative, and their pseudo-inverse 

717 EndOfPrd_v = DiscFacEff * expected( 

718 calc_end_of_prd_v_joint, 

719 ShockDstn, 

720 args=( 

721 aNrmNow, 

722 ShareNext, 

723 Rfree, 

724 AdjustPrb, 

725 PermGroFac, 

726 CRRA, 

727 vFuncAdj_next, 

728 vFuncFxd_next, 

729 ), 

730 ) 

731 EndOfPrd_vNvrs = uFunc.inv(EndOfPrd_v) 

732 

733 # value transformed through inverse utility 

734 EndOfPrd_vNvrsP = EndOfPrd_dvda * uFunc.derinv(EndOfPrd_v, order=(0, 1)) 

735 

736 # Construct the end-of-period value function 

737 EndOfPrd_vNvrsFunc_by_Share = [] 

738 for j in range(ShareCount): 

739 EndOfPrd_vNvrsFunc_by_Share.append( 

740 CubicInterp( 

741 aNrmNow[:, j], EndOfPrd_vNvrs[:, j], EndOfPrd_vNvrsP[:, j] 

742 ) 

743 ) 

744 EndOfPrd_vNvrsFunc = LinearInterpOnInterp1D( 

745 EndOfPrd_vNvrsFunc_by_Share, ShareGrid 

746 ) 

747 EndOfPrd_vFunc = ValueFuncCRRA(EndOfPrd_vNvrsFunc, CRRA) 

748 

749 # Find the optimal risky asset share either by choosing the best value among 

750 # the discrete grid choices, or by satisfying the FOC with equality (continuous) 

751 if DiscreteShareBool: 

752 # If we're restricted to discrete choices, then portfolio share is 

753 # the one with highest value for each aNrm gridpoint 

754 opt_idx = np.argmax(EndOfPrd_v, axis=1) 

755 ShareAdj_now = ShareGrid[opt_idx] 

756 

757 # Take cNrm at that index as well... and that's it! 

758 cNrmAdj_now = EndOfPrd_dvdaNvrs[np.arange(aNrmCount), opt_idx] 

759 

760 else: 

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

762 # order condition EndOfPrd_dvds == 0. 

763 FOC_s = EndOfPrd_dvds # Relabel for convenient typing 

764 

765 # For each value of aNrm, find the value of Share such that FOC_s == 0 

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

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

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

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

770 

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

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

773 a_idx = np.arange(aNrmCount) 

774 bot_s = ShareGrid[share_idx] 

775 top_s = ShareGrid[share_idx + 1] 

776 bot_f = FOC_s[a_idx, share_idx] 

777 top_f = FOC_s[a_idx, share_idx + 1] 

778 bot_c = EndOfPrd_dvdaNvrs[a_idx, share_idx] 

779 top_c = EndOfPrd_dvdaNvrs[a_idx, share_idx + 1] 

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

781 

782 # Calculate the continuous optimal risky share and optimal consumption 

783 ShareAdj_now = (1.0 - alpha) * bot_s + alpha * top_s 

784 cNrmAdj_now = (1.0 - alpha) * bot_c + alpha * top_c 

785 

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

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

788 constrained_top = FOC_s[:, -1] > 0.0 

789 constrained_bot = FOC_s[:, 0] < 0.0 

790 

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

792 # constraint should never be relevant) 

793 ShareAdj_now[constrained_top] = 1.0 

794 ShareAdj_now[constrained_bot] = 0.0 

795 cNrmAdj_now[constrained_top] = EndOfPrd_dvdaNvrs[constrained_top, -1] 

796 cNrmAdj_now[constrained_bot] = EndOfPrd_dvdaNvrs[constrained_bot, 0] 

797 

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

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

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

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

802 if not BoroCnstNat_iszero: 

803 ShareAdj_now[0] = 1.0 

804 cNrmAdj_now[0] = EndOfPrd_dvdaNvrs[0, -1] 

805 

806 # Construct functions characterizing the solution for this period 

807 

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

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

810 mNrmAdj_now = np.insert(aNrmGrid + cNrmAdj_now, 0, 0.0) 

811 cNrmAdj_now = np.insert(cNrmAdj_now, 0, 0.0) 

812 cFuncAdj_now = LinearInterp(mNrmAdj_now, cNrmAdj_now) 

813 

814 # Construct the marginal value (of mNrm) function when the agent can adjust 

815 vPfuncAdj_now = MargValueFuncCRRA(cFuncAdj_now, CRRA) 

816 

817 # Construct the consumption function when the agent *can't* adjust the risky 

818 # share, as well as the marginal value of Share function 

819 cFuncFxd_by_Share = [] 

820 dvdsFuncFxd_by_Share = [] 

821 for j in range(ShareCount): 

822 cNrmFxd_temp = np.insert(EndOfPrd_dvdaNvrs[:, j], 0, 0.0) 

823 mNrmFxd_temp = np.insert(aNrmGrid + cNrmFxd_temp[1:], 0, 0.0) 

824 dvdsFxd_temp = np.insert(EndOfPrd_dvds[:, j], 0, EndOfPrd_dvds[0, j]) 

825 cFuncFxd_by_Share.append(LinearInterp(mNrmFxd_temp, cNrmFxd_temp)) 

826 dvdsFuncFxd_by_Share.append(LinearInterp(mNrmFxd_temp, dvdsFxd_temp)) 

827 cFuncFxd_now = LinearInterpOnInterp1D(cFuncFxd_by_Share, ShareGrid) 

828 dvdsFuncFxd_now = LinearInterpOnInterp1D(dvdsFuncFxd_by_Share, ShareGrid) 

829 

830 # The share function when the agent can't adjust his portfolio is trivial 

831 ShareFuncFxd_now = IdentityFunction(i_dim=1, n_dims=2) 

832 

833 # Construct the marginal value of mNrm function when the agent can't adjust his share 

834 dvdmFuncFxd_now = MargValueFuncCRRA(cFuncFxd_now, CRRA) 

835 

836 # Construct the optimal risky share function when adjusting is possible. 

837 # The interpolation method depends on whether the choice is discrete or continuous. 

838 if DiscreteShareBool: 

839 # If the share choice is discrete, the "interpolated" share function acts 

840 # like a step function, with jumps at the midpoints of mNrm gridpoints. 

841 # Because an actual step function would break our (assumed continuous) linear 

842 # interpolator, there's a *tiny* region with extremely high slope. 

843 mNrmAdj_mid = (mNrmAdj_now[2:] + mNrmAdj_now[1:-1]) / 2 

844 mNrmAdj_plus = mNrmAdj_mid * (1.0 + 1e-12) 

845 mNrmAdj_comb = (np.transpose(np.vstack((mNrmAdj_mid, mNrmAdj_plus)))).flatten() 

846 mNrmAdj_comb = np.append(np.insert(mNrmAdj_comb, 0, 0.0), mNrmAdj_now[-1]) 

847 Share_comb = (np.transpose(np.vstack((ShareAdj_now, ShareAdj_now)))).flatten() 

848 ShareFuncAdj_now = LinearInterp(mNrmAdj_comb, Share_comb) 

849 

850 else: 

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

852 if BoroCnstNat_iszero: 

853 Share_lower_bound = ShareLimit 

854 else: 

855 Share_lower_bound = 1.0 

856 ShareAdj_now = np.insert(ShareAdj_now, 0, Share_lower_bound) 

857 ShareFuncAdj_now = LinearInterp(mNrmAdj_now, ShareAdj_now, ShareLimit, 0.0) 

858 

859 # Add the value function if requested 

860 if vFuncBool: 

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

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

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

864 

865 # Construct the value function when the agent can adjust his portfolio 

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

867 cNrm_temp = cFuncAdj_now(mNrm_temp) 

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

869 Share_temp = ShareFuncAdj_now(mNrm_temp) 

870 v_temp = uFunc(cNrm_temp) + EndOfPrd_vFunc(aNrm_temp, Share_temp) 

871 vNvrs_temp = uFunc.inv(v_temp) 

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

873 vNvrsFuncAdj = CubicInterp( 

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

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

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

877 ) 

878 # Re-curve the pseudo-inverse value function 

879 vFuncAdj_now = ValueFuncCRRA(vNvrsFuncAdj, CRRA) 

880 

881 # Construct the value function when the agent *can't* adjust his portfolio 

882 mNrm_temp, Share_temp = np.meshgrid(aXtraGrid, ShareGrid) 

883 cNrm_temp = cFuncFxd_now(mNrm_temp, Share_temp) 

884 aNrm_temp = mNrm_temp - cNrm_temp 

885 v_temp = uFunc(cNrm_temp) + EndOfPrd_vFunc(aNrm_temp, Share_temp) 

886 vNvrs_temp = uFunc.inv(v_temp) 

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

888 vNvrsFuncFxd_by_Share = [] 

889 for j in range(ShareCount): 

890 vNvrsFuncFxd_by_Share.append( 

891 CubicInterp( 

892 np.insert(mNrm_temp[:, 0], 0, 0.0), # x_list 

893 np.insert(vNvrs_temp[:, j], 0, 0.0), # f_list 

894 np.insert(vNvrsP_temp[:, j], 0, vNvrsP_temp[j, 0]), # dfdx_list 

895 ) 

896 ) 

897 vNvrsFuncFxd = LinearInterpOnInterp1D(vNvrsFuncFxd_by_Share, ShareGrid) 

898 vFuncFxd_now = ValueFuncCRRA(vNvrsFuncFxd, CRRA) 

899 

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

901 vFuncAdj_now = NullFunc() 

902 vFuncFxd_now = NullFunc() 

903 

904 # Package and return the solution 

905 solution_now = PortfolioSolution( 

906 cFuncAdj=cFuncAdj_now, 

907 ShareFuncAdj=ShareFuncAdj_now, 

908 vPfuncAdj=vPfuncAdj_now, 

909 vFuncAdj=vFuncAdj_now, 

910 cFuncFxd=cFuncFxd_now, 

911 ShareFuncFxd=ShareFuncFxd_now, 

912 dvdmFuncFxd=dvdmFuncFxd_now, 

913 dvdsFuncFxd=dvdsFuncFxd_now, 

914 vFuncFxd=vFuncFxd_now, 

915 AdjPrb=AdjustPrb, 

916 ) 

917 solution_now.hNrm = hNrmNow 

918 solution_now.MPCmin = MPCminNow 

919 return solution_now 

920 

921 

922############################################################################### 

923 

924# Make a dictionary of constructors for the portfolio choice consumer type 

925PortfolioConsumerType_constructors_default = { 

926 "IncShkDstn": construct_lognormal_income_process_unemployment, 

927 "PermShkDstn": get_PermShkDstn_from_IncShkDstn, 

928 "TranShkDstn": get_TranShkDstn_from_IncShkDstn, 

929 "aXtraGrid": make_assets_grid, 

930 "RiskyDstn": make_lognormal_RiskyDstn, 

931 "ShockDstn": combine_IncShkDstn_and_RiskyDstn, 

932 "ShareLimit": calc_ShareLimit_for_CRRA, 

933 "ShareGrid": make_simple_ShareGrid, 

934 "AdjustDstn": make_AdjustDstn, 

935 "kNrmInitDstn": make_lognormal_kNrm_init_dstn, 

936 "pLvlInitDstn": make_lognormal_pLvl_init_dstn, 

937 "solution_terminal": make_portfolio_solution_terminal, 

938} 

939 

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

941PortfolioConsumerType_kNrmInitDstn_default = { 

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

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

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

945} 

946 

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

948PortfolioConsumerType_pLvlInitDstn_default = { 

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

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

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

952} 

953 

954# Default parameters to make IncShkDstn using construct_lognormal_income_process_unemployment 

955PortfolioConsumerType_IncShkDstn_default = { 

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

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

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

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

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

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

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

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

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

965} 

966 

967# Default parameters to make aXtraGrid using make_assets_grid 

968PortfolioConsumerType_aXtraGrid_default = { 

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

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

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

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

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

974} 

975 

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

977PortfolioConsumerType_RiskyDstn_default = { 

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

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

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

981} 

982PortfolioConsumerType_ShareGrid_default = { 

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

984} 

985 

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

987PortfolioConsumerType_solving_default = { 

988 # BASIC HARK PARAMETERS REQUIRED TO SOLVE THE MODEL 

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

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

991 "constructors": PortfolioConsumerType_constructors_default, # See dictionary above 

992 # PRIMITIVE RAW PARAMETERS REQUIRED TO SOLVE THE MODEL 

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

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

995 "DiscFac": 0.90, # Intertemporal discount factor 

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

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

998 "BoroCnstArt": 0.0, # Artificial borrowing constraint 

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

1000 "PortfolioBool": True, # This *must* be set to True; only exists because of inheritance 

1001 "PortfolioBisect": False, # What does this do? 

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

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

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

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

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

1007 "RiskyShareFixed": None, # This does nothing in this model; only exists because of inheritance 

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

1009} 

1010PortfolioConsumerType_simulation_default = { 

1011 # PARAMETERS REQUIRED TO SIMULATE THE MODEL 

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

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

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

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

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

1017 # ADDITIONAL OPTIONAL PARAMETERS 

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

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

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

1021} 

1022PortfolioConsumerType_default = {} 

1023PortfolioConsumerType_default.update(PortfolioConsumerType_solving_default) 

1024PortfolioConsumerType_default.update(PortfolioConsumerType_simulation_default) 

1025PortfolioConsumerType_default.update(PortfolioConsumerType_kNrmInitDstn_default) 

1026PortfolioConsumerType_default.update(PortfolioConsumerType_pLvlInitDstn_default) 

1027PortfolioConsumerType_default.update(PortfolioConsumerType_aXtraGrid_default) 

1028PortfolioConsumerType_default.update(PortfolioConsumerType_ShareGrid_default) 

1029PortfolioConsumerType_default.update(PortfolioConsumerType_IncShkDstn_default) 

1030PortfolioConsumerType_default.update(PortfolioConsumerType_RiskyDstn_default) 

1031init_portfolio = PortfolioConsumerType_default 

1032 

1033 

1034class PortfolioConsumerType(RiskyAssetConsumerType): 

1035 r""" 

1036 A consumer type based on IndShockRiskyAssetConsumerType, with portfolio optimization. 

1037 The agent is only able to change their risky asset share with a certain probability. 

1038 

1039 .. math:: 

1040 \newcommand{\CRRA}{\rho} 

1041 \newcommand{\DiePrb}{\mathsf{D}} 

1042 \newcommand{\PermGroFac}{\Gamma} 

1043 \newcommand{\Rfree}{\mathsf{R}} 

1044 \newcommand{\DiscFac}{\beta} 

1045 \begin{align*} 

1046 v_t(m_t,S_t) &= \max_{c_t,S^{*}_t} u(c_t) + \DiscFac (1-\DiePrb_{t+1}) \mathbb{E}_{t} \left[(\PermGroFac_{t+1}\psi_{t+1})^{1-\CRRA} v_{t+1}(m_{t+1},S_{t+1}) \right], \\ 

1047 & \text{s.t.} \\ 

1048 a_t &= m_t - c_t, \\ 

1049 a_t &\geq \underline{a}, \\ 

1050 m_{t+1} &= \mathsf{R}_{t+1}/(\PermGroFac_{t+1} \psi_{t+1}) a_t + \theta_{t+1}, \\ 

1051 \mathsf{R}_{t+1} &=S_t\phi_{t+1}\mathbf{R}_{t+1}+ (1-S_t)\mathsf{R}_{t+1}, \\ 

1052 S_{t+1} &= \begin{cases} 

1053 S^{*}_t & \text{if } p_t < \wp\\ 

1054 S_t & \text{if } p_t \geq \wp, 

1055 \end{cases}\\ 

1056 (\psi_{t+1},\theta_{t+1},\phi_{t+1},p_t) &\sim F_{t+1}, \\ 

1057 \mathbb{E}[\psi]=\mathbb{E}[\theta] &= 1.\\ 

1058 u(c) &= \frac{c^{1-\CRRA}}{1-\CRRA} \\ 

1059 \end{align*} 

1060 

1061 

1062 Constructors 

1063 ------------ 

1064 IncShkDstn: Constructor, :math:`\psi`, :math:`\theta` 

1065 The agent's income shock distributions. 

1066 

1067 It's default constructor is :func:`HARK.Calibration.Income.IncomeProcesses.construct_lognormal_income_process_unemployment` 

1068 aXtraGrid: Constructor 

1069 The agent's asset grid. 

1070 

1071 It's default constructor is :func:`HARK.utilities.make_assets_grid` 

1072 ShareGrid: Constructor 

1073 The agent's risky asset share grid 

1074 

1075 It's default constructor is :func:`HARK.ConsumptionSaving.ConsRiskyAssetModel.make_simple_ShareGrid` 

1076 RiskyDstn: Constructor, :math:`\phi` 

1077 The agent's asset shock distribution for risky assets. 

1078 

1079 It's default constructor is :func:`HARK.Calibration.Assets.AssetProcesses.make_lognormal_RiskyDstn` 

1080 

1081 Solving Parameters 

1082 ------------------ 

1083 cycles: int 

1084 0 specifies an infinite horizon model, 1 specifies a finite model. 

1085 T_cycle: int 

1086 Number of periods in the cycle for this agent type. 

1087 CRRA: float, :math:`\rho` 

1088 Coefficient of Relative Risk Aversion. 

1089 Rfree: float or list[float], time varying, :math:`\mathsf{R}` 

1090 Risk Free interest rate. Pass a list of floats to make Rfree time varying. 

1091 DiscFac: float, :math:`\beta` 

1092 Intertemporal discount factor. 

1093 LivPrb: list[float], time varying, :math:`1-\mathsf{D}` 

1094 Survival probability after each period. 

1095 PermGroFac: list[float], time varying, :math:`\Gamma` 

1096 Permanent income growth factor. 

1097 BoroCnstArt: float, default=0.0, :math:`\underline{a}` 

1098 The minimum Asset/Perminant Income ratio. for this agent, BoroCnstArt must be 0. 

1099 vFuncBool: bool 

1100 Whether to calculate the value function during solution. 

1101 CubicBool: bool 

1102 Whether to use cubic spline interpoliation. 

1103 AdjustPrb: float or list[float], time varying 

1104 Must be between 0 and 1. Probability that the agent can update their risky portfolio share each period. Pass a list of floats to make AdjustPrb time varying. 

1105 

1106 Simulation Parameters 

1107 --------------------- 

1108 sim_common_Rrisky: Boolean 

1109 Whether risky returns have a shared/common value across agents. If True, Risky return's can't be time varying. 

1110 AgentCount: int 

1111 Number of agents of this kind that are created during simulations. 

1112 T_age: int 

1113 Age after which to automatically kill agents, None to ignore. 

1114 T_sim: int, required for simulation 

1115 Number of periods to simulate. 

1116 track_vars: list[strings] 

1117 List of variables that should be tracked when running the simulation. 

1118 For this agent, the options are 'Adjust', 'PermShk', 'Risky', 'TranShk', 'aLvl', 'aNrm', 'bNrm', 'cNrm', 'mNrm', 'pLvl', and 'who_dies'. 

1119 

1120 Adjust is the array of which agents can adjust 

1121 

1122 PermShk is the agent's permanent income shock 

1123 

1124 Risky is the agent's risky asset shock 

1125 

1126 TranShk is the agent's transitory income shock 

1127 

1128 aLvl is the nominal asset level 

1129 

1130 aNrm is the normalized assets 

1131 

1132 bNrm is the normalized resources without this period's labor income 

1133 

1134 cNrm is the normalized consumption 

1135 

1136 mNrm is the normalized market resources 

1137 

1138 pLvl is the permanent income level 

1139 

1140 who_dies is the array of which agents died 

1141 aNrmInitMean: float 

1142 Mean of Log initial Normalized Assets. 

1143 aNrmInitStd: float 

1144 Std of Log initial Normalized Assets. 

1145 pLvlInitMean: float 

1146 Mean of Log initial permanent income. 

1147 pLvlInitStd: float 

1148 Std of Log initial permanent income. 

1149 PermGroFacAgg: float 

1150 Aggregate permanent income growth factor (The portion of PermGroFac attributable to aggregate productivity growth). 

1151 PerfMITShk: boolean 

1152 Do Perfect Foresight MIT Shock (Forces Newborns to follow solution path of the agent they replaced if True). 

1153 NewbornTransShk: boolean 

1154 Whether Newborns have transitory shock. 

1155 

1156 Attributes 

1157 ---------- 

1158 solution: list[Consumer solution object] 

1159 Created by the :func:`.solve` method. Finite horizon models create a list with T_cycle+1 elements, for each period in the solution. 

1160 Infinite horizon solutions return a list with T_cycle elements for each period in the cycle. 

1161 

1162 Visit :class:`HARK.ConsumptionSaving.ConsPortfolioModel.PortfolioSolution` for more information about the solution. 

1163 

1164 history: Dict[Array] 

1165 Created by running the :func:`.simulate()` method. 

1166 Contains the variables in track_vars. Each item in the dictionary is an array with the shape (T_sim,AgentCount). 

1167 Visit :class:`HARK.core.AgentType.simulate` for more information. 

1168 """ 

1169 

1170 IncShkDstn_default = PortfolioConsumerType_IncShkDstn_default 

1171 aXtraGrid_default = PortfolioConsumerType_aXtraGrid_default 

1172 ShareGrid_default = PortfolioConsumerType_ShareGrid_default 

1173 RiskyDstn_default = PortfolioConsumerType_RiskyDstn_default 

1174 solving_default = PortfolioConsumerType_solving_default 

1175 simulation_default = PortfolioConsumerType_simulation_default 

1176 

1177 default_ = { 

1178 "params": PortfolioConsumerType_default, 

1179 "solver": solve_one_period_ConsPortfolio, 

1180 "model": "ConsPortfolio.yaml", 

1181 } 

1182 

1183 time_inv_ = deepcopy(RiskyAssetConsumerType.time_inv_) 

1184 time_inv_ = time_inv_ + ["DiscreteShareBool"] 

1185 

1186 def initialize_sim(self): 

1187 """ 

1188 Initialize the state of simulation attributes. Simply calls the same method 

1189 for IndShockConsumerType, then sets the type of AdjustNow to bool. 

1190 

1191 Parameters 

1192 ---------- 

1193 None 

1194 

1195 Returns 

1196 ------- 

1197 None 

1198 """ 

1199 # these need to be set because "post states", 

1200 # but are a control variable and shock, respectively 

1201 self.controls["Share"] = np.zeros(self.AgentCount) 

1202 RiskyAssetConsumerType.initialize_sim(self) 

1203 

1204 def sim_birth(self, which_agents): 

1205 """ 

1206 Create new agents to replace ones who have recently died; takes draws of 

1207 initial aNrm and pLvl, as in ConsIndShockModel, then sets Share and Adjust 

1208 to zero as initial values. 

1209 Parameters 

1210 ---------- 

1211 which_agents : np.array 

1212 Boolean array of size AgentCount indicating which agents should be "born". 

1213 

1214 Returns 

1215 ------- 

1216 None 

1217 """ 

1218 IndShockConsumerType.sim_birth(self, which_agents) 

1219 

1220 self.controls["Share"][which_agents] = 0.0 

1221 # here a shock is being used as a 'post state' 

1222 self.shocks["Adjust"][which_agents] = False 

1223 

1224 def get_controls(self): 

1225 """ 

1226 Calculates consumption cNrmNow and risky portfolio share ShareNow using 

1227 the policy functions in the attribute solution. These are stored as attributes. 

1228 

1229 Parameters 

1230 ---------- 

1231 None 

1232 

1233 Returns 

1234 ------- 

1235 None 

1236 """ 

1237 cNrmNow = np.zeros(self.AgentCount) + np.nan 

1238 ShareNow = np.zeros(self.AgentCount) + np.nan 

1239 

1240 # Loop over each period of the cycle, getting controls separately depending on "age" 

1241 for t in range(self.T_cycle): 

1242 these = t == self.t_cycle 

1243 

1244 # Get controls for agents who *can* adjust their portfolio share 

1245 those = np.logical_and(these, self.shocks["Adjust"]) 

1246 cNrmNow[those] = self.solution[t].cFuncAdj(self.state_now["mNrm"][those]) 

1247 ShareNow[those] = self.solution[t].ShareFuncAdj( 

1248 self.state_now["mNrm"][those] 

1249 ) 

1250 

1251 # Get controls for agents who *can't* adjust their portfolio share 

1252 those = np.logical_and(these, np.logical_not(self.shocks["Adjust"])) 

1253 cNrmNow[those] = self.solution[t].cFuncFxd( 

1254 self.state_now["mNrm"][those], self.controls["Share"][those] 

1255 ) 

1256 ShareNow[those] = self.solution[t].ShareFuncFxd( 

1257 self.state_now["mNrm"][those], self.controls["Share"][those] 

1258 ) # this just returns same share as before 

1259 

1260 # Store controls as attributes of self 

1261 self.controls["cNrm"] = cNrmNow 

1262 self.controls["Share"] = ShareNow