from otree.api import * c = cu doc = '' class C(BaseConstants): NAME_IN_URL = 'EscapingRecessionsGame' PLAYERS_PER_GROUP = None NUM_ROUNDS = 15 PHASE2START = 4 PHASE3START = 11 PITARGET1 = 5 PITARGET2 = 15 PHIPI = 2 GAMMA = 0.3 ALPHA = 0.7 BETA = 1 G = 0 LBAR = 1 D = 0.35 DSHOCK = 0.315 PRICE_SS = 100 NUMPLAYERS = 2 Y_F = 1 DEBT_SS = 36.75 ELB = 0 NOMINT_SS = 0.131 RANGE_SCALAR = 2 POINT_SCALAR = 2 INSTRUCTIONS_TEMPLATE = 'EscapingRecessionsGame/instructions.html' class Subsession(BaseSubsession): pass def group_by_arrival_time_method(subsession: Subsession, waiting_players): if len(waiting_players) >= C.NUMPLAYERS: return waiting_players[:C.NUMPLAYERS] class Group(BaseGroup): median_price_nowcast = models.FloatField() median_price_forecast = models.FloatField() e_nomint_usingmedian = models.FloatField() realized_price = models.FloatField() Total_Consumption_MiddleAged = models.FloatField() Total_Consumption_Young = models.FloatField() Total_Consumption_Old = models.FloatField() Total_Consumption_All = models.FloatField() Actual_Price = models.FloatField() e_net_income = models.FloatField() e_entering_debt = models.FloatField() median_inflation_nowcast = models.FloatField() median_inflation_forecast = models.FloatField() realized_inflation = models.FloatField() rf = models.FloatField() istar = models.FloatField() e_wage = models.FloatField() e_labor = models.FloatField() e_profit = models.FloatField() wage = models.FloatField() realized_labor_provided = models.FloatField() realized_firm_profit = models.FloatField() realized_debt = models.FloatField() realized_wage = models.FloatField() realized_nomint = models.FloatField() realized_output = models.FloatField() wage_flex = models.FloatField() wage_rigid = models.FloatField() def stage1gate(group: Group): import numpy as np round=group.round_number players = group.get_players() """ #turning inflation expectations into price expectations if round == 1: for p in players: p.e_price_today = C.PRICE_SS*(1 + (p.e_pi_today/100)) p.e_price_tomorrow = p.e_price_today*(1 + (p.e_pi_tomorrow/100)) else: for p in players: p.e_price_today = group.in_round(round-1).realized_price*(1 + (p.e_pi_today/100)) p.e_price_tomorrow = p.e_price_today*(1 + (p.e_pi_tomorrow/100)) """ #Getting median price forecasts group.median_price_nowcast = np.median([p.field_maybe_none('e_price_today') for p in players if p.field_maybe_none('e_price_today') is not None]) group.median_price_forecast = np.median([p.field_maybe_none('e_price_tomorrow') for p in players if p.field_maybe_none('e_price_tomorrow') is not None]) #Setting expected inflation: if round==1: group.median_inflation_nowcast = ((group.median_price_nowcast - C.PRICE_SS)/C.PRICE_SS)*100 group.median_inflation_forecast = ((group.median_price_forecast - group.median_price_nowcast)/group.median_price_nowcast)*100 else: group.median_inflation_nowcast = ((group.median_price_nowcast - group.in_round(round-1).realized_price)/group.in_round(round-1).realized_price)*100 group.median_inflation_forecast = ((group.median_price_forecast - group.median_price_nowcast)/group.median_price_nowcast)*100 #Defining rf and istar for entering debt calculations if round <= C.PHASE2START-1: group.rf=((1 + C.BETA) / C.BETA) * (1 + C.G) * (C.D / (C.Y_F - C.D)) - 1 ##calculates the per-period group.istar=((1 + group.rf)*(1+(C.PITARGET1/100)) - 1) elif round==C.PHASE2START: group.rf=((((1 + C.BETA) / C.BETA) * (1 + C.G) * C.DSHOCK )/ (C.Y_F - C.D)) - 1 ##calculates the per-period group.istar=((1 + group.rf)*(1+(C.PITARGET1/100)) - 1) elif round > C.PHASE2START and round < C.PHASE3START: group.rf=((1 + C.BETA) / C.BETA) * (1 + C.G) * (C.DSHOCK / (C.Y_F - C.DSHOCK)) - 1 ##calculates the per-period group.istar=((1 + group.rf)*(1+(C.PITARGET1/100)) - 1) else: group.rf=((1 + C.BETA) / C.BETA) * (1 + C.G) * (C.DSHOCK / (C.Y_F - C.DSHOCK)) - 1 ##calculates the per-period group.istar=((1 + group.rf)*(1+(C.PITARGET2/100)) - 1) ##Expected net income calculation ##First period values using SS values from previous period if round==1: if (group.median_price_nowcast < C.PRICE_SS) : ##If the subjects expect deflation group.e_wage = C.GAMMA*(C.ALPHA*C.PRICE_SS) + (1 - C.GAMMA)*(group.median_price_nowcast * C.ALPHA) group.e_labor = (group.e_wage / (C.ALPHA * group.median_price_nowcast)) ** (1 / (C.ALPHA - 1)) group.e_profit = group.median_price_nowcast * group.e_labor ** C.ALPHA - group.e_wage * group.e_labor group.e_net_income = group.e_wage * group.e_labor + group.e_profit - C.DEBT_SS else: ##else if the subjects expect inflation that is >=0 group.e_net_income = group.median_price_nowcast - C.DEBT_SS ##Need to ensure that the value stored as realized debt in stage2gate includes interest on borrowing else: ##This if condition means subjects expect prices today to be lower than yesterday...i.e. expect deflation if group.median_price_nowcast < group.in_round(round-1).realized_price: group.e_wage = C.GAMMA * group.in_round(round-1).realized_wage + (1 - C.GAMMA) * (group.median_price_nowcast * C.ALPHA) group.e_labor = (group.e_wage / (C.ALPHA * group.median_price_nowcast)) ** (1 / (C.ALPHA - 1)) group.e_profit = group.median_price_nowcast * group.e_labor ** C.ALPHA- group.e_wage * group.e_labor group.e_net_income = (group.e_wage * group.e_labor + group.e_profit - group.in_round(round-1).realized_debt) else:##else...subjects expect inflation group.e_net_income = group.median_price_nowcast - group.in_round(round-1).realized_debt ##Expected nominal interest rate based on median inflation expectations ##This assumes we are increasing the inflation target. If instead we are raising rates, turn this off and turn on negrates option below if round < C.PHASE3START: group.e_nomint_usingmedian = max(1, (1 + group.istar) * ((1+(group.median_inflation_nowcast/100)) / (1+(C.PITARGET1/100))) ** C.PHIPI)-1 else: group.e_nomint_usingmedian = max(1, (1 + group.istar) * ((1+(group.median_inflation_nowcast/100)) / (1+(C.PITARGET2/100))) ** C.PHIPI)-1 """ ##The only difference with the code above is that phase three does not change the inflation target ##but instead changes from a ZLB of 1 to an ELB of 1+ ELB where, for example, an ELB of -.06 creates max(-.06, market clearing rate) ##Notice that I shift the ..-1 to only apply to the second term in the else condition below so that it is more intuitive for us when setting the ELB (for example, we set -.06 instead of .94) if round <= C.PHASE3START: group.e_nomint_usingmedian = max(1, (1 + group.istar) * (group.median_inflation_nowcast / C.PITARGET1) ** C.PHIPI)-1 else: group.e_nomint_usingmedian = max(C.ELB, ((1 + group.istar) * (group.median_inflation_nowcast / C.PITARGET1) ** C.PHIPI)-1)) """ def stage2gate(group: Group): import numpy as np round=group.round_number players = group.get_players() active = sum([1 for p in players if p.IsDropout == False]) ## This gate determines prices, clears markets, etc. ##This code has to account for the change in D (i.e. the shock!) ##before the shock occurs, young households borrow a proportion D of expected middle-aged income if round < C.PHASE2START: #Young borrow D of expected future income, adjusting for the interest they expect to pay #This code accounts for number of young households, which we assume is the same as middle-aged households group.Total_Consumption_Young = active*((C.D*group.median_price_forecast)/( 1+(group.e_nomint_usingmedian) )) else: group.Total_Consumption_Young =active*((C.DSHOCK*group.median_price_forecast)/( 1+(group.e_nomint_usingmedian) )) ##Here, we sum the consumption decisions of all players in a group group.Total_Consumption_MiddleAged = sum([p.consumption_dollars for p in players if p.IsDropout == False]) ##Consumption dollars ought to equal a sum of everything saved by players yesterday ##This code also has to account for initialization in early periods #This assumes old in the first period of play exactly match steady-state equilibrium conditions if round == 1: group.Total_Consumption_Old = active * C.D * group.median_price_nowcast ### starting in round 2, consumption_old is impact by participant decisions. This should depend on what participants did yesterday and possible what happened two periods ago (i.e. before the participants take over) elif round==2: if group.in_round(round-1).realized_price >= C.PRICE_SS: group.Total_Consumption_Old = sum([p.in_round(round-1).realized_savings_dollars for p in players if p.IsDropout == False]) else: for p in players: p.in_round(round-1).realized_savings_dollars =(( (group.in_round(round-1).realized_wage * group.in_round(round-1).realized_labor_provided) + group.in_round(round-1).realized_firm_profit) - p.in_round(round-1).consumption_dollars - ((group.in_round(round-1).Total_Consumption_Young/active) * (1 + C.NOMINT_SS))) * (1 + group.in_round(round-1). realized_nomint) group.Total_Consumption_Old = sum([p.in_round(round-1).realized_savings_dollars for p in players if p.IsDropout==False]) else: if group.in_round(round-1).realized_price >= group.in_round(round-2).realized_price: group.Total_Consumption_Old = sum([p.in_round(round-1).realized_savings_dollars for p in players if p.IsDropout==False]) else: for p in players: p.in_round(round-1).realized_savings_dollars =( (group.in_round(round-1).realized_wage * group.in_round(round-1).realized_labor_provided) + group.in_round(round-1).realized_firm_profit) - p.in_round(round-1).consumption_dollars - ((group.in_round(round-2).Total_Consumption_Young/active) * (1 + group.in_round(round-2).realized_nomint)) * (1 + group.in_round(round-1).realized_nomint) group.Total_Consumption_Old = sum([p.in_round(round-1).realized_savings_dollars for p in players if p.IsDropout==False]) ### Here is the pricing alogorithm epsilon = .00000001 ##This determines how close we must be to zero when solving for prices numerically #Total consumption spending in the current period group.Total_Consumption_All= (group.Total_Consumption_Young + group.Total_Consumption_MiddleAged + group.Total_Consumption_Old)/active ##Exactly matches theory in the appendix D = (1/C.ALPHA)*(group.Total_Consumption_All**((1 - C.ALPHA)/C.ALPHA)) b=(1-C.ALPHA)/C.ALPHA Lbarpwr=C.LBAR**(C.ALPHA-1) A=D*(1-C.GAMMA)*C.ALPHA*Lbarpwr ##If its the first period t=1, then wage_t=0 == C.ALPHA*C.PRICE_SS ##else we use the market clearing wage from the previous period if round==1: B=D*C.GAMMA*(C.ALPHA*C.PRICE_SS) else: B=D*C.GAMMA*group.in_round(round-1).realized_wage ###Starting the N-R method. If period t=1 then we take the equilibrium price from t=0=C.PRICE_SS as our initial guess for prices if round==1: Pguess=C.PRICE_SS pguessb=Pguess**b P=Pguess*(pguessb-A)-B p=(b+1)*pguessb-A #else we use the market clearing price from the previous period else: Pguess=group.in_round(round-1).realized_price pguessb=Pguess**b P=Pguess*(pguessb-A)-B p=(b+1)*pguessb-A while(abs(P)>epsilon): Pguess = Pguess - P/p Pguess=Pguess.real pguessb=Pguess**b P=Pguess*(pguessb-A)-B p=(b+1)*pguessb-A ##The while loop has stopped, which means we have a temporary price that is within our tolerance of epsilon of the root of our implicit price equation group.realized_price=Pguess ## Let's see if this price leads to impossible levels of production PriceCheck=group.Total_Consumption_All/group.realized_price ##If so, the price is too low, let's use the other price rule if(PriceCheck>C.Y_F): group.realized_price=group.Total_Consumption_All/C.Y_F ##Now calculating wages ## Again need to distinguish between rounds 1 and other rounds if round == 1: Wflex=group.realized_price*C.ALPHA*(C.LBAR**(C.ALPHA-1)) group.wage_flex=Wflex Wrigid=C.GAMMA*(C.ALPHA*C.PRICE_SS)+(1-C.GAMMA)*group.realized_price*C.ALPHA*(C.LBAR**(C.ALPHA-1)) group.wage_rigid=Wrigid group.realized_wage=max(Wflex,Wrigid) ##For all other rounds, we use the market-clearing wage from the previous period else: Wflex=group.realized_price*C.ALPHA*(C.LBAR**(C.ALPHA-1)) group.wage_flex=Wflex Wrigid=(C.GAMMA*(group.in_round(round-1).realized_wage))+(1-C.GAMMA)*(group.realized_price*C.ALPHA*(C.LBAR**(C.ALPHA-1))) group.wage_rigid=Wrigid group.realized_wage=max(Wflex,Wrigid) #labor supply group.realized_labor_provided=(group.realized_wage/(group.realized_price*C.ALPHA))**(1/(C.ALPHA-1)) ##Inflation Rates, and again distinguishing between period 1 and other periods # period t=1 then we set inflation as difference between price today and SS price from t=0=C.PRICE_SS if round==1: group.realized_inflation = ((group.realized_price/C.PRICE_SS)-1)*100 #else we use the market clearing prices from today and yesterday else: group.realized_inflation = ((group.realized_price/group.in_round(round-1).realized_price)-1)*100 ##Now determining the interest rate. Here, we will need to distinguish between phases and treatments ###Use this bloc of code for increasing the inflation target: if round < C.PHASE3START: zlb=1 notzlb=(1+group.istar)*((1+(group.realized_inflation/100))/(1+(C.PITARGET1/100)))**C.PHIPI group.realized_nomint = max(zlb,notzlb)-1 else: zlb=1 notzlb=(1+group.istar)*((1+(group.realized_inflation/100))/C.PITARGET2)**C.PHIPI group.realized_nomint = max(zlb,notzlb)-1 ##Use this block of code for allowing negative rates: """ if round < C.PHASE3START: zlb=1 notzlb=(1+group.istar)*((1+(group.realized_inflation/100))/C.PITARGET1)**C.PHIPI group.realized_nomint = max(zlb,notzlb)-1 else: notzlb=(1+group.istar)*((1+(group.realized_inflation/100))/C.PITARGET2)**C.PHIPI group.realized_nomint = max(C.ELB,notzlb-1) """ ##determing how much each player actually has saved for spending tomorrow for p in players: if round==1: p.realized_savings_dollars =(group.realized_price - C.DEBT_SS - p.consumption_dollars)*(1+group.realized_nomint) else: p.realized_savings_dollars =(group.realized_price - group.in_round(round-1).realized_debt - p.consumption_dollars)*(1+group.realized_nomint) #Debt for middle-aged in the next period is what the young spent today multiplied by the nominal interest rate group.realized_debt = (group.Total_Consumption_Young/active)*(1+group.realized_nomint) ##Calculating Output if round == 1: if group.realized_price >= C.PRICE_SS: group.realized_output= active else: group.realized_output= group.Total_Consumption_All/group.realized_price else: if group.realized_price >= group.in_round(round-1).realized_price: group.realized_output= active else: group.realized_output= group.Total_Consumption_All/group.realized_price ##Individual-level firm profit group.realized_firm_profit = group.realized_price*(group.realized_output/active) -group.realized_wage*group.realized_labor_provided ############################################################################################## ############################################################################################## ############################################################################################## #####Now Calculating Consumption Points for this period and forecasting scores for this period ############################################################################################## ############################################################################################## ############################################################################################## ###### Handling the first period seperately if round == 1: #looping through all players -- logic below distinguishes between active and dropouts for p in players: ##Forecasting score ##only need to account for nowcast error in period == 1 #If the player dropped out, we set null values if p.IsDropout == True: p.abs_NE = None p.score_NE = None p.nowcast_range_score = None p.forecast_range_score = None p.units_today_middle = None p.units_today_old = None #If the player is active, we assign real values else: p.abs_NE = abs(p.e_pi_today - group.realized_inflation) p.score_NE = (2**-(p.abs_NE))*C.POINT_SCALAR ##temp var to hold nowcast range that won't be stored in data output nowcast_range = (p.Uncertainty_Upper_N - p.Uncertainty_Lower_N) #If the nowcast range contains realized inflation, the subject earns this if p.Uncertainty_Upper_N+.01 >= group.realized_inflation >= p.Uncertainty_Lower_N-.01: p.nowcast_range_score = C.RANGE_SCALAR/(1+nowcast_range) #If realized inflation is outside the nowcast range, the subject earns nothing else: p.nowcast_range_score = 0 ##In period one, the forecast range score is nothing p.forecast_range_score = 0 #starting the running total of expectations points p.total_expectations_points = p.field_maybe_none('score_NE')+p.field_maybe_none('nowcast_range_score') ##Consumption score #Units consumed by the middle-aged household today p.units_today_middle = p.consumption_dollars/group.realized_price #Units consumed by the old household today p.units_today_old = 0 ##In period one, player only gets consumption points from the middle-aged household #only need to account for consumption today p.consumption_utility_middle = 5.01 + np.log(.0067 + p.units_today_middle) ##starting total consumption points p.total_consumption_points = p.consumption_utility_middle # only consumption points from the middle household ##Else if round > 1 else: for p in players: ##Forecasting score #if the player dropped out, we set null values if p.IsDropout == True: p.abs_NE = None p.score_NE = None p.abs_FE = None p.score_FE = None p.nowcast_range_score = None p.forecast_range_score = None p.units_today_middle = None p.units_today_old = None temp = None p.total_expectations_points = None ## if the player is active, we set actual values else: ###Setting nowcast point and range earnings### #nowcast point# p.abs_NE = abs(p.e_pi_today - group.realized_inflation) p.score_NE = (2**-(p.abs_NE))*C.POINT_SCALAR #nowcast range# #temp var not stored in data output nowcast_range = p.Uncertainty_Upper_N - p.Uncertainty_Lower_N #If realized inflation inside nowcast range if p.Uncertainty_Upper_N+.01 >= group.realized_inflation >= p.Uncertainty_Lower_N-.01: p.nowcast_range_score = C.RANGE_SCALAR/(1+nowcast_range) #if not, player earns nothing else: p.nowcast_range_score = 0 #forecast point# p.abs_FE = abs(p.in_round(round-1).field_maybe_none('e_pi_tomorrow') - group.realized_inflation) p.score_FE = (2**-(p.abs_FE))*C.POINT_SCALAR #forecast range# #forecast tempvar not stored in data output forecast_range = p.in_round(round-1).Uncertainty_Upper_N - p.in_round(round-1).Uncertainty_Lower_N ##If realized inflation (today) lies within the nowcast bounds provided yesterday then subject earns something if p.in_round(round-1).Uncertainty_Upper_F+.01 >= group.realized_inflation >= p.in_round(round-1).Uncertainty_Lower_F -.01: p.forecast_range_score = C.RANGE_SCALAR/(1+forecast_range) #Otherwise, the subject earns nothing else: p.forecast_range_score = 0 #temp holds the point and range forecast earnings for both now cas (nowcast,NE) and forecast (FE, forecast) temp = p.score_NE + p.score_FE + p.nowcast_range_score + p.forecast_range_score # running total of forecast points p.total_expectations_points = temp + p.in_round(round-1).total_expectations_points ##Consumption score #Units consumed by the middle-aged household today p.units_today_middle = p.consumption_dollars/group.realized_price #Units consumed by the old household today p.units_today_old = p.in_round(round-1).realized_savings_dollars/group.realized_price ##In period one, player only gets consumption points from the middle-aged household #consumption from today's middle-aged household p.consumption_utility_middle = 5.01 + np.log(.0067 + p.units_today_middle) #consumption from yesterday's middle-aged household p.consumption_utility_old = 5.01 + np.log(.0067 + p.units_today_old) #storing them both together temp2 = p.consumption_utility_middle + p.consumption_utility_old ##Updating running total of consumption points p.total_consumption_points = temp2 + p.in_round(round-1).total_consumption_points def DropoutsRangeNowcast(group: Group): players = group.get_players() for p in players: if p.IsDropout: p.Uncertainty_Upper_N = p.e_pi_today + .5 p.Uncertainty_Lower_N = p.e_pi_today - .5 else: True def DropoutsRangeForecast(group: Group): players = group.get_players() for p in players: if p.IsDropout: p.Uncertainty_Upper_F = p.e_pi_tomorrow + .5 p.Uncertainty_Lower_F = p.e_pi_tomorrow - .5 else: True def DropoutsBudget(group: Group): import numpy as np players = group.get_players() for p in players: if p.IsDropout: p.consumption_dollars = np.median([p.field_maybe_none('consumption_dollars') for p in players if p.field_maybe_none('consumption_dollars') is not None ]) else: True def flag_update(group: Group): players = group.get_players() round=group.round_number if round > 1: for p in players: p.IsDropout = p.in_round(round-1).IsDropout p.DropOutCheckerFlag = p.in_round(round-1).DropOutCheckerFlag class Player(BasePlayer): e_price_today = models.FloatField(label='Expected price today?', min=0.01) e_price_tomorrow = models.FloatField(label='Expected price tomorrow?', min=0.01) abs_FE = models.FloatField(min=0) score_FE = models.FloatField() abs_NE = models.FloatField(min=0) score_NE = models.FloatField() total_expectations_points = models.FloatField() e_pi_today = models.FloatField(label='Expected inflation today?', min=-100000) e_pi_tomorrow = models.FloatField(label='Expected inflation tomorrow?', min=-100000) consumption_dollars = models.FloatField(initial=0, min=0) e_savings_dollars = models.FloatField() realized_savings_dollars = models.FloatField(min=0) consumption_utility_middle = models.FloatField() consumption_utility_old = models.FloatField() total_consumption_points = models.FloatField() Uncertainty_Upper_N = models.FloatField(label='Nowcast Range - Upper Bound') Uncertainty_Lower_N = models.FloatField(label='Nowcast Range - Lower Bound') Uncertainty_Upper_F = models.FloatField(label='Forecast Range - Upper Bound') Uncertainty_Lower_F = models.FloatField(label='Forecast Range - Lower Bound') IsDropout = models.BooleanField(initial=False) DropOutCheckerFlag = models.BooleanField(choices=[[True, 'No'], [False, 'Yes']], initial=False, label='Are you still participating in the experiment?', widget=widgets.RadioSelectHorizontal) nowcast_range_score = models.FloatField() forecast_range_score = models.FloatField() units_today_middle = models.FloatField() units_today_old = models.FloatField() def Uncertainty_Lower_N_max(player: Player): return player.e_pi_today def Uncertainty_Upper_N_min(player: Player): return player.e_pi_today def Uncertainty_Lower_F_max(player: Player): return player.e_pi_tomorrow def Uncertainty_Upper_F_min(player: Player): return player.e_pi_tomorrow class StartGate(WaitPage): group_by_arrival_time = True body_text = '