from otree.api import ( models, widgets, BaseConstants, BaseSubsession, BaseGroup, BasePlayer, Currency as c, currency_range ) import random import statistics from decimal import * doc = """ 2 firms complete in a market by setting prices for homogenous goods. See "Kruse, J. B., Rassenti, S., Reynolds, S. S., & Smith, V. L. (1994). Bertrand-Edgeworth competition in experimental markets. Econometrica: Journal of the Econometric Society, 343-371." """ class Constants(BaseConstants): players_per_group = 2 name_in_url = 'bertrand_class' num_rounds = 12 # maximum_price = 100 instructions_template = 'bertrand_class/Instructions.html' class Subsession(BaseSubsession): def vars_for_admin_report(self): group = self.get_groups()[0] player_data = [] player_average = [] # moved this loop into a full for loop as we needed to cast the decimal before passing it to # the template. float() was complaining about the NoneType that can appear before the game is completed. # therefor we have to ignore none types whenever we request the admin report page. # player_average = [float(g.average_price) for g in group.in_all_rounds()] for g in group.in_all_rounds(): if g.average_price != None: player_average.append(float(g.average_price)) player_data.append({ 'name': 'Average Price', 'data': player_average }) return { 'price': player_data, 'categories': list(range(1, Constants.num_rounds, 1)) } class Group(BaseGroup): sum_price = models.DecimalField(max_digits=19, decimal_places=2) average_price = models.DecimalField(max_digits=19, decimal_places=2) def best_response(self, previous_round, winner_last_round): # Should a player fail to respond, we'll $1 off the higest losers' previous play if winner_last_round == True: print("previous winner {}".format(self.session.vars['first_loser'])) return self.session.vars['first_loser'] - 1 elif winner_last_round == False: print("best response: {}".format(previous_round)) return self.session.vars['winner'] - 1 return previous_round def set_payoffs(self): players = self.get_players() highest_price = [] for p in players: # Ensure that we cannot play a value below marginal_cost: if p.price < self.session.config['marginal_cost']: p.price = self.session.config['marginal_cost'] highest_price.append(p.price) # Should be the highest value played in the round: first = second = max(highest_price) # Sorting our list: sets the lowest price as first and second lowest as second: for p in players: if p.price < first: second = first first = p.price elif (p.price < second and p.price != first): second = p.price print("second: {}".format(second)) print("first: {}".format(first)) winning_price= min([p.price for p in players]) winners= [p for p in players if p.price == winning_price] winner= random.choice(winners) self.session.vars['winner'] = winner.price self.session.vars['first_loser'] = second print(winner.price) self.sum_price= sum([p.price for p in self.get_players()]) self.average_price= (self.sum_price)/len(players) for p in players: print("profit: {}".format(p.price)) p.payoff= c(0) if p == winner: p.is_a_winner= True p.payoff += p.price - self.session.config['marginal_cost'] def average_per_player(self): average_price= statistics.mean(p.price for p in self.get_players()) self.session.vars[self.round_number]= average_price return average_price class Player(BasePlayer): price= models.DecimalField( initial=None, doc="""Price player chooses to sell product for""", max_digits=19, # will hold numbers up to 1 billion decimal_places=2, # should limit to two decimal places ) is_a_winner= models.BooleanField( initial=False, doc="""Whether this player offered lowest price""" )