from ast import If from otree.api import * import random import itertools from math import comb doc = """ Price Information and Cost Uncertainty in Oligopoly by Ruolong Xiao and Cesar Martinelli" """ class C(BaseConstants): PLAYERS_PER_GROUP = 3 NAME_IN_URL = 'oligopoly' NUM_ROUNDS = 30 max_price = 100 min_price = 50 min_cost = 50 max_cost = 70 cap_more_info = True # This is the indication variable. ncap_more_info = False baseline = False # num_buyer = 60 unit_demand = 2 num_buyer_1 = 30 # number of buyers who quote 1 prices num_buyer_2 = 90 # number of buyers who quote 2 prices num_buyer_3 = 0 # number of buyers who quote 3 prices @staticmethod def assign_player_cost(): """Generate a random cost for a player between min_cost and max_cost""" return random.randint(int(C.min_cost), int(C.max_cost)) class Subsession(BaseSubsession): # round of the game that will be used for payment pay_round = models.IntegerField() pass def creating_session(subsession: Subsession): # Only roll the dice in round 1 if subsession.round_number == 1: # Pick a random round (1 to NUM_ROUNDS) pay_round = random.randint(1, C.NUM_ROUNDS) # Store it globally in the session subsession.session.vars['pay_round'] = pay_round # Group players randomly while keeping ID in group fixed subsession.group_randomly(fixed_id_in_group=True) class Group(BaseGroup): pass class Player(BasePlayer): price = models.CurrencyField( min=C.min_price, max=C.max_price, doc="""Price player offers to sell product for""", label=f"Please enter an amount from {C.min_price} to {C.max_price} as your price:", ) num_winner = models.IntegerField(initial=0) num_winner_b1 = models.IntegerField(initial=0) num_winner_b2 = models.IntegerField(initial=0) num_winner_b3 = models.IntegerField(initial=0) cost = models.CurrencyField() n1 = models.IntegerField(initial=0) n2 = models.IntegerField(initial=0) n3 = models.IntegerField(initial=0) final_pay = models.CurrencyField(initial=0) pay_round = models.IntegerField(initial=0) # FUNCTIONS def set_payoffs(group: Group): players = group.get_players() # Initial setup if C.baseline: # captive consumers demand for p in players: p.payoff = (p.price - p.cost) * C.num_buyer_1 * C.unit_demand / C.PLAYERS_PER_GROUP p.num_winner = int(C.num_buyer_1 * C.unit_demand / C.PLAYERS_PER_GROUP) p.num_winner_b1 += p.num_winner # type-2 consumers demand sampled_firms = [list(k) for k in itertools.combinations(players, 2)] for s in sampled_firms: winning_price = min(p.price for p in s) winners = [p for p in s if p.price == winning_price] num_buyers_per_comb = comb(int(C.PLAYERS_PER_GROUP), len(s)) for winner in winners: units_sold = C.unit_demand * C.num_buyer_2 / (num_buyers_per_comb * len(winners)) # the number of winner.payoff += (winner.price - winner.cost) * units_sold winner.num_winner_b2 += int(units_sold) winner.num_winner += int(units_sold) # Treatment 1, captive consumers are more informed if C.cap_more_info: sampled_firms = [list(k) for k in itertools.combinations(players, 2)] for s in sampled_firms: winning_price = min(p.price for p in s) winners = [p for p in s if p.price == winning_price] num_buyers_per_comb = comb(int(C.PLAYERS_PER_GROUP), len(s)) for winner in winners: units_sold = C.unit_demand * C.num_buyer_2 / (num_buyers_per_comb * len(winners)) winner.payoff += (winner.price - winner.cost) * units_sold winner.num_winner_b2 += int(units_sold) winner.num_winner += int(units_sold) sampled_firms = [list(k) for k in itertools.combinations(players, 3)] for s in sampled_firms: winning_price = min(p.price for p in s) winners = [p for p in s if p.price == winning_price] num_buyers_per_comb = comb(int(C.PLAYERS_PER_GROUP), len(s)) for winner in winners: units_sold = C.unit_demand * C.num_buyer_3 / (num_buyers_per_comb * len(winners)) winner.payoff += (winner.price - winner.cost) * units_sold winner.num_winner_b3 += int(units_sold) winner.num_winner += int(units_sold) # Treatment 2, non-captive consumers are more informed if C.ncap_more_info: for p in players: p.payoff = (p.price - p.cost) * C.num_buyer_1 * C.unit_demand / C.PLAYERS_PER_GROUP p.num_winner = int(C.num_buyer_1 * C.unit_demand / C.PLAYERS_PER_GROUP) p.num_winner_b1 += p.num_winner sampled_firms = [list(k) for k in itertools.combinations(players, 3)] for s in sampled_firms: winning_price = min(p.price for p in s) winners = [p for p in s if p.price == winning_price] num_buyers_per_comb = comb(int(C.PLAYERS_PER_GROUP), len(s)) for winner in winners: units_sold = C.unit_demand * C.num_buyer_3 / (num_buyers_per_comb * len(winners)) winner.payoff += (winner.price - winner.cost) * units_sold winner.num_winner_b3 += int(units_sold) winner.num_winner += int(units_sold) class PrePricing(Page): def before_next_page(self, timeout_happened): # Assign cost directly using Class C method if C.baseline: self.n1 = int(C.num_buyer_1 / C.PLAYERS_PER_GROUP) self.n2 = int(C.num_buyer_2 * comb(int(C.PLAYERS_PER_GROUP-1), 1) / comb(C.PLAYERS_PER_GROUP, 2)) self.n3 = 0 if C.cap_more_info: self.n1 = 0 self.n2 = int(C.num_buyer_2 * comb(int(C.PLAYERS_PER_GROUP-1), 1) / comb(C.PLAYERS_PER_GROUP, 2)) self.n3 = int(C.num_buyer_3 * comb(int(C.PLAYERS_PER_GROUP-1), 2) / comb(C.PLAYERS_PER_GROUP, 3)) if C.ncap_more_info: self.n1 = int(C.num_buyer_1 / C.PLAYERS_PER_GROUP) self.n2 = 0 self.n3 = int(C.num_buyer_3 * comb(int(C.PLAYERS_PER_GROUP-1), 2) / comb(C.PLAYERS_PER_GROUP, 3)) if self.round_number == 1: self.cost = C.assign_player_cost() else: self.cost = self.in_round(1).cost class Introduction(Page): pass class Pricing(Page): form_model = 'player' form_fields = ['price'] @staticmethod def vars_for_template(player: Player): # Previous 5 rounds (exclude current) start = max(1, player.round_number - 5) last5 = player.in_rounds(start, player.round_number - 1) return dict(last5=last5) class PreResults(WaitPage): after_all_players_arrive = set_payoffs class Results(Page): @staticmethod def vars_for_template(player: Player): # (1) Others this round others = sorted(player.get_others_in_group(), key=lambda q: q.id_in_group) # (2) Previous 5 rounds (exclude current) start = max(1, player.round_number - 5) last5 = player.in_rounds(start, player.round_number - 1) # (3) Current round info is directly from `player` me_now = dict( price=player.price, cost=player.cost, sales=player.num_winner, sales_b1=player.num_winner_b1, sales_b2=player.num_winner_b2, sales_b3=player.num_winner_b3, profit=player.payoff, ) return dict(others=others, last5=last5, me_now=me_now) class Payment(Page): @staticmethod def is_displayed(player: Player): return player.round_number == C.NUM_ROUNDS @staticmethod def before_next_page(player: Player, timeout_happened=False): pr = player.session.vars.get('pay_round', 1) target_round = player.in_round(pr) payoff_val = target_round.payoff or cu(0) bonus_pay = payoff_val / 200 fee_amount = cu(10) total_pay = bonus_pay + fee_amount player.pay_round = pr player.final_pay = total_pay player.participant.payoff = total_pay @staticmethod def vars_for_template(player: Player): pr = player.session.vars.get('pay_round', 1) target_round = player.in_round(pr) payoff_val = target_round.payoff or cu(0) bonus_pay = int(payoff_val / 200) fee_amount = int(cu(10)) total_pay = bonus_pay + fee_amount return dict(pr=pr, final=dict(bonus_pay=bonus_pay, fee=fee_amount, total_pay=total_pay)) page_sequence = [PrePricing, Pricing, PreResults, Results, Payment]