from otree.api import * import numpy import random doc = "Double auction market in an OLG framework" # TO DO: # IMPORTANT: # - DoNE: Tullock page # - DONE: settings variables # - Done: player random replacement at certain time # - DONE: player f() change at a certain time # - DONE: check the case when everyone invests zero in the JS # -Done: how do we deal with fitness to take into account new entrants --> start with the average of playing subjects # DONE -four versions for server: 2 w/short horizon and 6 players and 2 w/long horizon 9 players # - what to do if player is not responding? If they don't click in the next minute we drae a new player?? # NOT SO IMPORTANT: # DONE - complete the results screen # DONE- for the first round use np.random.choice instead of group id to select # DONE - default values when player just submits investment decision # DONE- add instructions # DONE- custom page for WaitOutside # DONE- what if we have a tie when looking at the pressure treatment class C(BaseConstants): NAME_IN_URL = 'Tullock' PLAYERS_PER_GROUP = None NUM_ROUNDS = 100 class Subsession(BaseSubsession): pass def creating_session(subsession: Subsession): subsession.group_randomly(fixed_id_in_group=False) if subsession.round_number == 1: players = subsession.get_players() for p in players: if p.id_in_subsession <= p.session.config['num_active_participants']: p.is_playing = True else: p.is_playing = False p.is_out = False p.round_payoff = 0 for p in subsession.get_players(): p.fitness = 0 class Group(BaseGroup): total_investment = models.FloatField() class Player(BasePlayer): is_playing = models.BooleanField() # whether they are trading or just waiting is_out = models.BooleanField() # whether they already played prediction = models.FloatField() # predicted price is_winner = models.BooleanField() # who won the contest investment = models.FloatField() # effort/investment in Tullock contest probability = models.FloatField() # f(effort and agg effort) prob of winning fitness = models.FloatField() # f(of payoff) to determine round_payoff = models.FloatField() # for recording payments to be selected at random # FUNCTIONS # calculates individual probability def prob_calculation(player: Player): if player.is_playing is True and player.investment > 0: if sum(p.investment for p in player.get_others_in_subsession() if p.is_playing) == 0: player.probability = 1 else: player.probability = player.investment**player.session.config['r'] / \ sum(p.investment**player.session.config['r'] for p in player.get_others_in_subsession() if p.is_playing) elif player.is_playing is True and player.investment == 0: player.probability = 0 # normalizes probability so it adds up to 1 def prob_normalization(group: Group): prob_sum = sum(x.probability for x in group.get_players() if x.is_playing) for p in group.get_players(): if p.is_playing: if prob_sum == 0: p.probability = 0 else: p.probability = p.probability/prob_sum print('probability of', p.id_in_group, 'is', p.probability) # chooses a winner at random def winner(group: Group): group.total_investment = sum(x.investment for x in group.get_players() if x.is_playing) for p in group.get_players(): if p.is_playing: p.is_winner = False # if no one invests if group.total_investment == 0: winner_id = numpy.random.choice([x for x in group.get_players() if x.is_playing is True]) winner_id.is_winner = True for p in group.get_players(): if p.is_playing: print('id in group=', p.id_in_group, 'probability=', p.probability, 'won?', p.is_winner) # if at least one invests else: prob = [x.probability for x in group.get_players() if x.is_playing is True] # creates list of probabilities aux = numpy.random.choice([x for x in group.get_players() if x.is_playing is True], p=prob) aux.is_winner = True for p in group.get_players(): if p.is_playing: print('id in group=', p.id_in_group, 'probability=', p.probability, 'won?', p.is_winner) print(prob) # calculates round payoff and fitness def round_payoff(player: Player): if player.is_playing: if player.is_winner: player.round_payoff = player.session.config['endowment']+player.session.config['V']-player.investment else: player.round_payoff = player.session.config['endowment']-player.investment # we can change this, so we have a ranking or any function if player.is_playing: player.fitness += sum(x.round_payoff for x in player.in_all_rounds() if x.is_playing) # selects one round for random payment for those who leave def random_payoff_leaves(player: Player): players_all = player.in_all_rounds() if player.is_playing is True and player.in_round(player.round_number+1).is_out is True: aux_paying_round = float(random.sample([p.round_number for p in players_all if p.is_playing], 1)[0]) player.participant.vars['paying_round'] = aux_paying_round player.payoff = player.in_round(aux_paying_round).round_payoff / player.session.config['us_exchange_rate'] player.participant.vars['aux_payoff'] = player.payoff*player.session.config['us_exchange_rate'] print('round paid for leaving player', player.participant.vars['paying_round'], 'a total of LS', player.participant.vars['aux_payoff'], 'in dollars', player.payoff) # selects one round for random payment for those who make it to the end def random_payoff_final(player: Player): players_all = player.in_all_rounds() if player.is_playing is True and player.round_number == player.session.config['num_rounds']: aux_paying_round = float(random.sample([p.round_number for p in players_all if p.is_playing], 1)[0]) player.participant.vars['paying_round'] = aux_paying_round player.payoff = player.in_round(aux_paying_round).round_payoff / player.session.config['us_exchange_rate'] player.participant.vars['aux_payoff'] = player.payoff*player.session.config['us_exchange_rate'] print('round paid for leaving player', player.participant.vars['paying_round'], 'a total of LS', player.participant.vars['aux_payoff'], 'in dollars', player.payoff) # selects the player to leave the game and the one that enters def player_leaves(group: Group): players = group.get_players() # for the random treatment if group.session.config['pressure'] is False: expelled_id = numpy.random.choice([x for x in players if x.is_playing is True]) print(expelled_id) expelled_id.in_round(group.round_number+1).is_out = True expelled_id.in_round(group.round_number+1).is_playing = False # for the pressure treatment else: players_aux = [x for x in group.get_players() if x.is_playing is True] expelled_id = sorted(players_aux, key=lambda player: player.fitness)[0] print(expelled_id) expelled_id.in_round(group.round_number + 1).is_out = True expelled_id.in_round(group.round_number + 1).is_playing = False new_entrant = numpy.random.choice([x for x in players if x.is_playing is False and x.is_out is False]) new_entrant.in_round(group.round_number+1).is_playing = True new_entrant.in_round(group.round_number + 1).is_out = False new_entrant.in_round(group.round_number + 1).fitness = sum(p.fitness for p in group.get_players() if p.is_playing)/\ group.session.config['num_active_participants'] print(new_entrant) # at the end of each round it keeps the roles: is_out, is_playing def new_round(group: Group): players = group.get_players() for p in players: p.in_round(p.round_number+1).is_playing = p.is_playing p.in_round(p.round_number+1).is_out = p.is_out # PAGES --------------------------------------------------------------------------------------------------------------- class PayingIntro(Page): # timeout_seconds = 20 @staticmethod def is_displayed(player: Player): return player.round_number <= player.session.config['num_rounds'] and \ ((player.round_number == 1 and player.is_playing is True) or (player.round_number > 1 and player.in_round(player.round_number-1).is_out is True and player.is_playing is True)) @staticmethod def vars_for_template(player: Player): return dict( num_rounds=player.session.config['num_rounds'] ) class WaitToStart(WaitPage): @staticmethod def is_displayed(player: Player): return player.round_number <= player.session.config['num_rounds'] and not player.is_out and player.is_playing @staticmethod def after_all_players_arrive(group: Group): new_round(group) class Investment(Page): @staticmethod def is_displayed(player: Player): return player.round_number <= player.session.config['num_rounds'] and player.is_playing is True form_model = 'player' form_fields = ['prediction', 'investment'] @staticmethod def js_vars(player: Player): return dict(id_in_group=player.id_in_group) @staticmethod def vars_for_template(player: Player): return dict( round_number=player.session.config['num_rounds'], endowment=player.session.config['endowment'], r=player.session.config['r'], V=player.session.config['V'], num_others=player.session.config['num_active_participants']-1, slider_max=player.session.config['V'], slider_min=0, slider_default=0, signal=5, theta_hat_prize=1 ) class ResultsWaitPage(WaitPage): @staticmethod def is_displayed(player: Player): return player.round_number <= player.session.config['num_rounds'] and player.is_playing is True and \ not player.is_out @staticmethod def after_all_players_arrive(group: Group): for player in group.get_players(): prob_calculation(player) prob_normalization(group) winner(group) for player in group.get_players(): round_payoff(player) class Results(Page): # timeout_seconds = 20 @staticmethod def is_displayed(player: Player): return player.round_number <= player.session.config['num_rounds'] and player.is_playing is True @staticmethod def vars_for_template(player: Player): return dict( player_in_all_rounds=reversed(player.in_all_rounds()), num_rounds=player.session.config['num_rounds'], others_investment=sum(p.investment for p in player.get_others_in_subsession() if p.is_playing) / (player.session.config['num_active_participants']-1), others_fitness=sum(p.fitness for p in player.get_others_in_subsession() if p.is_playing) / (player.session.config['num_active_participants']-1), probability=player.probability*100 ) @staticmethod def before_next_page(player: Player, timeout_happened): random_payoff_final(player) class LeavePage(WaitPage): @staticmethod def is_displayed(player: Player): return player.round_number <= player.session.config['num_rounds'] and \ ((player.round_number == player.session.config['1_replacement'] or player.round_number == player.session.config['2_replacement'] or player.round_number == player.session.config['3_replacement'] or player.round_number == player.session.config['4_replacement'] or player.round_number == player.session.config['5_replacement']) and not player.is_out and player.is_playing) @staticmethod def after_all_players_arrive(group: Group): player_leaves(group) for player in group.get_players(): random_payoff_leaves(player) class WaitToEnter(WaitPage): @staticmethod def is_displayed(player: Player): return player.round_number <= player.session.config['num_rounds'] and player.is_playing is False and\ not player.is_out title_text = "Waiting to Enter" body_text = "Please stay in your computer until you are invited to join the contest " @staticmethod def vars_for_template(player: Player): return dict( ) class FinalResults(Page): # timeout_seconds = 25 @staticmethod def is_displayed(player: Player): return player.round_number <= player.session.config['num_rounds'] and \ ((player.round_number == player.session.config['num_rounds'] and player.is_playing) or (player.is_out is True and player.in_round(player.round_number-1).is_playing is True)) @staticmethod def vars_for_template(player: Player): return dict( player_in_all_rounds=reversed(player.in_all_rounds()), payoff_plus_participation_fee=player.participant.payoff_plus_participation_fee(), total_earnings=player.participant.vars['aux_payoff'], show_up_fee=float(player.session.config['participation_fee']), total_earnings_US=float(player.participant.payoff), total_payoff_US=float(player.participant.payoff_plus_participation_fee()), paying_round=player.participant.vars['paying_round'], us_exchange_rate=player.session.config['us_exchange_rate'], num_rounds=player.session.config['num_rounds'], pressure=player.session.config['pressure'] ) @staticmethod def app_after_this_page(player, upcoming_apps): print('upcoming_apps is', upcoming_apps) if player.is_out is True: return "Tullock_questionnaire" class WaitOutside(WaitPage): @staticmethod def is_displayed(player: Player): return player.round_number <= player.session.config['num_rounds'] and player.is_out is True title_text = "Your participation is over" body_text = "Thanks for participating you are now free to leave " @staticmethod def vars_for_template(player: Player): return dict( ) class PlayerExited(Page): @staticmethod def is_displayed(player: Player): return player.round_number <= player.session.config['num_rounds'] and \ ((player.round_number == player.session.config['1_replacement'] or player.round_number == player.session.config['2_replacement'] or player.round_number == player.session.config['3_replacement'] or player.round_number == player.session.config['4_replacement'] or player.round_number == player.session.config['5_replacement']) and not player.is_out and player.is_playing) @staticmethod def vars_for_template(player: Player): return dict(pressure=player.session.config['pressure'] ) class WaitBeforeNextRound(WaitPage): @staticmethod def is_displayed(player: Player): return player.round_number <= player.session.config['num_rounds'] wait_for_all_groups = True title_text = "Setting up next round" body_text = "Please wait" @staticmethod def vars_for_template(player: Player): return dict( ) @staticmethod def after_all_players_arrive(subsession: Subsession): pass page_sequence = [PayingIntro, WaitToStart, Investment, ResultsWaitPage, Results, WaitToEnter, LeavePage, FinalResults, WaitOutside, PlayerExited, WaitBeforeNextRound]