from otree.api import ( models, widgets, BaseConstants, BaseSubsession, BaseGroup, BasePlayer, Currency as c, currency_range ) import random, math def indefinite(continue_prob, hard_max=0): # Created by Patrick Julius of UC Irvine # This simple function is designed to aid in the creation of # experiments using indefinitely-iterated games. # Using the inverse CDF of the geometric distribution, # this function randomly generates a number of rounds # with the same ex ante probability distribution as would # be generated by randomly continuing at each round with # a constant probability equal to continue_prob. # hard_max is an optional parameter that imposes a cap on the # largest possible number of rounds; it is recommended to set # this to a very large number so that it is rarely if ever # encountered. To avoid deception, participants should be # informed that there is hard maximum at e.g. 1000 rounds; # as long as the maximum is sufficiently large, this # has a negligible effect on the effective discount rate # of the supergame. If you do not want a hard cap at all, # simply omit hard_max. # The proper use of this function is as follows: # Constants.num_rounds = indefinite(continue_prob, hard_max) # To preserve indefiniteness, do not display the value of # Constants.num_rounds to participants; tell them it was # randomly generated using the stated continuation probability. p = random.random() N = math.floor(math.log(1 - p) / math.log(continue_prob)) if hard_max > 0: return min(N, hard_max) else: return N author = 'Patrick Julius' doc = """ An experiment using a monopolistic competition price-setting game to look for asymmetric price transmission of cost shocks, the 'Rockets and Feathers' effect. """ class Constants(BaseConstants): name_in_url = 'Rockets_Feathers' players_per_group = 4 # A maximum, which hopefully will not bind num_rounds = 200 class Subsession(BaseSubsession): def creating_session(self): # Configure rounds if self.round_number == 1: # print("Shock sequence: " + str(self.session.config['shock_sequence'])) # Reverse order of shocks so that they can be popped as a stack self.session.config['shock_sequence'] = self.session.config['shock_sequence'][::-1] if self.session.config['indefinite_duration']: ## Indefinite duration of whole game ## self.session.vars['num_rounds'] = self.session.config['shock_gap'] * (1 + indefinite(self.session.config['continuation_probability'])) else: ## Definite duration of whole game ## self.session.vars['num_rounds'] = len(self.session.config['shock_sequence']) * self.session.config['shock_gap'] # Group players into markets of the chosen size group_matrix = [] i = 0 group = [] for player in self.get_players(): # print("Player: " + str(player)) group.append(player) i += 1 if i >= self.session.config['market_size']: print("Group: " + str(group)) i = 0 group_matrix.append(group) group = [] # The final group, once you run out of players print("Group: " + str(group)) group_matrix.append(group) print("Group matrix: " + str(group_matrix)) self.set_group_matrix(group_matrix) else: self.group_like_round(self.round_number-1) # Cost shocks print("Round: " + str(self.round_number) + " of " + str(self.session.vars['num_rounds'])) # print("Shock gap: " + str(self.session.config['shock_gap'])) if self.round_number == 1: for player in self.get_players(): player.cost = self.session.config['initial_cost'] elif (self.session.config['indefinite_shocks'] and random.random() < 1/self.session.config['shock_gap'] or not self.session.config['indefinite_shocks'] and self.round_number % self.session.config['shock_gap'] == 1) and \ self.round_number <= self.session.vars['num_rounds']: # print("Should trigger a shock") shock = self.session.config['shock_size'] * self.session.config['shock_sequence'].pop() # shock = random.randint(-2, 2)*Constants.shock_size for player in self.get_players(): player.cost = player.in_round(self.round_number - 1).cost + shock else: for player in self.get_players(): player.cost = player.in_round(self.round_number - 1).cost class Group(BaseGroup): average_price = models.CurrencyField() def set_payoffs(self): self.average_price = 0 for player in self.get_players(): self.average_price += player.price self.average_price /= Constants.players_per_group demand_constant = self.session.config['demand_constant'] demand_multiplier = self.session.config['demand_multiplier'] payoff_adjustment = self.session.config['payoff_adjustment'] for p in self.get_players(): p.other_average = (self.average_price * self.session.config['market_size'] - p.price) / (self.session.config['market_size'] - 1) p.payoff = (demand_constant - demand_multiplier * (p.price - p.other_average)) * (p.price - p.cost) + \ payoff_adjustment p.payoff = max(p.payoff, 0) class Player(BasePlayer): price = models.CurrencyField() cost = models.CurrencyField() other_average = models.CurrencyField()