from otree.api import Currency as c, currency_range from ._builtin import Page, WaitPage from .models import Constants, Group, Player from .logger import logger debug = True class Determine_Rate_of_Return(Page): """requests the lender to specify the return that they would expect off of a loan""" form_model = 'group' form_fields = ['rate_of_return'] def is_displayed(self): """only shows the page to lender""" group = self.group return self.player.id_in_group == 1 class Collateral_offer(Page): """requests a collateral offer from the borrower this is bounded by 0 and the endowment(initial_points ) of the borrower""" form_model = 'group' form_fields = ['collateral'] def before_next_page(self): """this removes the collateral and places it in escrow""" group = self.group borrower = self.player borrower.money -= group.collateral # puts the borrowers money into exrow group.collateral_after_salvage = group.collateral - Constants.recovery_fee # finds the value of the collateral after salvage if group.collateral_after_salvage < 0: """confirms that the collateral after salvage is always worth at least 0""" group.collateral_after_salvage = 0 def is_displayed(self): """only show this page to the borrower""" if self.player.role() == 'borrower': return True class Loan_amount(Page): """Requests a loan amount from lender this loan amount is bounded by 0 and the initial_points of the player""" form_model = 'group' form_fields = ['loan'] def before_next_page(self): """gives the borrower(player2) the amount of money lent * productivity and remove the amount of the loan from the lender(player1)""" group = self.group borrower = group.get_players()[1] #TODO: good place for a comment lender = self.player """removes the money from the lender and passes it to the borrower after multiplication by productivity""" lender.money -= group.loan #store project returns borrower.money += group.loan * Constants.productivity def is_displayed(self): """only shows the page to lender""" if self.player.role() == 'lender': return True class Loan_package(Page):#package isnt what is being made """Presents the loan package to the borrower and asks if they would like to accept the terms or end with their endowment""" form_model = 'group' form_fields = ['accept_loan'] def before_next_page(self): """restores the money for both players to their initial starting initial_points """ group = self.group if group.accept_loan == 'No': for i in group.get_players(): i.money = Constants.initial_points def vars_for_template(self): """passes in variables for the loan package""" group = self.group Var_Temp = {} #change to loan package if debug == True: """print statements for debuging purpose""" print('the amount that the borrower wants back ', (group.loan * Constants.rate_of_return)) print('the total value of the loan ', (group.loan * Constants.productivity)) print('amount that would be left to borrowers ', (group.loan * Constants.productivity) - (group.loan * Constants.rate_of_return)) Var_Temp['expected_value'] = (group.loan * Constants.rate_of_return) Var_Temp['total_value'] = (group.loan * Constants.productivity) Var_Temp['left_over'] = (group.loan * Constants.productivity) - (group.loan * Constants.rate_of_return) Var_Temp['loan_value'] = group.loan * Constants.productivity return Var_Temp def is_displayed(self): """only show this page to the borrower""" if self.player.role() == 'borrower': return True class Realize_Return(Page): form_model = 'group' form_fields = ['repayment'] #posb change name def before_next_page(self): """updates the borrower lenders money to take into accound the returned loan""" group = self.group lender = group.get_players()[0] borrower = group.get_players()[1] """passes the money returned by the borrower to the lender""" lender.money += group.repayment borrower.money -= group.repayment """assings the value of the real return""" if group.loan != 0: """without this otree was rounding the actual return""" repayment = float(group.repayment) loan = float(group.loan) if debug == True: print('this is the amount actually returned ', repayment) print('this is the size of the loan ', loan) print('this is the actual repayment rate', repayment/loan) group.actual_return = repayment/loan else: group.actual_return = 0 def is_displayed(self): """only show this page to the borrower if the loan package was accepted""" group = self.group if group.accept_loan == 'Yes': if self.player.role() == 'borrower': return True class Collateral_decision(Page): #capital D about satis not colat """Requests an answer from the lender about regarding their satifaction with the borrowers return from the loan""" form_model = 'group' form_fields = ['satisfied'] def before_next_page(self): """if satisfaction is True i.e. if the lender is satified with the borrowers performance the collateral is returned from escrow""" group = self.group borrower = group.get_players()[1] lender = self.lender if Constants.contract_enforcement == True: if Constants.rate_of_return * group.loan > group.repayment: group.satisfied = 'No' if group.satisfied == 'Yes': borrower.money += group.collateral def is_displayed(self): """only shows the page to lender if the loan package was accepted""" group = self.group if Constants.contract_enforcement == False: if group.accept_loan == 'Yes': if self.player.role() == 'lender': return True else: return False class Seize_collateral(Page):#captial C """if the lender is not satified with the outcome of the loan this page will display the seizure of collateral""" if Constants.contract_enforcement == False: form_model = 'group' form_fields = ['recovered_collateral'] def before_next_page(self): """updates the lenders money to include the collateral minus from salvage cost""" group = self.group lender = group.get_players()[0] borrower = group.get_players()[1] repayment_plus_collateral = group.repayment + group.collateral_after_salvage expected_return = Constants.rate_of_return * group.loan if group.satisfied == 'No': if debug == True: # Header indicating the format of the functions """print statements for debuging purpose""" print('the cost of salvage ', Constants.recovery_fee) print('the amount of collateral that the lender collects ', group.collateral_after_salvage) """take the value of the collateral after salvage and passes it to the lender""" if Constants.contract_enforcement == False: lender.money += group.recovered_collateral else: print('value of return plus collateral ', group.repayment + group.collateral_after_salvage) print('expected value of loan ', Constants.rate_of_return * group.loan) if repayment_plus_collateral < expected_return: #checks to see if the value of the collateral plus the return is greater than what was expected from the loan #if it is smaller is adds that to the player money group.recovered_collateral = group.collateral_after_salvage lender.money += group.collateral_after_salvage else: group.recovered_collateral = 0 while group.recovered_collateral + group.repayment < expected_return: lender.money += 1 group.recovered_collateral += 1 borrower.money += group.collateral_after_salvage - group.recovered_collateral def vars_for_template(self): """passes in variables from the loan package""" group = self.group return {'expected_return': Constants.rate_of_return*group.loan, 'actual_return': group.actual_return, 'collateral_after_salvage': group.collateral - Constants.recovery_fee} def is_displayed(self): """will only show this page if lender is disasified with the loan and the loan package was initial accepted""" group = self.group if group.satisfied == 'No':#change outter loop if self.player.role() == 'lender': return True else: return False class Results(Page): """displays results""" def vars_for_template(self): group = self.group if debug == True: print('this is the loan ', group.loan) print('this is the repayment rate', Constants.rate_of_return) print('this is the lender return ', group.repayment) print('result was satifacotry to lender? ', group.satisfied) print('loan package was accepted? ', group.accept_loan) return {'expected_return': Constants.rate_of_return*group.loan, 'actual_return': group.actual_return} def before_next_page(self): self.participant.payoff += self.player.money self.player.payoff += self.player.money def vars_for_template(self): loan = self.group.loan role = self.player.role() collateral = self.group.collateral actual_return = self.group.actual_return return {'loan': loan, 'role': role, 'collateral': collateral, 'actual_return': actual_return} class WaitForPlayer(WaitPage): '''wait page and logger. Logging in the Collateral Game: oTree works by printing predefined variables into the console and potentially writing them into a txt file. This uses both functions inside of the pages.py file as well as a function inside of the logger file inside of the pages.py first a list of players is created as well as two lists of dicts that (the first of which contains variables that will always appear in any log, the second of which contains the name and value of the variable we are logging). These dicts are passed to a function that attaches the variables to logging to the standard dict and passes them to a logger function which formats and prints. because some variables are player specific(money) where as others are group specific (repayment rate) this process is done in two separate stages first logging all of the group variables once followed by logging each players variable''' def after_all_players_arrive(self): players = self.group.get_players() def logger_var(self, new_vars): logger_vars = {'debug':debug, 'role': 'group', 'group_id':self.group.id_in_subsession} for i in new_vars.keys(): logger_vars.update({i:new_vars[i]}) return logger_vars def log_var(self, vars): for i in vars: logger(logger_var(self, i)) group_vars = [ {'name':Group.collateral.field_name, 'var':self.group.collateral}, {'name':Constants.rate_of_return.field_name, 'var': self.Constants.rate_of_return}, {'name':Group.loan.field_name, 'var': self.group.loan}, {'name':Group.repayment.field_name, 'var': self.group.repayment}, {'name':Group.actual_return.field_name, 'var': self.group.actual_return}, {'name':Group.accept_loan.field_name, 'var': self.group.accept_loan}, {'name':Group.collateral_after_salvage.field_name, 'var': self.group.collateral_after_salvage}, {'name':Group.satisfied.field_name, 'var': self.group.satisfied}, {'name':Group.recovered_collateral.field_name, 'var': self.group.recovered_collateral} ] log_var(self,group_vars) for player in players: player_vars = [{'name': Player.money.field_name, 'var':player.money, 'role':player.role()}] log_var(self, player_vars) class WaitTreatmentCollateral(WaitPage): '''this is here in order to auto caluclate 'satisfied' for the lender during contract enforcement''' def after_all_players_arrive(self): group = self.group expected_return = Constants.rate_of_return * group.loan if group.accept_loan == 'Yes': if Constants.contract_enforcement == True: if expected_return > group.repayment: group.satisfied = 'No' """determines the ordering of the pages""" page_sequence = [ Determine_Rate_of_Return, WaitForPlayer, Collateral_offer, WaitForPlayer, Loan_amount, WaitForPlayer, Loan_package, WaitForPlayer, Realize_Return, WaitTreatmentCollateral, Collateral_decision, WaitForPlayer, Seize_collateral, WaitForPlayer, Results, WaitForPlayer, ]