from otree.api import * import numpy as np doc = """ Your app description """ # CLASSES class C(BaseConstants): NAME_IN_URL = 'adv_auctions_2_slots' PLAYERS_PER_GROUP = 2 NUM_ROUNDS = 8 CTR_1 = 10 CTR_2 = 4 V_high = 90 V_low = 70 noise_high = 70 noise_low = 50 sim_num = 5 INSTRUCTIONS_ENG = """ In this experiment you will put yourself in the shoes of marketing strategist competing for the advertising slots in search engine (e.g. Google). Every time user enters a particular word into Google an instant auction is conducted between every company willing to bid for this word in user's area. For instance, word combination 'food delivery' might attract all restaurants working in the area, while word 'pizza' is attractive only for italian food restaurants. Mechanism of distributing ad positions (generally 2-4 ads) follows General Second Price (GSP) auction rules. That means that positions are distributed according to descending bids (highest bidder receives first position, second highest - second, etc.), and we generally assume that users click on higher positions more often. If user indeed clicks on the ad, advertisers pays underline(next highest bid), just like in second-price auction user pays second highest bid [note, however, that, unlike in second-price option, always bidding your true value isn't dominant strategy]. Consider a following example: 3 pizzerias compete for a word combination 'pizza nearby' - pizzeria "Arezzo" bids $4 per click, "Bari" bids $3, and "Cagliari" bids $2. If search engine sets 2 slots and reserve price over $2.5 for this combination, "Arezzo" will receive highest position for $3 per click and "Bari" will pay $2.5, since "Cagliari"'s bid was below reserve price. In this assignment you will be competing for 2 word combinations simultaneously - "italian food" and "japanese food". """ class Subsession(BaseSubsession): pass class Group(BaseGroup): av_noise_1 = models.FloatField() av_noise_2 = models.FloatField() noise_11 = models.IntegerField() noise_12 = models.IntegerField() noise_13 = models.IntegerField() noise_14 = models.IntegerField() noise_15 = models.IntegerField() noise_21 = models.IntegerField() noise_22 = models.IntegerField() noise_23 = models.IntegerField() noise_24 = models.IntegerField() noise_25 = models.IntegerField() pass class Player(BasePlayer): bid_1 = models.IntegerField(min=0, max=100, blank=True) bid_2 = models.IntegerField(min=0, max=100, blank=True) outcome_11 = models.StringField(initial="") outcome_12 = models.StringField(initial="") outcome_13 = models.StringField(initial="") outcome_14 = models.StringField(initial="") outcome_15 = models.StringField(initial="") outcome_21 = models.StringField(initial="") outcome_22 = models.StringField(initial="") outcome_23 = models.StringField(initial="") outcome_24 = models.StringField(initial="") outcome_25 = models.StringField(initial="") payoff_11 = models.CurrencyField(initial=cu(0)) payoff_12 = models.CurrencyField(initial=cu(0)) payoff_13 = models.CurrencyField(initial=cu(0)) payoff_14 = models.CurrencyField(initial=cu(0)) payoff_15 = models.CurrencyField(initial=cu(0)) payoff_21 = models.CurrencyField(initial=cu(0)) payoff_22 = models.CurrencyField(initial=cu(0)) payoff_23 = models.CurrencyField(initial=cu(0)) payoff_24 = models.CurrencyField(initial=cu(0)) payoff_25 = models.CurrencyField(initial=cu(0)) payoff_1 = models.CurrencyField(initial=cu(0)) payoff_2 = models.CurrencyField(initial=cu(0)) final_payoff = models.CurrencyField(initial=cu(0)) pv_1 = models.IntegerField() pv_2 = models.IntegerField() # FUNCTIONS def auction_res(bid_p1: int, bid_p2: int, b_noise: int, pv_1: int, pv_2: int): p1_payoff, p2_payoff, p1_outcome, p2_outcome = cu(0), cu(0), "", "" bids = np.array([bid_p1, bid_p2, b_noise]) sorted_bids = [i if i > 50 else 50 for i in np.sort(bids)[::-1]] positions = np.lexsort((np.random.random(bids.size), bids))[::-1] # ASSIGN WINNING BIDS if positions[0] == 0: p1_outcome = "First" p1_payoff = cu((pv_1 - sorted_bids[1]) * C.CTR_1) if positions[1] == 1: p2_outcome = "Second" p2_payoff = cu((pv_2 - sorted_bids[2]) * C.CTR_2) elif (positions[2] == 1) and (b_noise == bid_p2): p2_outcome = "Second" p2_payoff = cu((pv_2 - sorted_bids[2]) * C.CTR_2) elif positions[0] == 1: p2_outcome = "First" p2_payoff = cu((pv_2 - sorted_bids[1]) * C.CTR_1) if positions[1] == 0: p1_outcome = "Second" p1_payoff = cu((pv_1 - sorted_bids[2]) * C.CTR_2) elif (positions[2] == 0) and (b_noise == bid_p1): p1_outcome = "Second" p1_payoff = cu((pv_1 - sorted_bids[2]) * C.CTR_2) else: if (positions[1] == 0) and (bid_p1 >= b_noise): p1_payoff = cu((pv_1 - sorted_bids[2]) * C.CTR_2) p1_outcome = "Second" elif (positions[1] == 1) and (bid_p2 >= b_noise): p2_payoff = cu((pv_2 - sorted_bids[2]) * C.CTR_2) p2_outcome = "Second" # ASSIGN LOSING BIDS if bid_p1 == -100: p1_outcome = "No Bet" elif bid_p1 < b_noise: p1_outcome = "Below RP" if bid_p2 == -100: p2_outcome = "No Bet" elif bid_p2 < b_noise: p2_outcome = "Below RP" return p1_payoff, p2_payoff, p1_outcome, p2_outcome def set_payoffs(group: Group): b_noise_1 = np.random.randint(C.noise_low, C.noise_high, C.sim_num) b_noise_2 = np.random.randint(C.noise_low, C.noise_high, C.sim_num) group.noise_11 = int(b_noise_1[0]) group.noise_12 = int(b_noise_1[1]) group.noise_13 = int(b_noise_1[2]) group.noise_14 = int(b_noise_1[3]) group.noise_15 = int(b_noise_1[4]) group.noise_21 = int(b_noise_2[0]) group.noise_22 = int(b_noise_2[1]) group.noise_23 = int(b_noise_2[2]) group.noise_24 = int(b_noise_2[3]) group.noise_25 = int(b_noise_2[4]) players = group.get_players() bid_matrix = np.zeros((C.PLAYERS_PER_GROUP, 2)) payoff_matrix = np.zeros((C.PLAYERS_PER_GROUP, 5, 2)) outcome_matrix = [[['', ''] for _ in range(5)] for _ in range(2)] for i, p in enumerate(players): try: bid_matrix[i][0] = p.bid_1 except TypeError: bid_matrix[i][0] = -100 try: bid_matrix[i][1] = p.bid_2 except TypeError: bid_matrix[i][1] = -100 for j in range(C.sim_num): payoff_matrix[0][j][0], payoff_matrix[1][j][0], outcome_matrix[0][j][0], outcome_matrix[1][j][0] = auction_res( bid_matrix[0][0], bid_matrix[1][0], b_noise_1[j], players[0].pv_1, players[1].pv_1 ) payoff_matrix[0][j][1], payoff_matrix[1][j][1], outcome_matrix[0][j][1], outcome_matrix[1][j][1] = auction_res( bid_matrix[0][1], bid_matrix[1][1], b_noise_2[j], players[0].pv_2, players[1].pv_2 ) players[0].payoff_11, players[0].payoff_12, players[0].payoff_13, players[0].payoff_14, players[ 0].payoff_15 = payoff_matrix[0, :, 0] players[0].payoff_21, players[0].payoff_22, players[0].payoff_23, players[0].payoff_24, players[ 0].payoff_25 = payoff_matrix[0, :, 1] players[1].payoff_11, players[1].payoff_12, players[1].payoff_13, players[1].payoff_14, players[ 1].payoff_15 = payoff_matrix[1, :, 0] players[1].payoff_21, players[1].payoff_22, players[1].payoff_23, players[1].payoff_24, players[ 1].payoff_25 = payoff_matrix[1, :, 1] players[0].outcome_11, players[0].outcome_12, players[0].outcome_13, players[0].outcome_14, players[ 0].outcome_15 = np.array(outcome_matrix)[0, :, 0] players[0].outcome_21, players[0].outcome_22, players[0].outcome_23, players[0].outcome_24, players[ 0].outcome_25 = np.array(outcome_matrix)[0, :, 1] players[1].outcome_11, players[1].outcome_12, players[1].outcome_13, players[1].outcome_14, players[ 1].outcome_15 = np.array(outcome_matrix)[1, :, 0] players[1].outcome_21, players[1].outcome_22, players[1].outcome_23, players[1].outcome_24, players[ 1].outcome_25 = np.array(outcome_matrix)[1, :, 1] players[0].payoff_1 = np.mean(payoff_matrix[0, :, 0]) players[0].payoff_2 = np.mean(payoff_matrix[0, :, 1]) players[1].payoff_1 = np.mean(payoff_matrix[1, :, 0]) players[1].payoff_2 = np.mean(payoff_matrix[1, :, 1]) players[0].payoff = players[0].payoff_1 + players[0].payoff_2 players[1].payoff = players[1].payoff_1 + players[1].payoff_2 group.av_noise_1 = np.mean(b_noise_1) group.av_noise_2 = np.mean(b_noise_2) if group.round_number == C.NUM_ROUNDS: for p in group.get_players(): possible_payoffs = [p.in_round(j).payoff for j in range(4, 9)] final_po = np.random.choice(possible_payoffs) p.final_payoff = final_po else: pass # PAGES class InitialWaitPage(WaitPage): @staticmethod def after_all_players_arrive(group: Group): for p in group.get_players(): if p.round_number == 1: p.bid_1 = None p.bid_2 = None else: prev_player = p.in_round(p.round_number - 1) try: p.bid_1 = prev_player.bid_1 except TypeError: p.bid_1 = None try: p.bid_2 = prev_player.bid_2 except TypeError: p.bid_2 = None if p.id_in_group == 1: p.pv_1 = C.V_high p.pv_2 = C.V_low elif p.id_in_group == 2: p.pv_1 = C.V_low p.pv_2 = C.V_high class BidChat(Page): form_model = "player" form_fields = ["bid_1", "bid_2"] def js_vars(self: Player): opponent = self.get_others_in_group()[0] values_1 = [[self.pv_1, self.pv_1, self.pv_1], [opponent.pv_1, opponent.pv_1, opponent.pv_1]] values_2 = [[self.pv_2, self.pv_2, self.pv_2], [opponent.pv_2, opponent.pv_2, opponent.pv_2]] noise = [[C.noise_low, C.noise_low, C.noise_high], [C.noise_low, C.noise_low, C.noise_high]] return dict( v1=values_1, v2=values_2, noise=noise ) class ResultsWaitPage(WaitPage): after_all_players_arrive = set_payoffs class Results(Page): @staticmethod def vars_for_template(player: Player): opponent = player.get_others_in_group()[0] try: b1 = player.bid_1 except TypeError: b1 = -100 try: b2 = player.bid_2 except TypeError: b2 = -100 try: ob1 = opponent.bid_1 except TypeError: ob1 = -100 try: ob2 = opponent.bid_2 except TypeError: ob2 = -100 return { 'bid1': b1, 'bid2': b2, 'opponent_bid1': ob1, 'opponent_bid2': ob2 } @staticmethod def js_vars(player: Player): opponent = player.get_others_in_group()[0] group = player.group try: b1 = player.bid_1 except TypeError: b1 = None try: b2 = player.bid_2 except TypeError: b2 = None try: ob1 = opponent.bid_1 except TypeError: ob1 = None try: ob2 = opponent.bid_2 except TypeError: ob2 = None bids = [b1, b2] opponent_bids = [ob1, ob2] reserve_value = [group.av_noise_1, group.av_noise_2] return dict( bids=bids, opponent_bids=opponent_bids, reserve_value=reserve_value ) class FinalPage(Page): @staticmethod def is_displayed(player): return player.round_number == C.NUM_ROUNDS @staticmethod def vars_for_template(player: Player): try: final_payoff = player.final_payoff except TypeError: final_payoff = 0 return { 'final_payoff': final_payoff } page_sequence = [InitialWaitPage, BidChat, ResultsWaitPage, Results, FinalPage]