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]