import csv
import numpy as np
import random
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 = 'ambiguity_auction'
PLAYERS_PER_GROUP = 3
NUM_ROUNDS = 40 # set the number big enough than the potential rounds used in the experiments.
INSTRUCTIONS_TEMPLATE = 'ambiguity_auction/instructions.html'
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)
real_rounds = models.IntegerField(initial=0)
# set the grouping method
switch_group = models.BooleanField(initial=False)
switch_type = models.BooleanField(initial=False)
# set distrbution parameters
p = models.FloatField(initial=0)
a = models.IntegerField(initial=0)
b = models.IntegerField(initial=0)
c = models.IntegerField(initial=0)
d = models.IntegerField(initial=0)
info = models.IntegerField(initial=0)
class Group(BaseGroup):
highest_bid = models.IntegerField()
reserve_price = models.IntegerField()
info_purchase = models.BooleanField(
label='Will you purchase the information of p?',
widget=widgets.RadioSelect
)
info_will = models.IntegerField(
label='What is your maximum willingness to pay for p in points?',
min=0
)
guess_p = models.IntegerField(
label='What do you think p% might be in percentage term (between 0 and 100)?',
min=0,
max=100
)
class Player(BasePlayer):
role_set = models.StringField()
bid_amount = models.IntegerField(
label="Please enter your bid.",
min=1,
max=100
)
is_winner = models.BooleanField(
initial=False
)
select_distribution = models.StringField()
item_private_value = models.IntegerField(
initial=0
)
money_earned = models.FloatField(initial=0)
# FUNCTIONS
def creating_session(subsession: Subsession):
# set the total rounds
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']
# 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 = C.PLAYERS_PER_GROUP
# set the grouping method
subsession.group_randomly(fixed_id_in_group=True)
# get distribution parameters
subsession.p = parameters['p']
subsession.a = parameters['a']
subsession.b = parameters['b']
subsession.c = parameters['c']
subsession.d = parameters['d']
subsession.info = parameters['info']
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 private value of the object for players
random_prob = random.random()
random_prob2 = random.random()
if random_prob <= subsession.p:
select_distribution = 'A'
if random_prob2 <= 0.75:
item_private_value = random.randint(subsession.a, subsession.b)
else:
item_private_value = random.randint(subsession.c, subsession.d)
else:
select_distribution = 'B'
if random_prob2 <= 0.25:
item_private_value = random.randint(subsession.a, subsession.b)
else:
item_private_value = random.randint(subsession.c, subsession.d)
p.select_distribution = select_distribution
p.item_private_value = item_private_value
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 parse_config(config_file):
with open('ambiguity_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',
'p': float(row['p']),
'a': int(row['a']),
'b': int(row['b']),
'c': int(row['c']),
'd': int(row['d']),
'info': int(row['info']),
'practice': row['practice'] == 'Yes',
})
return rounds
def set_winner(group: Group):
players = group.get_players()
passed_bids = [p.bid_amount for p in players if p.id_in_group > 1 and p.bid_amount >= group.reserve_price]
if len(passed_bids) > 0:
group.highest_bid = max(passed_bids)
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
winner.is_winner = True
else:
group.highest_bid = 0
for p in players:
set_payoff(p)
def set_payoff(player: Player):
subsession = player.subsession
if subsession.is_practice_round:
player.payoff = 0
else:
if player.role_set == 'auctioneer':
player.payoff = player.group.highest_bid - player.group.info_purchase * player.subsession.info
else:
if player.is_winner:
player.payoff = player.item_private_value - player.bid_amount
else:
player.payoff = 0
# 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 SellerWaitPage(WaitPage):
wait_for_all_groups = True
class Seller(Page):
#timeout_seconds = 60
@staticmethod
def vars_for_template(player: Player):
group = player.group
return {
'probability_a': player.subsession.p*100,
'probability_b': 100-player.subsession.p*100
}
@staticmethod
def get_form_fields(player: Player):
if player.role_set == 'bidder':
return []
else:
return ['info_purchase', 'info_will', 'guess_p']
form_model = 'group'
class SellerInfoWaitPage(WaitPage):
wait_for_all_groups = True
class SellerInfo(Page):
#timeout_seconds = 30
@staticmethod
def vars_for_template(player: Player):
group = player.group
return {
'probability_a': player.subsession.p*100,
'probability_b': 100-player.subsession.p*100
}
@staticmethod
def get_form_fields(player: Player):
if player.role_set == 'bidder':
return []
else:
return ['reserve_price']
form_model = 'group'
class BidWaitPage(WaitPage):
wait_for_all_groups = True
class Bid(Page):
#timeout_seconds = 60
@staticmethod
def vars_for_template(player: Player):
return {
'probability_a': player.subsession.p*100,
'probability_b': 100-player.subsession.p*100
}
@staticmethod
def get_form_fields(player: Player):
if player.role_set == 'bidder':
return ['bid_amount']
else:
return []
form_model = 'player'
class ResultsWaitPage(WaitPage):
after_all_players_arrive = set_winner
class Results(Page):
timeout_seconds = 20
@staticmethod
def vars_for_template(player: Player):
group = player.group
# player_id = player.id_in_group
group_players = group.get_players()
if player.role_set == 'auctioneer':
current_round_payoff = player.group.highest_bid - player.group.info_purchase * player.subsession.info
else:
if player.is_winner:
current_round_payoff = player.item_private_value - player.bid_amount
else:
current_round_payoff = 0
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],
)
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,
'money_earned': money_earned
}
# page sequence
page_sequence = [Welcome, Introduction, SellerWaitPage, Seller,
SellerInfoWaitPage, SellerInfo, BidWaitPage, Bid, ResultsWaitPage, Results, FinalPage]