import csv import numpy as np from otree.api import * doc = """ This oTree program can run private value, common value, and mixed value auction
The first treatment add an additional seller (auctioner) role.
""" class C(BaseConstants): NAME_IN_URL = 'common_value_auction' PLAYERS_PER_GROUP = None NUM_ROUNDS = 50 # set the number big enough than the potential rounds used in the experiments. INSTRUCTIONS_TEMPLATE = 'common_value_auction/instructions.html' # auctioneer_ROLE = 'auctioneer' # BIDDER1_ROLE = 'bidder1' # BIDDER2_ROLE = 'bidder2' class Subsession(BaseSubsession): # define the parameters as the config file is_practice_round = models.BooleanField(initial=False, doc="""set the practice round, default is False""") # set the rounds number total_rounds = models.IntegerField(initial=0) # practice_rounds = models.IntegerField(initial=0) real_rounds = models.IntegerField(initial=0) # real_round_number = models.IntegerField() auction = models.StringField( initial='private', doc="""set the auction type, default is private auction""") switch_group = models.BooleanField(initial=False) switch_type = models.BooleanField(initial=False) a = models.IntegerField(initial=0) b = models.IntegerField(initial=0) c = models.IntegerField(initial=0) d = models.IntegerField(initial=0) endowment = models.IntegerField(initial=0) red_bid = models.BooleanField(initial=False) class Group(BaseGroup): common_value = models.IntegerField( doc="""Common value of the item to be auctioned, random for treatment""" ) highest_bid = models.IntegerField() class Player(BasePlayer): role_set = models.StringField() # the max and min bid need to be determined by the parameters [a,b,c,d], to be defined via function below bid_amount = models.IntegerField( doc="""Amount bidden by the player""", label="Bid amount", ) is_winner = models.BooleanField( initial=False, doc="""Indicates whether the player is the winner""" ) item_private_value = models.IntegerField( initial=0, doc="""Set the private value of the player, integer""" ) item_common_value_signal = models.IntegerField( initial=0, doc="""Set the common value signal of the player, integer""" ) item_common_value = models.IntegerField( initial=0, doc=""" the common value of the player, integer""" ) item_both_value = models.IntegerField( initial=0, doc="""Set the total value of the player if the auction type is "both", integer""" ) item_final_value = models.IntegerField( initial=0, doc="""Set the final value of the player depend on the auction type, integer""" ) money_earned = models.FloatField(initial=0) max_bid = models.IntegerField(initial=0) min_bid = models.IntegerField(initial=0) retake = models.BooleanField(initial=False, doc="""Set whether the auctioneer decide to retake the auction, default is False""", blank=True) bid_amount1 = models.IntegerField( doc="""Amount bidden by the player in the red bid stage 1""", label="Bid amount" ) bid_amount2 = models.IntegerField( doc="""Amount bidden by the player in the red bid stage 2""", label="Bid amount" ) # FUNCTIONS def creating_session(subsession: Subsession): import random # session = subsession.session print(subsession.round_number) # set the total rounds # subsession.total_rounds, subsession.practice_rounds, subsession.real_rounds = get_rounds(subsession) subsession.total_rounds, subsession.real_rounds = get_rounds(subsession) if subsession.round_number > subsession.total_rounds: return parameters = get_parameters(subsession) # specify the practice round subsession.is_practice_round = parameters['practice'] # get the auction type subsession.auction = parameters['auction'] # get endowment subsession.endowment = parameters['endowment'] # define the red_bid treatment subsession.red_bid = parameters['red_bid'] # define group switch subsession.switch_group = parameters['switch_group'] subsession.switch_type = parameters['switch_type'] # set group size, because the group size varies, we can not use the otree built-in method players = subsession.get_players() group_size = parameters['group_size'] if subsession.round_number == 1: matrix = [] player_list = list(range(1, len(players) + 1)) for i in range(0, len(players), group_size): matrix.append(player_list[i: i + group_size]) print('matrix is', matrix) subsession.set_group_matrix(matrix) else: if subsession.switch_group: # if switch_group is set to true , we need to switch group if subsession.switch_type: # if switch_type is set to true , we need to switch type in group matrix = [] player_list = list(range(1, len(players) + 1)) random.shuffle(player_list) for i in range(0, len(players), group_size): matrix.append(player_list[i: i + group_size]) subsession.set_group_matrix(matrix) print('matrix is', matrix) else: # if we switch group, but we do not switch type for subjects subsession.group_like_round(subsession.round_number - 1) matrix = subsession.get_group_matrix() print('the old matrix is', matrix) new_matrix = [] group_id_1 = [row[0] for row in matrix] random.shuffle(group_id_1) new_matrix.append(group_id_1) group_id_others = [row[group_id] for row in matrix for group_id in range(1, group_size)] random.shuffle(group_id_others) row_length = int(len(group_id_others) / (group_size - 1)) for i in range(0, group_size - 1): temp = group_id_others[i * row_length:(i + 1) * row_length] new_matrix.append(temp) matrix_np = np.array(new_matrix).T new_matrix = matrix_np.tolist() print('mew_matrix', new_matrix) subsession.set_group_matrix(new_matrix) # need to be classified into two subclasses else: if subsession.switch_type: # switch type within one group subsession.group_like_round(subsession.round_number - 1) matrix = subsession.get_group_matrix() print('the old matrix is', matrix) new_matrix = [] for row in matrix: random.shuffle(row) new_matrix.append(row) print('mew_matrix', new_matrix) subsession.set_group_matrix(new_matrix) else: # if we do not need to switch_group, and do not need to switch within group, then let the group like last round subsession.group_like_round(subsession.round_number - 1) matrix = subsession.get_group_matrix() print('matrix is', matrix) # get the private value U(a,b) subsession.a, subsession.b = parameters['a'], parameters['b'] # get the common value signal U(c,d) subsession.c, subsession.d = parameters['c'], parameters['d'] for g in subsession.get_groups(): group_players = g.get_players() # set the id1 player in a group as auctioneer, and other players as bidders, only relevant if red_bid=True for p in group_players: if p.id_in_group == 1: p.role_set = 'auctioneer' else: p.role_set = 'bidder' # set the value of the object for players item_private_value = random.uniform(subsession.a, subsession.b) p.item_private_value = int(round(item_private_value, 1)) item_common_value_signal = random.uniform(subsession.c, subsession.d) p.item_common_value_signal = int(round(item_common_value_signal, 1)) # distinguish the definition of common value between red bid and non-red bid if subsession.red_bid: common_value = sum([p.item_common_value_signal for p in group_players if p.id_in_group > 1]) / len( [p.item_common_value_signal for p in group_players if p.id_in_group > 1]) g.common_value = int(round(common_value, 1)) for player in group_players: player.item_common_value = g.common_value player.item_both_value = player.item_private_value + g.common_value # To define the bidders' final value if player.id_in_group > 1: if subsession.auction == 'private': player.item_final_value = player.item_private_value elif subsession.auction == 'common': player.item_final_value = player.item_common_value elif subsession.auction == 'both': player.item_final_value = player.item_both_value else: print("no such auction type") # To define the auctioneer's reservation value else: if subsession.auction == 'private': player.item_final_value = 0 elif subsession.auction == 'common': player.item_final_value = 0 elif subsession.auction == 'both': player.item_final_value = 0 else: print("no such auction type") else: common_value = sum([p.item_common_value_signal for p in group_players]) / len( [p.item_common_value_signal for p in group_players]) g.common_value = int(round(common_value, 1)) # define the player.item_final_value based on auction type for player in group_players: player.item_common_value = g.common_value player.item_both_value = player.item_private_value + g.common_value if subsession.auction == 'private': player.item_final_value = player.item_private_value elif subsession.auction == 'common': player.item_final_value = player.item_common_value elif subsession.auction == 'both': player.item_final_value = player.item_both_value else: print("no such auction type") # define the max and min bid amount for players def bid_amount_max(player): subsession = player.subsession if subsession.auction == 'private': player.max_bid = subsession.b elif subsession.auction == 'common': player.max_bid = subsession.d elif subsession.auction == 'both': player.max_bid = subsession.b + subsession.d else: print("no such auction type") return player.max_bid def bid_amount1_max(player): subsession = player.subsession if subsession.auction == 'private': player.max_bid = subsession.b elif subsession.auction == 'common': player.max_bid = subsession.d elif subsession.auction == 'both': player.max_bid = subsession.b + subsession.d else: print("no such auction type") return player.max_bid def bid_amount2_max(player): subsession = player.subsession if subsession.auction == 'private': player.max_bid = subsession.b elif subsession.auction == 'common': player.max_bid = subsession.d elif subsession.auction == 'both': player.max_bid = subsession.b + subsession.d else: print("no such auction type") return player.max_bid def bid_amount_min(player): subsession = player.subsession if subsession.auction == 'private': player.min_bid = subsession.a elif subsession.auction == 'common': player.min_bid = subsession.c elif subsession.auction == 'both': player.min_bid = subsession.a + subsession.c else: print("no such auction type") return player.min_bid def bid_amount1_min(player): subsession = player.subsession if subsession.auction == 'private': player.min_bid = subsession.a elif subsession.auction == 'common': player.min_bid = subsession.c elif subsession.auction == 'both': player.min_bid = subsession.a + subsession.c else: print("no such auction type") return player.min_bid def bid_amount2_min(player): subsession = player.subsession if subsession.auction == 'private': player.min_bid = subsession.a elif subsession.auction == 'common': player.min_bid = subsession.c elif subsession.auction == 'both': player.min_bid = subsession.a + subsession.c else: print("no such auction type") return player.min_bid def get_parameters(subsession: Subsession): return parse_config(subsession.session.config['config_file'])[subsession.round_number - 1] def get_rounds(subsession: Subsession): # this function returns the total rounds and the total non-practice rounds rounds = parse_config(subsession.session.config['config_file']) total_rounds = len(rounds) practice_rounds = sum([rounds[i]['practice'] for i in range(len(rounds))]) real_rounds = total_rounds - practice_rounds return total_rounds, real_rounds # def get_reward(player: Player): # player.reward = parse_config(player.session.config['config_file'])[player.round_number - 1]['reward'] def parse_config(config_file): with open('common_value_auction/configs/' + config_file) as f: rows = list(csv.DictReader(f)) rounds = [] for row in rows: rounds.append({ 'round_number': int(row['round_number']), 'switch_group': row['switch_group'] == 'Yes', 'switch_type': row['switch_type'] == 'Yes', 'group_size': int(row['group_size']), 'a': int(row['a']), 'b': int(row['b']), 'c': int(row['c']), 'd': int(row['d']), 'practice': row['practice'] == 'Yes', 'red_bid': row['red_bid'] == 'Yes', 'auction': str(row['auction']), 'endowment': int(row['endowment']) }) return rounds def set_winner(group: Group): import random if group.subsession.red_bid: # if we have red bid treatment players = group.get_players() if group.get_player_by_id(1).retake: for p in players: if p.id_in_group > 1: p.bid_amount = p.bid_amount2 p.is_winner = False # reset the is_winner else: for p in players: if p.id_in_group > 1: p.bid_amount = p.bid_amount1 # The following condition is for the case: If in the first stage, there are more than one bidders with the highest bid(the same), then we random choose one as the winner; if the auctioneer chooses not to retake the auction, we need to choose the same one as the winner if p.is_winner: return group.highest_bid = max([p.bid_amount for p in players if p.id_in_group > 1]) players_with_highest_bid = [p for p in players if p.id_in_group > 1 and p.bid_amount == group.highest_bid] winner = random.choice( players_with_highest_bid ) # if there is a tie, winner is chosen at random # debug print # print('This is round', players[1].round_number) # print('players_with_highest_bid',[p.id_in_group for p in players_with_highest_bid]) # print('winner is player',winner.id_in_group) winner.is_winner = True for p in players: set_payoff(p) else: players = group.get_players() group.highest_bid = max([p.bid_amount for p in players]) players_with_highest_bid = [p for p in players if p.bid_amount == group.highest_bid] winner = random.choice( players_with_highest_bid ) # if there is a tie, winner is chosen at random # debug print # print('This is round', players[1].round_number) # print('players_with_highest_bid',[p.id_in_group for p in players_with_highest_bid]) # print('winner is player',winner.id_in_group) winner.is_winner = True for p in players: set_payoff(p) def set_payoff(player: Player): # group = player.group subsession = player.subsession # auction = subsession.auction if subsession.is_practice_round: player.payoff = 0 return # add the endowment for each bidder(include the auctioneer), this is for the database if subsession.red_bid: if player.role_set == 'auctioneer': # Remark: need to define the payoff of the auctioneer in the red bid mode player.payoff = player.group.highest_bid else: if player.is_winner: player.payoff = player.item_final_value - player.bid_amount + player.subsession.endowment else: player.payoff = 0 + player.subsession.endowment else: # for the normal auction if player.is_winner: player.payoff = player.item_final_value - player.bid_amount + player.subsession.endowment # Assume allow negative payoff; # if player.payoff < 0: # player.payoff = 0 else: player.payoff = 0 + player.subsession.endowment # PAGES class Welcome(Page): @staticmethod def is_displayed(player: Player): return player.round_number == 1 class Introduction(Page): @staticmethod def is_displayed(player: Player): return player.round_number == 1 class BidWaitPage(WaitPage): wait_for_all_groups = True class Bid(Page): # define the time limits timeout_seconds = 60 # this page is displayed only if red_bid is False @staticmethod def is_displayed(player: Player): return not player.subsession.red_bid form_model = 'player' form_fields = ['bid_amount'] class FirstBid(Page): # define the time limits timeout_seconds = 60 # this page is displayed only if red_bid is True and for the bidders @staticmethod def is_displayed(player: Player): return player.subsession.red_bid # and player.id_in_group > 1 @staticmethod def get_form_fields(player: Player): if player.id_in_group > 1: return ['bid_amount1'] else: return [] form_model = 'player' # form_fields = ['bid_amount1'] class ResultWaitForFirstBid(WaitPage): # this page is displayed only if red_bid is True and for the bidders @staticmethod def is_displayed(player: Player): return player.subsession.red_bid after_all_players_arrive = set_winner class ResultForFirstBid(Page): # define the time limits timeout_seconds = 40 # this page is displayed only if red_bid is True and for the bidders @staticmethod def is_displayed(player: Player): return player.subsession.red_bid @staticmethod def vars_for_template(player: Player): group = player.group # player_id = player.id_in_group group_players = group.get_players() # add the endowment for each bidder(include the auctioneer), this is for the page if player.role_set == 'auctioneer': current_round_payoff = player.group.highest_bid else: if player.is_winner: current_round_payoff = player.item_final_value - player.bid_amount + player.subsession.endowment else: current_round_payoff = 0 + player.subsession.endowment return dict(group_bids=[p.bid_amount for p in group_players if p.id_in_group > 1], group_size=len(group_players), current_round_payoff=current_round_payoff) @staticmethod def js_vars(player): group = player.group group_players = group.get_players() return dict( group_bids_js=[p.bid_amount for p in group_players if p.id_in_group > 1], ) form_model = 'player' form_fields = ['retake'] # class FirstBidauctioneer(Page): # # this page is displayed only if red_bid is True and for the bidders # @staticmethod # def is_displayed(player: Player): # return player.subsession.red_bid and player.id_in_group == 1 # # form_model = 'player' # form_fields = ['retake'] class WaitForSecondBid(WaitPage): pass class SecondBid(Page): # define the time limits timeout_seconds = 40 # this page is displayed only if red_bid is True and for the bidders @staticmethod def is_displayed(player: Player): return player.subsession.red_bid and player.id_in_group > 1 and player.group.get_player_by_id(1).retake form_model = 'player' form_fields = ['bid_amount2'] class ResultsWaitPage(WaitPage): after_all_players_arrive = set_winner class ResultsWaitPage2(WaitPage): wait_for_all_groups = True class Results(Page): # define the time limits timeout_seconds = 40 @staticmethod def vars_for_template(player: Player): group = player.group # player_id = player.id_in_group group_players = group.get_players() retake_decision = player.group.get_player_by_id(1).retake if group.subsession.red_bid: if player.role_set == 'auctioneer': current_round_payoff = player.group.highest_bid else: if player.is_winner: current_round_payoff = player.item_final_value - player.bid_amount + player.subsession.endowment else: current_round_payoff = 0 + player.subsession.endowment return dict(group_bids=[p.bid_amount for p in group_players if p.id_in_group > 1], group_size=len(group_players), current_round_payoff=current_round_payoff, retake_decision= retake_decision) else: if player.is_winner: current_round_payoff = player.item_final_value - player.bid_amount + player.subsession.endowment else: current_round_payoff = 0 + player.subsession.endowment return dict(group_bids=[p.bid_amount for p in group_players], group_size=len(group_players), current_round_payoff=current_round_payoff, retake_decision= retake_decision) @staticmethod def js_vars(player): group = player.group group_players = group.get_players() if group.subsession.red_bid: return dict( group_bids_js=[p.bid_amount for p in group_players if p.id_in_group > 1], ) else: return dict( group_bids_js=[p.bid_amount for p in group_players], ) class FinalPage(Page): @staticmethod def is_displayed(player: Player): last_round = player.subsession.total_rounds return player.round_number == last_round @staticmethod def vars_for_template(player: Player): participant = player.participant real_rounds = player.subsession.real_rounds participant.payoff = participant.payoff # adjust for payoff as the total payoff of non-practice rounds money_earned = participant.payoff_plus_participation_fee() player.money_earned = float(money_earned) return { 'cumulative_payoff': participant.payoff, 'raw_payoff': participant.payoff - player.subsession.endowment, 'money_earned': money_earned } # page_sequence = [Welcome, Introduction, BidWaitPage, Bid, FirstBid, ResultWaitForFirstBid, ResultForFirstBid, WaitForSecondBid, SecondBid, ResultsWaitPage, ResultsWaitPage2, Results, FinalPage]