Coverage for HARK/Calibration/Assets/AssetProcesses.py: 82%
45 statements
« prev ^ index » next coverage.py v7.11.0, created at 2025-11-02 05:14 +0000
« prev ^ index » next coverage.py v7.11.0, created at 2025-11-02 05:14 +0000
1"""
2This file contains tools for creating risky asset return distributions, for use
3as inputs to several consumption-saving model solvers.
4"""
6import numpy as np
7from scipy.optimize import minimize_scalar
8from HARK.distributions import (
9 combine_indep_dstns,
10 DiscreteDistributionLabeled,
11 IndexDistribution,
12 Lognormal,
13)
16def make_lognormal_RiskyDstn(T_cycle, RiskyAvg, RiskyStd, RiskyCount, RNG):
17 r"""
18 Creates a discrete approximation of lognormal risky asset returns, either
19 as a single distribution or as a lifecycle sequence.
21 .. math::
22 \begin{align}
23 \phi_t &\sim \exp(\mathcal{N}(\textbf{RiskyStd}_{t}^2)) \\
24 \mathbb{E}_{t} \left[ \phi_t \right] &= \textbf{RiskyAvg}_{t}\\
25 \end{align}
27 Parameters
28 ----------
29 T_cycle : int
30 Number of non-terminal periods in this agent's cycle.
31 RiskyAvg : float or [float]
32 Mean return factor of risky asset. If a single number, it is used for all
33 periods. If it is a list, then it represents lifecycle returns (or
34 perceptions thereof).
35 RiskyStd : float or [float]
36 Standard deviation of log returns of the risky asset. Allows the same
37 options as RiskyAvg.
38 RiskyCount : int
39 Number of equiprobable discrete nodes in the risky return distribution.
40 RNG : RandomState
41 Internal random number generator for the AgentType instance, used to
42 generate random seeds.
44 Returns
45 -------
46 RiskyDstn : DiscreteDistribution or [DiscreteDistribution]
47 Discretized approximation to lognormal asset returns.
48 """
49 # Determine whether this instance has time-varying risk perceptions
50 if (
51 (type(RiskyAvg) is list)
52 and (type(RiskyStd) is list)
53 and (len(RiskyAvg) == len(RiskyStd))
54 and (len(RiskyAvg) == T_cycle)
55 ):
56 time_varying_RiskyDstn = True
57 elif (type(RiskyStd) is list) or (type(RiskyAvg) is list):
58 raise AttributeError(
59 "If RiskyAvg is time-varying, then RiskyStd must be as well, and they must both have length of T_cycle!"
60 )
61 else:
62 time_varying_RiskyDstn = False
64 # Generate a discrete approximation to the risky return distribution
65 # if its parameters are time-varying
66 if time_varying_RiskyDstn:
67 RiskyDstn = IndexDistribution(
68 Lognormal,
69 {"mean": RiskyAvg, "sigma": RiskyStd},
70 seed=RNG.integers(0, 2**31 - 1),
71 ).discretize(RiskyCount, method="equiprobable")
73 # Generate a discrete approximation to the risky return distribution if
74 # its parameters are constant
75 else:
76 RiskyDstn = Lognormal(mean=RiskyAvg, sigma=RiskyStd).discretize(
77 RiskyCount, method="equiprobable"
78 )
80 return RiskyDstn
83def combine_IncShkDstn_and_RiskyDstn(T_cycle, RiskyDstn, IncShkDstn):
84 """
85 Combine the income shock distribution (over PermShk and TranShk) with the
86 risky return distribution (RiskyDstn) to make a new object called ShockDstn.
88 Parameters
89 ----------
90 T_cycle : int
91 Number of non-terminal periods in this agent's cycle.
92 RiskyDstn : DiscreteDistribution or [DiscreteDistribution]
93 Discretized approximation to lognormal asset returns.
94 IncShkDstn : [Distribution]
95 A discrete approximation to the income process between each period.
97 Returns
98 -------
99 ShockDstn : IndexDistribution
100 A combined trivariate discrete distribution of permanent shocks, transitory
101 shocks, and risky returns. Has one element per period of the agent's cycle.
102 """
103 # Create placeholder distributions
104 try:
105 dstn_list = [
106 combine_indep_dstns(IncShkDstn[t], RiskyDstn[t]) for t in range(T_cycle)
107 ]
108 except:
109 dstn_list = [
110 combine_indep_dstns(IncShkDstn[t], RiskyDstn) for t in range(T_cycle)
111 ]
113 # Names of the variables (hedging for the unlikely case that in
114 # some index of IncShkDstn variables are in a switched order)
115 names_list = [
116 list(IncShkDstn[t].variables.keys()) + ["Risky"] for t in range(T_cycle)
117 ]
119 conditional = {
120 "pmv": [x.pmv for x in dstn_list],
121 "atoms": [x.atoms for x in dstn_list],
122 "var_names": names_list,
123 }
125 # Now create the actual distribution using the index and labeled class
126 ShockDstn = IndexDistribution(
127 engine=DiscreteDistributionLabeled,
128 conditional=conditional,
129 )
130 return ShockDstn
133def calc_ShareLimit_for_CRRA(T_cycle, RiskyDstn, CRRA, Rfree):
134 """
135 Calculates the lower bound on the risky asset share as market resources go
136 to infinity, given that utility is CRRA.
138 Parameters
139 ----------
140 T_cycle : int
141 Number of non-terminal periods in this agent's cycle.
142 RiskyDstn : DiscreteDistribution or [DiscreteDistribution]
143 Discretized approximation to lognormal asset returns.
144 CRRA : float
145 Coefficient of relative risk aversion.
146 Rfree : float
147 Return factor on the risk-free asset.
149 Returns
150 -------
151 ShareLimit : float or [float]
152 Lower bound on risky asset share. Can be a single number or a lifecycle sequence.
153 """
154 RiskyDstn_is_time_varying = hasattr(RiskyDstn, "__getitem__")
155 Rfree_is_time_varying = type(Rfree) is list
157 # If the risky share lower bound is time varying...
158 if RiskyDstn_is_time_varying or Rfree_is_time_varying:
159 ShareLimit = []
160 for t in range(T_cycle):
161 if RiskyDstn_is_time_varying:
162 RiskyDstn_t = RiskyDstn[t]
163 else:
164 RiskyDstn_t = RiskyDstn
165 if Rfree_is_time_varying:
166 Rfree_t = Rfree[t]
167 else:
168 Rfree_t = Rfree
170 def temp_f(s):
171 return -((1.0 - CRRA) ** -1) * np.dot(
172 (Rfree_t + s * (RiskyDstn_t.atoms[0] - Rfree_t)) ** (1.0 - CRRA),
173 RiskyDstn_t.pmv,
174 )
176 SharePF = minimize_scalar(temp_f, bounds=(0.0, 1.0), method="bounded").x
177 ShareLimit.append(SharePF)
179 # If the risky share lower bound is not time-varying...
180 else:
182 def temp_f(s):
183 return -((1.0 - CRRA) ** -1) * np.dot(
184 (Rfree + s * (RiskyDstn.atoms[0] - Rfree)) ** (1.0 - CRRA),
185 RiskyDstn.pmv,
186 )
188 SharePF = minimize_scalar(
189 temp_f, bracket=(0.0, 1.0), method="golden", tol=1e-10
190 ).x
191 if type(SharePF) is np.array:
192 SharePF = SharePF[0]
193 ShareLimit = SharePF
195 return ShareLimit