from otree.api import ( models, widgets, BaseConstants, BaseSubsession, BaseGroup, BasePlayer, Currency as c, currency_range ) doc = """ The principal offers a contract to the agent, who can decide if to reject or accept. The agent then chooses an effort level. The implementation is based on Gaechter and Koenigstein (2006) . """ class Constants(BaseConstants): name_in_url = 'principal_agent' players_per_group = 2 num_rounds = 1 instructions_template = 'principal_agent/Instructions.html' min_fixed_payment = c(-30) max_fixed_payment = c(30) # """Amount principal gets if contract is rejected""" reject_principal_pay = c(0) reject_agent_pay = c(10) agent_return_share_choices = [ [percent / 100.0, '{}%'.format(percent)] for percent in range(10, 100 + 1, 10)] EFFORT_TO_RETURN = { 1: 14, 2: 28, 3: 42, 4: 54, 5: 70, 6: 84, 7: 98, 8: 112, 9: 126, 10: 140} EFFORT_TO_COST = { 1: 0, 2: 4, 3: 8, 4: 12, 5: 18, 6: 24, 7: 32, 8: 40, 9: 50, 10: 60} def cost_from_effort(effort): return c(Constants.EFFORT_TO_COST[effort]) def return_from_effort(effort): return c(Constants.EFFORT_TO_RETURN[effort]) class Subsession(BaseSubsession): pass class Group(BaseGroup): total_return = models.CurrencyField( doc="""Total return from agent's effort = [Return for single unit of agent's work effort] * [Agent's work effort]""" ) agent_fixed_pay = models.CurrencyField( doc="""Amount offered as fixed pay to agent""", min=Constants.min_fixed_payment, max=Constants.max_fixed_payment, ) agent_return_share = models.FloatField( choices=Constants.agent_return_share_choices, doc="""Agent's share of total return""", widget=widgets.RadioSelectHorizontal ) agent_work_effort = models.IntegerField( choices=range(1, 10 + 1), doc="""Agent's work effort, [1, 10]""", widget=widgets.RadioSelectHorizontal, blank=True # if they reject, they don't have to submit work effort ) agent_work_cost = models.CurrencyField( doc="""Agent's cost of work effort""" ) contract_accepted = models.BooleanField( doc="""Whether agent accepts proposal""", widget=widgets.RadioSelect, choices=[ [True, 'Accept'], [False, 'Reject'], ] ) def set_payoffs(self): principal = self.get_player_by_role('principal') agent = self.get_player_by_role('agent') if self.contract_accepted: self.agent_work_cost = cost_from_effort(self.agent_work_effort) self.total_return = return_from_effort(self.agent_work_effort) money_to_agent = self.agent_return_share * self.total_return + self.agent_fixed_pay agent.payoff = money_to_agent - self.agent_work_cost principal.payoff = self.total_return - money_to_agent else: principal.payoff = Constants.reject_principal_pay agent.payoff = Constants.reject_agent_pay def return_share_as_percentage(self): return int(self.agent_return_share * 100) class Player(BasePlayer): def role(self): if self.id_in_group == 1: return 'principal' if self.id_in_group == 2: return 'agent'