from otree.api import Currency as c, currency_range from ._builtin import Page, WaitPage from .models import Constants, Player, Group from .logger import logger import random import config_collat as cc debug = True from otree_mturk_utils.views import CustomMturkPage, CustomMturkWaitPage """docstring format Method Description: Input: Output: Class Description: Input: Output: """ class Collateral_Offer(CustomMturkPage): """ Description: Collateral_Offer inherits the oTree class Page. The purpose of this page is to ask the borrower for the amount that they are willing to pledge as collateral. Input: Page -(oTree Class) Output: Form Field - (to HTML Collateral_Offer.HTML) """ form_model = 'group' form_fields = ['collateral'] timeout_seconds = Constants.timer def before_next_page(self): """ Description: before_next_page performs the necessary calculations and modifications of variables before the experiment continues. In this case the value of collateral is taken from the borrowers money and held in escrow. Input: Borrower Money - (Global, Stored in models.py, Player, Currency) Collateral - (Global, Stored in models.py, Group, Currency) Value of Collateral - (Global, Stored in models.py, Group, Currency) Output: Collateral - (Player Input) Value of Collateral - (Collateral - Salvage Cost, Min = 0) Borrower Money - (Money - Collateral) """ group = self.group borrower = self.player if self.timeout_happened: group.game_timeout += 1 group.game_timeout_borrower += 1 '''Places borrowers collateral pledge in escrow''' borrower.money -= group.collateral '''Assigns value of collateral as equal to collateral - salvage cost. If for some reason the salvage cost is higher than the collateral its value is assigned to zero.''' group.value_of_collateral = max(0, group.collateral - Constants.salvage_cost) group.lender_dollar_value = str( group.get_players()[0].money.to_real_world_currency(self.session)) group.borrower_dollar_value = str( group.get_players()[1].money.to_real_world_currency(self.session)) def is_displayed(self): """ Description: is_displayed tells oTree which player to show the relevant page to and under what conditions. Input: Player Role - (Global, Stored in models.py, Player) Output: Bool - (to oTree Page class) """ if self.player.role() == 'borrower' and self.participant.vars['is_mobile'] is False: return True else: return False class Loan_Amount(CustomMturkPage): # TODO: Update docstrings to include lender and borrower profit terms """ Description: Loan_Amount inherits the oTree class Page. The purpose of this page is to ask the lender for the amount that they are willing to lend to the borrower. Input: Page -(oTree Class) Output: Form Field - (to HTML Loan_Amount.HTML) """ form_model = 'group' form_fields = ['loan'] timeout_seconds = Constants.timer def before_next_page(self): """ Description: before_next_page performs the necessary calculations and modifications of variables before the experiment continues. In this case the value of the loan is taken from the lender multiplied by productivity and passed to the borrower. Input: Borrower - (Global, Location of Borrower) Lender - (Global, Location of Lender) Lender Money - (Global, Stored in models.py, Player, Currency) Borrower Money - (Global, Stored in models.py, Player, Currency) Expected Return -(Global, Stored in models.py, Player, Currency) Loan - (Global, Stored in models.py, Group, Currency) Productivity - (Global, Stored in models.py, Constants, Integer) Output: Loan - (Player Input) Lender Money - (Money - Loan) Borrower Money - (Money + Loan * Productivity) """ group = self.group lender = self.player if self.timeout_happened: group.game_timeout += 1 group.game_timeout_lender += 1 '''group.get_players returns a list of players''' borrower = group.get_players()[1] """Passes the loan from the lender to the borrower and multiplies it by productivity""" lender.money -= group.loan borrower.money += group.loan * Constants.productivity group.project_return = group.loan * Constants.productivity '''this calculates the requested return''' # TODO: change expected return to requested return group.requested_return = Constants.rate_of_return * group.loan group.expected_borrower_profit = Constants.productivity * group.loan - group.requested_return group.lender_dollar_value = str( group.get_players()[0].money.to_real_world_currency(self.session)) group.borrower_dollar_value = str( group.get_players()[1].money.to_real_world_currency(self.session)) def is_displayed(self): """ Description: is_displayed tells oTree which player to show the relevant page to and under what conditions. Input: Player Role - (Global, Stored in models.py, Player) Output: Bool - (to oTree Page class) """ player = self.player if player.role() == 'lender' and self.group.game_timeout == 0 and self.participant.vars['is_mobile'] is False: return True else: return False class Loan_Package(CustomMturkPage): # package isn't what is being made """ Description: Loan_Package inherits the oTree class Page. This page confirms with the borrower that they want would like to accept the loan package. Input: Page - (oTree Class) Output: Form Field - (to HTML Loan_Package.HTML) """ form_model = 'group' form_fields = ['accept_loan'] timeout_seconds = Constants.timer def before_next_page(self): """ Description: before_next_page performs the necessary calculations and modifications of variables before the experiment continues. In this Case if the Loan package is not accepted borrower and lender money is restored to their endowments. Input: Accept Loan - (Global, Location of Borrower, String) Lender - (Global, Location of Lender) Lender Money - (Global, Stored in models.py, Player, Currency) Borrower Money - (Global, Stored in models.py, Player, Currency) Loan - (Global, Stored in models.py, Group, Currency) Productivity - (Global, Stored in models.py, Constants, Integer) Output: Loan - (Player Input) Lender Money - (Money - Loan) Borrower Money - (Money + Loan * Productivity) """ '''if the loan package isn't accepted restores both players money to their endowments''' group = self.group if self.timeout_happened: group.game_timeout += 1 group.game_timeout_borrower += 1 if group.accept_loan == 'No': for i in group.get_players(): i.money = Constants.wealth group.lender_dollar_value = str( group.get_players()[0].money.to_real_world_currency(self.session)) group.borrower_dollar_value = str( group.get_players()[1].money.to_real_world_currency(self.session)) def vars_for_template(self): """ Description: vars_for_template passes Django variables to the HTML. Input: Rate of Return - (Global, Stored in models.py, Group, Integer) Loan - (Global, Stored in models.py, Group, Currency) Productivity - (Global, Stored in models.py, Constants, Integer) Output: Expected Value - (Loan * Rate of Return) Total Value - (Loan * Productivity) Left Over - (Loan * Productivity - Loan * Rate of Return) Loan Value - (Loan * Productivity) Package - (Dictionary, for Django Document) """ group = self.group Package = {} Package['expected_value'] = (group.loan * Constants.rate_of_return) Package['project_return'] = (group.loan * Constants.productivity) Package['left_over'] = (group.loan * Constants.productivity) - \ (group.loan * Constants.rate_of_return) Package['loan_value'] = group.loan * Constants.productivity return Package def is_displayed(self): """ Description: is_displayed tells oTree which player to show the relevant page to and under what conditions. Input: Player Role - (Global, Stored in models.py, Player) Output: Bool - (to oTree Page class) """ player = self.player if player.role() == 'borrower' and self.group.game_timeout == 0 and self.participant.vars['is_mobile'] is False: return True else: return False class Realize_Return(CustomMturkPage): """ Description: Realize_Return inherits the oTree class Page. On this page the borrower decides how much of their project return they would like to return to the lender. Input: Page - (oTree Class) Output: Form Field - (to HTML Realize_Return.HTML) """ form_model = 'group' form_fields = ['lender_return'] timeout_seconds = Constants.timer def before_next_page(self): """ Description: before_next_page performs the necessary calculations and modifications of variables before the experiment continues. In this case the return to the lender is added to their money and substracted from the borrowers additionally if the treatment has contract enforcement == True satisfaction is determined. Input: Lender - (Global, Location of Lender) Borrower - (Global, Location of Borrower) Lender Money - (Global, Stored in models.py, Player, Currency) Borrower Money - (Global, Stored in models.py, Player, Currency) Loan - (Global, Stored in models.py, Group, Currency) Productivity - (Global, Stored in models.py, Constants, Integer) Lender Return - (Global, Stored in models.py, Group, Currency) Rate of Return - (Global, Stored in models.py, Group, Float) Actual Return - (Global, Stored in models.pu, Group, Float) Output: Lender Money - (Money - Loan) Borrower Money - (Money + Loan * Productivity) """ group = self.group if self.timeout_happened: group.game_timeout += 1 group.game_timeout_borrower += 1 lender = group.get_players()[0] borrower = group.get_players()[1] """passes the money returned by the borrower to the lender""" lender.money += group.lender_return borrower.money -= group.lender_return """assigns the value of the real return""" if group.loan != 0: """without this oTree was rounding the actual return""" lender_return = float(group.lender_return) loan = float(group.loan) group.actual_return = lender_return/loan else: group.actual_return = 0 '''what this does it auto define satisfaction if contract enforcement == True contract enforcement. This is done by checking actual return vs expected rate of return''' group.lender_dollar_value = str( group.get_players()[0].money.to_real_world_currency(self.session)) group.borrower_dollar_value = str( group.get_players()[1].money.to_real_world_currency(self.session)) def is_displayed(self): """ Description: is_displayed tells oTree which player to show the relevant page to and under what conditions. Input: Player Role - (Global, Stored in models.py, Player) Accept Loan - (Global, Stored in models.py, Group, String) Output: Bool - (to oTree Page class) """ group = self.group if group.accept_loan == 'Yes' and self.player.role() == 'borrower' and group.game_timeout == 0 \ and self.participant.vars['is_mobile'] is False: return True else: return False class Collateral_Decision(CustomMturkPage): """ Description: Collateral Decision inherits the oTree class Page. On this page the lender decides whether or not to go after the borrowers collateral. This page is only shown if Contract Enforcement == False. Input: Page - (oTree Class) Output: Form Field - (to HTML Collateral_Decision.HTML) """ if Constants.contract_enforcement == False: form_model = 'group' form_fields = ['satisfied'] timeout_seconds = Constants.timer def before_next_page(self): """ Description: before_next_page performs the necessary calculations and modifications of variables before the experiment continues. In this case it returns the amount of collateral not seized to the borrower. Input: Borrower - (Global, Location of Borrower) Borrower Money - (Global, Stored in models.py, Player, Currency) Satisfied - (Global, Stored in models.py, Group, String) Returned Collateral = (Global, Stored in models.py, Group, Currency) Seized Collateral = (Global, Stored in models.py, Group, Currency) Output: Conditional Satisfied = Yes Borrower Money - (Money + Collateral) Returned Collateral -(Collateral) Seized Collateral -(0) """ # TODO: change seized collateral to recovered collateral group = self.group if self.timeout_happened: group.game_timeout += 1 group.game_timeout_lender += 1 borrower = group.get_players()[1] if Constants.contract_enforcement == True: if group.lender_return >= group.requested_return: group.satisfied = 'Yes' else: group.satisfied = 'No' if group.satisfied == 'Yes': group.returned_collateral = group.collateral group.seized_collateral = 0 borrower.money += group.collateral group.lender_dollar_value = str( group.get_players()[0].money.to_real_world_currency(self.session)) group.borrower_dollar_value = str( group.get_players()[1].money.to_real_world_currency(self.session)) def is_displayed(self): """ Description: is_displayed tells oTree which player to show the relevant page to and under what conditions. Input: Player Role - (Global, Stored in models.py, Player) Accept Loan - (Global, Stored in models.py, Group, String) Contract Enforcement - (Global, Stored in models.py, Constants, Bool) Output: Bool - (to oTree Page class) """ group = self.group if group.accept_loan == 'Yes' and self.player.role() == 'lender' and group.game_timeout == 0: return True else: return False class Seize_Collateral(CustomMturkPage): """ Description: Seize Decision inherits the oTree class Page. On this page the lender decides how much of the of the collateral to seize if Contract Enforcement == True else collateral seizure happens automatically. Input: Page - (oTree Class) Output: Conditional Contract Enforcement == False Form Field - (to HTML Seize_Collateral.HTML) """ timeout_seconds = Constants.timer if Constants.contract_enforcement == False: form_model = 'group' form_fields = ['seized_collateral'] def before_next_page(self): # TODO: update doc strings """ Description: before_next_page performs the necessary calculations and modifications of variables before the experiment continues. In this case it returns the amount of collateral not seized to the borrower. Input: Lender - (Global, Location of Lender) Borrower - (Global, Location of Borrower) Lender Money - (Global, Stored in models.py, Player, Currency) Borrower Money - (Global, Stored in models.py, Player, Currency) Loan - (Global, Stored in models.py, Group, Currency) Lender Return - (Global, Stored in models.py, Group, Currency) Rate of Return - (Global, Stored in models.py, Group, Float) Actual Return - (Global, Stored in models.py, Group, Float) Satisfied - (Global, Stored in models.py, Group, String) Seized Collateral - (Global, Stored in models.py, Group, Currency) Value of Collateral - (Global, Sotred in models.py, Group, Currency) Output: Conditional Contract Enforcement == True Seized Collateral (Value of Collateral Max=Terms of Loan == Fulfilled) Lender Money - (Money - Loan) Borrower Money - (Money + Loan * Productivity) """ group = self.group lender = group.get_players()[0] borrower = group.get_players()[1] if self.timeout_happened: group.game_timeout += 1 group.game_timeout_lender += 1 lender_return_plus_collateral = group.lender_return + group.value_of_collateral '''if contract enforcement isn't true the lender seizes collateral and the amount remaining is returned to the borrower''' if Constants.contract_enforcement == False: lender.money += group.seized_collateral group.returned_collateral = group.value_of_collateral - group.seized_collateral borrower.money += group.value_of_collateral - group.seized_collateral else: if lender_return_plus_collateral <= group.requested_return: group.seized_collateral = group.value_of_collateral group.returned_collateral = 0 lender.money += group.value_of_collateral else: '''if the value of the collateral plus lender return is greater than the expected return the amount necessary to fulfill the terms of the loan are seized and the rest is returned to the borrower''' group.seized_collateral = 0 while group.seized_collateral + group.lender_return < group.requested_return: lender.money += 1 group.seized_collateral += 1 group.returned_collateral = group.value_of_collateral - group.seized_collateral borrower.money += group.value_of_collateral - group.seized_collateral group.lender_dollar_value = str( group.get_players()[0].money.to_real_world_currency(self.session)) group.borrower_dollar_value = str( group.get_players()[1].money.to_real_world_currency(self.session)) def is_displayed(self): """ Description: is_displayed tells oTree which player to show the relevant page to and under what conditions. Input: Satisfied - (Global, Stored in models.py, Group, String) Role - (Global, Stored in models.py, Player, String) Output: Bool - (to oTree Page class) """ group = self.group if group.satisfied == 'No' and self.player.role() == 'lender' \ and group.game_timeout == 0 and self.participant.vars['is_mobile'] is False: return True else: return False class Results(CustomMturkPage): """ Description: Results inherits the oTree class Page. On this page both the lender and borrower are showed the results of the experiment Input: Page - (oTree Class) Output: None """ timeout_seconds = Constants.timer def before_next_page(self): """ Description: before_next_page performs the necessary calculations and modifications of variables before the experiment continues. In this case it assigns the final payoffs. Input: Player - (Global, Stored in models.py, Group) Participant - (oTree Specific Represents Actual Person) Money - (Global, Stored in models.py, Player, Currency) Output: Participant Payoff - (Money) Player Money - (Money) """ """adds the players money to their final payoff: this is what we pay them""" if self.player.role() == 'borrower': self.participant.vars['game_earnings'] = self.group.borrower_money else: if self.player.role() == 'lender': self.participant.vars['game_earnings'] = self.group.lender_money def is_displayed(self): if self.participant.vars['is_mobile'] is False: return True else: return False class WaitForPlayer(WaitPage): """ Description: WaitPage confirms that all of the players have arrived before the it continues. The WaitPage has a secondary function of writing to the log after every page has been completed. Input: WaitPage - (oTree Class) Output: None """ def after_all_players_arrive(self): # TODO: update docstrings """ Description: After all players arrive performs calculations and changes variables before players proceed to the next page. Input: None Output: None """ group = self.group players = self.group.get_players() for player in players: try: player.participant.vars['go_to_the_end'] except KeyError: if group.game_timeout == 1: if group.game_timeout_lender == 1 and group.game_timeout_borrower == 0: group.lender_money = 0 players[0].money = 0 group.borrower_money = max(players[1].money, Constants.wealth) players[1].money = max(players[1].money, Constants.wealth) elif group.game_timeout_lender == 0 and group.game_timeout_borrower == 1: group.lender_money = max(players[0].money, Constants.wealth) players[0].money = max(players[0].money, Constants.wealth) group.borrower_money = 0 players[1].money = 0 else: players[0].money = 0 group.lender_money = 0 players[1].money = 0 group.borrower_money = 0 else: group.lender_money = players[0].money group.borrower_money = players[1].money def logger_var(self, new_vars): """ Description: logger_var takes a list of variables to log and adds them to a predefined dict which it passes to a logger. Input: new_vars - (local, dict) variables_for_logging - (local, dict) Output: to logger (new_vars + variables_for_logging) """ """since these variables are always the same they are 'hard' coded""" variables_for_logging = {'debug': debug, 'role': 'group', 'group_id': Constants.group_id} '''this for loop adds the new variables to the original dict and sends them to the logger. if that key already exists it is overwritten ie for name which changes every time''' for var in new_vars: for key in var.keys(): variables_for_logging.update({key: var[key]}) (logger(variables_for_logging)) parameters = [{'name': 'group_id', 'var': Constants.group_id, 'role': 'parameter'}, {'name': 'rate_of_return', 'var': Constants.group_id, 'role': 'parameter'}, {'name': 'contract_enforcement', 'var': Constants.contract_enforcement, 'role': 'parameter'}, {'name': 'wealth', 'var': Constants.wealth, 'role': 'parameter'}, {'name': 'salvage_cost', 'var': Constants.salvage_cost, 'role': 'parameter'}] group_vars = [ {'name': Group.collateral.field_name, 'var': group.collateral}, {'name': Group.rate_of_return.field_name, 'var': group.rate_of_return}, {'name': Group.loan.field_name, 'var': group.loan}, {'name': Group.lender_return.field_name, 'var': group.lender_return}, {'name': Group.actual_return.field_name, 'var': group.actual_return}, {'name': Group.accept_loan.field_name, 'var': group.accept_loan}, {'name': Group.value_of_collateral.field_name, 'var': group.value_of_collateral}, {'name': Group.satisfied.field_name, 'var': group.satisfied}, {'name': Group.seized_collateral.field_name, 'var': group.seized_collateral} ] logger_var(self, group_vars) for player in players: player_vars = [{'name': Player.money.field_name + player.role(), 'var': player.money, 'role': player.role()}] logger_var(self, player_vars) def is_displayed(self): if self.participant.vars['is_mobile'] is False: return True else: return False class GroupWaitPage(CustomMturkWaitPage): group_by_arrival_time = True startwp_timer = 600 def get_players_for_group(self, waiting_players): print('in get_players_for_group', (waiting_players)) for p in waiting_players: lender_players = [p for p in waiting_players if p.participant.vars['role'] == 'lender'] borrower_players = [ p for p in waiting_players if p.participant.vars['role'] == 'borrower'] if len(lender_players) >= 1 and len(borrower_players) >= 1: print('about to create a group') return [lender_players[0], borrower_players[0]] # FULL PAGE SEQUENCE page_sequence = [ GroupWaitPage, Collateral_Offer, WaitForPlayer, Loan_Amount, WaitForPlayer, Loan_Package, WaitForPlayer, Realize_Return, WaitForPlayer, Collateral_Decision, WaitForPlayer, Seize_Collateral, WaitForPlayer, Results ] # removed from game """ class Determine_Rate_of_Return(Page): Description: Determine_Rate_of_Return inherits the oTree class Page. The purpose of this page is to ask the lender for their desired rate of return from a loan. Input: Page - (oTree Class) Output: Form Field - (to HTML Determine_Rate_of_Return.HTML) form_model = 'group' form_fields = ['rate_of_return'] def is_displayed(self): Description: is_displayed tells oTree which player to show the relevant page to and under what conditions. Input: Player Role - (Global, Stored in models.py, Player) Output: Bool - (to oTree Page class) if self.player.role() == 'lender': return True else: return False """