from otree.api import ( models, widgets, BaseConstants, BaseSubsession, BaseGroup, BasePlayer, Currency as c, currency_range, ) import random author = 'John Duffy, Nishtha Sharma, and Patrick Julius' doc = """ This app implements a simulation of bank runs, in which players represent banks which must decide how to allocate their asset portfolios in order to retain solvency and liquidity in order to avoid bank runs. In one treatment, the accounting review is automatic and always correct. In another treatment, the accounting review is done by another participant and is incentivized to be correct but may still be wrong. """ ### TO DO ### # Send with my report: # Mention that I set default assets to 33 and default price to 1.5 # Note that I gave no payoff for prediction when trade did not occur # Suggest that they get paid for liquid AND solvent together, in the same rounds where other players get paid for allocation # This way everyone has an allocation payoff and a prediction payoff # High priority # # In progress: Adding functionality for accountant players # ISSUE: When the price is calculated with accountant players, it doubles everything. (Doesn't change the outcome.) # ERROR: Cash and assets not updating correctly after trade when reported to accountants # Need to implement payoffs for making correct predictions # Make sure that role assignment works properly. If it doesn't, switch to this: # Auto-assign all roles, if there is no accountant, then accountants are just other banks # Medium priority # # Floating-point errors can occur in display, esp. history table # Low priority # # Payoffs in history table are given in "points", not dollars # Idea: Maybe make the default price forecast the last period's price? class Constants(BaseConstants): name_in_url = 'BR' players_per_group = 4 # num_rounds = 30 # For actual experiment num_rounds = 4 # For testing class Subsession(BaseSubsession): def creating_session(self): if self.round_number == 1: num_paying_rounds_allocation = self.session.config['paying_rounds_allocation'] num_paying_rounds_prediction = self.session.config['paying_rounds_prediction'] total_paying_rounds = num_paying_rounds_prediction + num_paying_rounds_prediction paying_rounds = random.sample(range(1, Constants.num_rounds+1), total_paying_rounds) self.session.vars['paying_rounds_allocation'] = random.sample(paying_rounds, num_paying_rounds_allocation) self.session.vars['paying_rounds_prediction'] = list(x for x in paying_rounds if x not in self.session.vars['paying_rounds_allocation']) # At the end this should yield two disjoint lists of rounds, each of the size set by the config var. self.group_randomly(fixed_id_in_group=True) class Group(BaseGroup): state = models.IntegerField() total_offer = models.IntegerField() total_bid = models.IntegerField() price = models.FloatField() def set_state(self): if random.random() < 0.5: self.state = 1 else: self.state = 2 class Player(BasePlayer): # These variables are used by bank players asset = models.IntegerField() default_asset = models.IntegerField() decision = models.StringField(label="Do you want to buy or sell assets?", widget=widgets.RadioSelectHorizontal, choices=['Buy', 'Sell']) bid = models.IntegerField(label="Please enter the amount of your offer.") price_guess = models.FloatField(label="Please enter your prediction of the market price of assets.") allocation_payoff = models.CurrencyField() prediction_payoff = models.CurrencyField() # These variables are used by accountant players decision_guess = models.StringField(label="Do you expect the bank to buy or sell assets?", widget=widgets.RadioSelectHorizontal, choices=['Buy', 'Sell']) bid_guess = models.IntegerField(label="Please enter the amount you expect the bank to offer.") solvent_correct = models.BooleanField() liquid_correct = models.BooleanField() # These variables are used by both, either observed or modified initial_asset = models.IntegerField() cash = models.FloatField() impatient = models.IntegerField() liquid = models.BooleanField(label="Do you believe the bank is liquid?", choices=[[True, 'Yes'], [False, 'No']]) solvent = models.BooleanField(label="Do you believe the bank is solvent?", choices=[[True, 'Yes'], [False, 'No']]) def set_default(self): # Defining default asset investment if self.round_number == 1: self.default_asset = self.session.config['default_asset'] else: self.default_asset = self.in_round(self.round_number - 1).initial_asset return self.default_asset def set_impatient(self): if self.group.state == 1: if self.get_role() == 'Region I' or self.get_role() == 'Region I Accountant': self.impatient = 67 else: self.impatient = 33 else: if self.get_role() == 'Region I' or self.get_role() == 'Region I Accountant': self.impatient = 33 else: self.impatient = 67 # Is this a deprecated functionality? Should I redo the roles? def get_role(self): # accountant is always bank + 2 if self.session.config['has_accountant']: if self.id_in_group % 4 == 1: return 'Region I' elif self.id_in_group % 4 == 2: return 'Region II' elif self.id_in_group % 4 == 3: return 'Region I Accountant' else: return 'Region II Accountant' else: if self.id_in_group % 2 == 1: return 'Region I' else: return 'Region II' def get_partner(self): if self.get_role() == 'Region I': target = 'Region I Accountant' elif self.get_role() == 'Region II': target = 'Region II Accountant' elif self.get_role() == 'Region I Accountant': target = 'Region I' else: target = 'Region II' for player in self.get_others_in_group(): if player.get_role() == target: return player def other_players(self): return self.get_others_in_group()