def getAdjust(self): ''' Sets the attribute AdjustNow as a boolean array of size AgentCount, indicating whether each agent is able to adjust their risky portfolio share this period. Uses the attribute AdjustPrb to draw from a Bernoulli distribution. Parameters ---------- None Returns ------- None ''' self.AdjustNow = Bernoulli(self.AdjustPrb).draw(self.AgentCount, seed=self.RNG.randint(0, 2**31-1))
def noticeStimulus(self): ''' Give each agent the opportunity to notice the future stimulus payment and mentally account for it in their market resources. ''' if self.T_til_check > 0: self.T_til_check -= 1 updaters = Bernoulli(p=self.UpdatePrb).draw(self.AgentCount, seed=self.RNG.randint(0,2**31-1)) if self.T_til_check == 0: updaters = np.ones(self.AgentCount, dtype=bool) self.mNrmNow[updaters] += self.Stim_unnoticed[updaters]*self.StimLvl[updaters]/self.pLvlNow[updaters]*self.Rfree[0]**(-self.T_til_check) self.Stim_unnoticed[updaters] = False
def getShocks(self): ''' Determine which agents switch from employment to unemployment. All unemployed agents remain unemployed until death. Parameters ---------- None Returns ------- None ''' employed = self.eStateNow == 1.0 N = int(np.sum(employed)) newly_unemployed = Bernoulli(self.UnempPrb).draw(N, seed=self.RNG.randint(0,2**31-1)) self.eStateNow[employed] = 1.0 - newly_unemployed
def get_shocks(self): """ Determine which agents switch from employment to unemployment. All unemployed agents remain unemployed until death. Parameters ---------- None Returns ------- None """ employed = self.shocks["eStateNow"] == 1.0 N = int(np.sum(employed)) newly_unemployed = Bernoulli(self.UnempPrb, seed=self.RNG.randint(0, 2**31 - 1)).draw(N) self.shocks["eStateNow"][employed] = 1.0 - newly_unemployed
class PortfolioConsumerType(IndShockConsumerType): """ A consumer type with a portfolio choice. This agent type has log-normal return factors. Their problem is defined by a coefficient of relative risk aversion, intertemporal discount factor, risk-free interest factor, and time sequences of permanent income growth rate, survival probability, and permanent and transitory income shock standard deviations (in logs). The agent may also invest in a risky asset, which has a higher average return than the risk-free asset. He *might* have age-varying beliefs about the risky-return; if he does, then "true" values of the risky asset's return distribution must also be specified. """ poststate_vars_ = ['aNrmNow', 'pLvlNow', 'ShareNow', 'AdjustNow'] time_inv_ = deepcopy(IndShockConsumerType.time_inv_) time_inv_ = time_inv_ + ['AdjustPrb', 'DiscreteShareBool'] def __init__(self, cycles=1, verbose=False, quiet=False, **kwds): params = init_portfolio.copy() params.update(kwds) kwds = params # Initialize a basic consumer type IndShockConsumerType.__init__( self, cycles=cycles, verbose=verbose, quiet=quiet, **kwds ) # Set the solver for the portfolio model, and update various constructed attributes self.solveOnePeriod = solveConsPortfolio self.update() def preSolve(self): AgentType.preSolve(self) self.updateSolutionTerminal() def update(self): IndShockConsumerType.update(self) self.updateRiskyDstn() self.updateShockDstn() self.updateShareGrid() self.updateShareLimit() def updateSolutionTerminal(self): ''' Solves the terminal period of the portfolio choice problem. The solution is trivial, as usual: consume all market resources, and put nothing in the risky asset (because you have nothing anyway). Parameters ---------- None Returns ------- None ''' # Consume all market resources: c_T = m_T cFuncAdj_terminal = IdentityFunction() cFuncFxd_terminal = IdentityFunction(i_dim=0, n_dims=2) # Risky share is irrelevant-- no end-of-period assets; set to zero ShareFuncAdj_terminal = ConstantFunction(0.) ShareFuncFxd_terminal = IdentityFunction(i_dim=1, n_dims=2) # Value function is simply utility from consuming market resources vFuncAdj_terminal = ValueFunc(cFuncAdj_terminal, self.CRRA) vFuncFxd_terminal = ValueFunc2D(cFuncFxd_terminal, self.CRRA) # Marginal value of market resources is marg utility at the consumption function vPfuncAdj_terminal = MargValueFunc(cFuncAdj_terminal, self.CRRA) dvdmFuncFxd_terminal = MargValueFunc2D(cFuncFxd_terminal, self.CRRA) dvdsFuncFxd_terminal = ConstantFunction(0.) # No future, no marg value of Share # Construct the terminal period solution self.solution_terminal = PortfolioSolution( cFuncAdj=cFuncAdj_terminal, ShareFuncAdj=ShareFuncAdj_terminal, vFuncAdj=vFuncAdj_terminal, vPfuncAdj=vPfuncAdj_terminal, cFuncFxd=cFuncFxd_terminal, ShareFuncFxd=ShareFuncFxd_terminal, vFuncFxd=vFuncFxd_terminal, dvdmFuncFxd=dvdmFuncFxd_terminal, dvdsFuncFxd=dvdsFuncFxd_terminal ) def updateRiskyDstn(self): ''' Creates the attributes RiskyDstn from the primitive attributes RiskyAvg, RiskyStd, and RiskyCount, approximating the (perceived) distribution of returns in each period of the cycle. Parameters ---------- None Returns ------- None ''' # Determine whether this instance has time-varying risk perceptions if (type(self.RiskyAvg) is list) and (type(self.RiskyStd) is list) and (len(self.RiskyAvg) == len(self.RiskyStd)) and (len(self.RiskyAvg) == self.T_cycle): self.addToTimeVary('RiskyAvg','RiskyStd') elif (type(self.RiskyStd) is list) or (type(self.RiskyAvg) is list): raise AttributeError('If RiskyAvg is time-varying, then RiskyStd must be as well, and they must both have length of T_cycle!') else: self.addToTimeInv('RiskyAvg','RiskyStd') # Generate a discrete approximation to the risky return distribution if the # agent has age-varying beliefs about the risky asset if 'RiskyAvg' in self.time_vary: RiskyDstn = [] for t in range(self.T_cycle): RiskyAvgSqrd = self.RiskyAvg[t] ** 2 RiskyVar = self.RiskyStd[t] ** 2 mu = np.log(self.RiskyAvg[t] / (np.sqrt(1. + RiskyVar / RiskyAvgSqrd))) sigma = np.sqrt(np.log(1. + RiskyVar / RiskyAvgSqrd)) RiskyDstn.append(approxLognormal(self.RiskyCount, mu=mu, sigma=sigma)) self.RiskyDstn = RiskyDstn self.addToTimeVary('RiskyDstn') # Generate a discrete approximation to the risky return distribution if the # agent does *not* have age-varying beliefs about the risky asset (base case) else: RiskyAvgSqrd = self.RiskyAvg ** 2 RiskyVar = self.RiskyStd ** 2 mu = np.log(self.RiskyAvg / (np.sqrt(1. + RiskyVar / RiskyAvgSqrd))) sigma = np.sqrt(np.log(1. + RiskyVar / RiskyAvgSqrd)) self.RiskyDstn = approxLognormal(self.RiskyCount, mu=mu, sigma=sigma) self.addToTimeInv('RiskyDstn') def updateShockDstn(self): ''' Combine the income shock distribution (over PermShk and TranShk) with the risky return distribution (RiskyDstn) to make a new attribute called ShockDstn. Parameters ---------- None Returns ------- None ''' if 'RiskyDstn' in self.time_vary: self.ShockDstn = [combineIndepDstns(self.IncomeDstn[t], self.RiskyDstn[t]) for t in range(self.T_cycle)] else: self.ShockDstn = [combineIndepDstns(self.IncomeDstn[t], self.RiskyDstn) for t in range(self.T_cycle)] self.addToTimeVary('ShockDstn') # Mark whether the risky returns and income shocks are independent (they are) self.IndepDstnBool = True self.addToTimeInv('IndepDstnBool') def updateShareGrid(self): ''' Creates the attribute ShareGrid as an evenly spaced grid on [0.,1.], using the primitive parameter ShareCount. Parameters ---------- None Returns ------- None ''' self.ShareGrid = np.linspace(0.,1.,self.ShareCount) self.addToTimeInv('ShareGrid') def updateShareLimit(self): ''' Creates the attribute ShareLimit, representing the limiting lower bound of risky portfolio share as mNrm goes to infinity. Parameters ---------- None Returns ------- None ''' if 'RiskyDstn' in self.time_vary: self.ShareLimit = [] for t in range(self.T_cycle): RiskyDstn = self.RiskyDstn[t] temp_f = lambda s : -((1.-self.CRRA)**-1)*np.dot((self.Rfree + s*(RiskyDstn.X-self.Rfree))**(1.-self.CRRA), RiskyDstn.pmf) SharePF = minimize_scalar(temp_f, bounds=(0.0, 1.0), method='bounded').x self.ShareLimit.append(SharePF) self.addToTimeVary('ShareLimit') else: RiskyDstn = self.RiskyDstn temp_f = lambda s : -((1.-self.CRRA)**-1)*np.dot((self.Rfree + s*(RiskyDstn.X-self.Rfree))**(1.-self.CRRA), RiskyDstn.pmf) SharePF = minimize_scalar(temp_f, bounds=(0.0, 1.0), method='bounded').x self.ShareLimit = SharePF self.addToTimeInv('ShareLimit') def getRisky(self): ''' Sets the attribute RiskyNow as a single draw from a lognormal distribution. Uses the attributes RiskyAvgTrue and RiskyStdTrue if RiskyAvg is time-varying, else just uses the single values from RiskyAvg and RiskyStd. Parameters ---------- None Returns ------- None ''' if 'RiskyDstn' in self.time_vary: RiskyAvg = self.RiskyAvgTrue RiskyStd = self.RiskyStdTrue else: RiskyAvg = self.RiskyAvg RiskyStd = self.RiskyStd RiskyAvgSqrd = RiskyAvg**2 RiskyVar = RiskyStd**2 mu = np.log(RiskyAvg / (np.sqrt(1. + RiskyVar / RiskyAvgSqrd))) sigma = np.sqrt(np.log(1. + RiskyVar / RiskyAvgSqrd)) self.RiskyNow = Lognormal(mu, sigma).draw(1, seed=self.RNG.randint(0, 2**31-1)) def getAdjust(self): ''' Sets the attribute AdjustNow as a boolean array of size AgentCount, indicating whether each agent is able to adjust their risky portfolio share this period. Uses the attribute AdjustPrb to draw from a Bernoulli distribution. Parameters ---------- None Returns ------- None ''' self.AdjustNow = Bernoulli(self.AdjustPrb).draw(self.AgentCount, seed=self.RNG.randint(0, 2**31-1)) def getRfree(self): ''' Calculates realized return factor for each agent, using the attributes Rfree, RiskyNow, and ShareNow. This method is a bit of a misnomer, as the return factor is not riskless, but would more accurately be labeled as Rport. However, this method makes the portfolio model compatible with its parent class. Parameters ---------- None Returns ------- Rport : np.array Array of size AgentCount with each simulated agent's realized portfolio return factor. Will be used by getStates() to calculate mNrmNow, where it will be mislabeled as "Rfree". ''' Rport = self.ShareNow*self.RiskyNow + (1.-self.ShareNow)*self.Rfree self.RportNow = Rport return Rport def initializeSim(self): ''' Initialize the state of simulation attributes. Simply calls the same method for IndShockConsumerType, then sets the type of AdjustNow to bool. Parameters ---------- None Returns ------- None ''' IndShockConsumerType.initializeSim(self) self.AdjustNow = self.AdjustNow.astype(bool) def simBirth(self,which_agents): ''' Create new agents to replace ones who have recently died; takes draws of initial aNrm and pLvl, as in ConsIndShockModel, then sets Share and Adjust to zero as initial values. Parameters ---------- which_agents : np.array Boolean array of size AgentCount indicating which agents should be "born". Returns ------- None ''' IndShockConsumerType.simBirth(self,which_agents) self.ShareNow[which_agents] = 0. self.AdjustNow[which_agents] = False def getShocks(self): ''' Draw idiosyncratic income shocks, just as for IndShockConsumerType, then draw a single common value for the risky asset return. Also draws whether each agent is able to update their risky asset share this period. Parameters ---------- None Returns ------- None ''' IndShockConsumerType.getShocks(self) self.getRisky() self.getAdjust() def getControls(self): ''' Calculates consumption cNrmNow and risky portfolio share ShareNow using the policy functions in the attribute solution. These are stored as attributes. Parameters ---------- None Returns ------- None ''' cNrmNow = np.zeros(self.AgentCount) + np.nan ShareNow = np.zeros(self.AgentCount) + np.nan # Loop over each period of the cycle, getting controls separately depending on "age" for t in range(self.T_cycle): these = t == self.t_cycle # Get controls for agents who *can* adjust their portfolio share those = np.logical_and(these, self.AdjustNow) cNrmNow[those] = self.solution[t].cFuncAdj(self.mNrmNow[those]) ShareNow[those] = self.solution[t].ShareFuncAdj(self.mNrmNow[those]) # Get Controls for agents who *can't* adjust their portfolio share those = np.logical_and(these, np.logical_not(self.AdjustNow)) cNrmNow[those] = self.solution[t].cFuncFxd(self.mNrmNow[those], self.ShareNow[those]) ShareNow[those] = self.solution[t].ShareFuncFxd(self.mNrmNow[those], self.ShareNow[those]) # Store controls as attributes of self self.cNrmNow = cNrmNow self.ShareNow = ShareNow
def test_Bernoulli(self): self.assertEqual(Bernoulli().draw(1)[0], False)