from otree.api import ( models, widgets, BaseConstants, BaseSubsession, BaseGroup, BasePlayer, Currency as c, currency_range ) import random import json from django.db import models as djmodels from django.db.models import F, Q, Sum, ExpressionWrapper from django.db.models.signals import post_save, pre_save from django.utils.safestring import mark_safe from django.template.loader import render_to_string from django.core.exceptions import ObjectDoesNotExist from channels import Group as ChannelGroup from django.contrib.auth import authenticate, login from django.template import RequestContext from django.db.models.query import QuerySet from pprint import PrettyPrinter from .exceptions import NotEnoughFunds, NotEnoughItemsToSell ,NoEndowment ,NoItems author = '' def dprint(object, stream=None, indent=1, width=80, depth=None): """ A small addition to pprint that converts any Django model objects to dictionaries so they print prettier. h3. Example usage >>> dprint(Dummy.objects.all().latest()) {'first_name': u'Ben', 'last_name': u'Welsh', 'city': u'Los Angeles', 'slug': u'ben-welsh', """ # Catch any singleton Django model object that might get passed in if getattr(object, '__metaclass__', None): if object.__metaclass__.__name__ == 'ModelBase': # Convert it to a dictionary object = object.__dict__ # Catch any Django QuerySets that might get passed in elif isinstance(object, QuerySet): # Convert it to a list of dictionaries object = [i.__dict__ for i in object] # Pass everything through pprint in the typical way printer = PrettyPrinter(stream=stream, indent=indent, width=width, depth=depth) printer.pprint(object) class Constants(BaseConstants): name_in_url = 'double_auction' players_per_group = None num_rounds = 40 multiple_unit_trading = False price_max_numbers = 10 price_digits = 2 initial_quantity = 1 seller_endowment = 5 buyer_dollar_endowment = 100.00 buyer_btc_endowment = 100.00 variable_params = ('num_sellers', 'num_buyers', 'units_per_seller', 'units_per_buyer', 'time_per_round', 'time_per_round_currency', 'drop_player_on', 'currency_exchange_on', 'inflation_on', 'inflation_rate', 'sequence_0', 'sequence_1', 'sequence_2', 'sequence_3', 'sequence_4', 'paid_sequence_1', 'paid_sequence_2', ) instructions_template = 'double_auction/Instructions.html' class Subsession(BaseSubsession): num_sellers = models.IntegerField() num_buyers = models.IntegerField() units_per_seller = models.IntegerField() units_per_buyer = models.IntegerField() time_per_round = models.IntegerField() time_per_round_currency = models.IntegerField() drop_player_on = models.IntegerField() currency_exchange_on = models.IntegerField() inflation_on = models.IntegerField() inflation_rate = models.FloatField() sequence_0 = models.IntegerField() sequence_1 = models.IntegerField() sequence_2 = models.IntegerField() sequence_3 = models.IntegerField() sequence_4 = models.IntegerField() paid_sequence_1 = models.IntegerField() paid_sequence_2 = models.IntegerField() # to see variables across apps, they have to be stored in session_vars_dump #session_vars_dump = models.StringField() def set_config(self): for k in Constants.variable_params: setattr(self, k, self.session.config.get(k)) for g in self.get_groups(): g.set_role() def creating_session(self): self.set_config() if self.session.num_participants % (self.num_buyers + self.num_sellers) != 0: raise Exception('Number of participants is not divisible by number of sellers and buyers') #self.session_vars_dump = str(self.session.vars) class Group(BaseGroup): active = models.BooleanField(initial=True) dollar_btc_rate = models.FloatField() btc_dollar_rate = models.FloatField() After_CE = models.BooleanField(initial=False) round_within_sequence = models.IntegerField(initial=1) sequence_number = models.IntegerField(initial=1) total_round = models.IntegerField() drawn_number = models.IntegerField() def get_channel_group_name(self): return 'double_auction_group_{}'.format(self.pk) def get_players_by_role(self, role): return [p for p in self.get_players() if p.role() == role] def get_buyers(self): return self.get_players_by_role('buyer') def get_sellers(self): return self.get_players_by_role('seller') def get_contracts(self,house): return Contract.objects.filter(Q(bid__player__group=self) | Q(ask__player__group=self),Q(bid__BTC_Statement=house) | Q(ask__BTC_Statement=house)) def get_bids(self,house): return Bid.active_statements.filter(player__group=self,BTC_Statement=house).order_by('-price','created_at') def get_asks(self,house): return Ask.active_statements.filter(player__group=self,BTC_Statement=house).order_by('price','created_at') def get_spread_html(self,house): if house == False: return mark_safe(render_to_string('double_auction/includes/spread_to_render.html', { 'group': self, })) else: return mark_safe(render_to_string('double_auction/includes/spread_to_render_btc.html', { 'group': self, })) def no_buyers_left(self,house) -> bool: if house == False: return not any([p.active_dollar for p in self.get_buyers()]) if house == True: return not any([p.active_btc for p in self.get_buyers()]) def no_sellers_left(self,house) -> bool: if house == False: return not any([p.active_dollar for p in self.get_sellers()]) if house == True: return not any([p.active_btc for p in self.get_sellers()]) def is_market_closed(self,house) -> bool: return any(self.no_buyers_left(house), self.no_sellers_left(house)) def best_ask(self): bests = self.get_asks(False).order_by('-price') if bests.exists(): return bests.first() def best_ask_btc(self): bests = self.get_asks(True).order_by('-price') if bests.exists(): return bests.first() def best_bid(self): bests = self.get_bids(False).order_by('price') if bests.exists(): return bests.last() def best_bid_btc(self): bests = self.get_bids(True).order_by('price') if bests.exists(): return bests.last() def presence_check(self): msg = {'market_over': False} house_dollar_inactive = (self.no_buyers_left(False) or self.no_sellers_left(False)) house_btc_inactive = (self.no_buyers_left(True) or self.no_sellers_left(True)) if house_dollar_inactive and house_btc_inactive: self.active = False self.save() msg = {'market_over': True, 'over_message': 'No trading partners remaining'} return msg def exchange_currency(self): total_bitcoin = sum([p.exchange_btc for p in self.get_players()]) total_dollar = sum([p.exchange_dollar for p in self.get_players()]) if total_bitcoin == 0 or total_dollar == 0: self.dollar_btc_rate = 0 self.btc_dollar_rate = 0 else: self.dollar_btc_rate = round(total_dollar/total_bitcoin,2) self.btc_dollar_rate = round(total_bitcoin/total_dollar,2) for p in self.get_players(): p.allocation_dollar += round(p.exchange_btc*self.dollar_btc_rate,2)-p.exchange_dollar p.allocation_btc += round(p.exchange_dollar/self.dollar_btc_rate,2)-p.exchange_btc print("dollar btc exchange RRRRate:", self.dollar_btc_rate) def reactive(self): self.active = True for p in self.get_players(): p.active_dollar = p.is_active(False) p.active_btc = p.is_active(True) house_dollar_inactive = (self.no_buyers_left(False) or self.no_sellers_left(False)) house_btc_inactive = (self.no_buyers_left(True) or self.no_sellers_left(True)) if house_dollar_inactive and house_btc_inactive: self.active = False def clear_ask_bid(self): Bid.active_statements.update(active=False) Ask.active_statements.update(active=False) def set_sequence(self): sequence_list = self.final_round_in_sequence() i = 0 for j in range(len(sequence_list)): if self.round_number > sequence_list[j]: i +=1 self.sequence_number = i if i > 0: self.round_within_sequence = self.round_number - sequence_list[i-1] elif i ==0: self.round_within_sequence = self.round_number ''' if self.round_number > sequence_list[4]: self.sequence_number =999 elif self.round_number > sequence_list[3]: self.sequence_number =4 elif self.round_number > sequence_list[2]: self.sequence_number =3 elif self.round_number > sequence_list[1]: self.sequence_number =2 elif self.round_number > sequence_list[0]: self.sequence_number =1 else: self.sequence_number =0 ''' def is_last_sequence(self): if self.set_total_round() == self.round_number: return 1 else: return 0 def set_total_round(self): sequence_list = self.final_round_in_sequence() self.total_round = sequence_list[-1] def fake_draw(self): # if the sequence ends, draw 1. other wise draw 2-6. if self.round_number in self.final_round_in_sequence(): self.drawn_number = 1 else: self.drawn_number = random.randint(2,6) # say first sequence has 2 round, second sequence has 3 round, then return [2,5] def final_round_in_sequence(self): if self.subsession.sequence_4 != 0: return [self.subsession.sequence_0, self.subsession.sequence_0 + self.subsession.sequence_1, self.subsession.sequence_0 + self.subsession.sequence_1 + self.subsession.sequence_2, self.subsession.sequence_0 + self.subsession.sequence_1 + self.subsession.sequence_2 + self.subsession.sequence_3, self.subsession.sequence_0 + self.subsession.sequence_1 + self.subsession.sequence_2 + self.subsession.sequence_3 + self.subsession.sequence_4, ] elif self.subsession.sequence_4 == 0: return [self.subsession.sequence_0, self.subsession.sequence_0 + self.subsession.sequence_1, self.subsession.sequence_0 + self.subsession.sequence_1 + self.subsession.sequence_2, self.subsession.sequence_0 + self.subsession.sequence_1 + self.subsession.sequence_2 + self.subsession.sequence_3, ] def first_round_in_sequence(self): if self.subsession.sequence_4 != 0: return [1, 1 + self.subsession.sequence_0, 1 + self.subsession.sequence_0 + self.subsession.sequence_1, 1 + self.subsession.sequence_0 + self.subsession.sequence_1 + self.subsession.sequence_2, 1 + self.subsession.sequence_0 + self.subsession.sequence_1 + self.subsession.sequence_2 + self.subsession.sequence_3, ] elif self.subsession.sequence_4 == 0: return [1, 1 + self.subsession.sequence_0, 1 + self.subsession.sequence_0 + self.subsession.sequence_1, 1 + self.subsession.sequence_0 + self.subsession.sequence_1 + self.subsession.sequence_2, ] def get_active_groupid(self): active_groupid = [] for p in self.get_players(): if p.disconnection_flag == 0: active_groupid.append(p.id_in_group) return active_groupid def set_role(self): active_groupid = self.get_active_groupid() if len(active_groupid)%2 == 1: print("Error: odd number of active players.") buyer_group = active_groupid[0:int(len(active_groupid)/2)] for p in self.get_players(): if self.round_number in self.first_round_in_sequence(): if p.id_in_group in buyer_group: p.role_code = 'buyer' else: p.role_code = 'seller' else: # careful here. have to guarantee to drop a pair of seller and buyer together every time. if p.in_round(p.round_number - 1).role() == 'seller': p.role_code = 'buyer' else: p.role_code = 'seller' class Player(BasePlayer): active_dollar = models.BooleanField(initial=True) active_btc = models.BooleanField(initial=True) endowment = models.FloatField(initial=0) #payoff_btc = models.CurrencyField(initial=0) units_dollar = models.IntegerField(initial=0) units_btc = models.IntegerField(initial=0) thisround_dollar = models.FloatField(initial=0.0) thisround_btc = models.FloatField(initial=0.0) thisround_profit = models.FloatField(initial=0) thisstage_profit = models.FloatField(initial=0) thissequence_profit = models.FloatField(initial=0) thisround_euro = models.CurrencyField(initial=0) thisstage_euro = models.CurrencyField(initial=0) thissequence_euro = models.CurrencyField(initial=0) # disconnection_flag deals with the cases where someone disconnects # 0 is normal, 1 is disconnection and no respons, 2 is to be dropped to balance the number disconnection_flag = models.IntegerField(initial=0) role_code = models.StringField() no_action1 = models.BooleanField(initial=False) exit_fee = models.CurrencyField(initial=0) drop_sequence = models.IntegerField() #no_action2 = models.StringField(label="Timeout happened. ""Are you still participating?",widget=widgets.RadioSelect, choices=[["Yes", "Yes"]],initial="No") payment_message = models.StringField(initial="") allocation_unit_dollar = models.IntegerField( min=0,max= Constants.seller_endowment, doc="""Number of units to be allocated to Currency A auction house""") allocation_unit_btc = models.IntegerField( min=0,blank=True, doc="""Number of units to be allocated to Currency B auction house""") allocation_dollar = models.FloatField( min=0,max = Constants.buyer_dollar_endowment,initial =Constants.buyer_dollar_endowment, doc="""Amount of Currency A to be allocated to Currency A auction house""") allocation_btc = models.FloatField( min=0,max = Constants.buyer_btc_endowment,initial =Constants.buyer_btc_endowment, doc="""Amount of Currency B to be allocated to Currency B auction house""") allocation_dollar_save = models.FloatField( min=0,blank=True,initial = 0, doc="""Amount of Currency A remaining""") allocation_btc_save = models.FloatField( min=0,blank=True,initial = 0, doc="""Amount of Currency B remaining""") exchange_dollar = models.FloatField( blank =True, doc="""Amount of Currency A to exchange""") exchange_btc = models.FloatField( blank =True, doc="""Amount of Currency B to exchange""") currency_option = models.IntegerField( choices=[[0,'Exchange Currency A for Currency B'], [1,'Exchange Currency B for Currency A'], [2,'No exchange']], widget=widgets.RadioSelect ) def is_display(self): player_active = self.active_btc or self.active_dollar return player_active and self.group.active def exchange_dollar_error_message(self, value): if self.currency_option == 0: if value > self.allocation_dollar: return 'The amount of Currency A to be exchanged cannot be larger than {} '.format(self.allocation_dollar) elif value < 0.01: return 'The amount of Currency A to be exchanged cannot be smaller than {} '.format(0.01) elif value is None: return 'The amount of Currency A to be exchanged cannot be empty' def exchange_btc_error_message(self, value): if self.currency_option == 1: if value > self.allocation_btc: return 'The amount of Currency B to be exchanged cannot be larger than {} '.format(self.allocation_btc) elif value < 0.01: return 'The amount of Currency B to be exchanged cannot be smaller than {} '.format(0.01) elif value is None: return 'The amount of Currency B to be exchanged cannot be empty' def Currency_symbol(self): if self.session.config['experiment_country'].upper() == 'CAD': #return dollar sign return '$' elif self.session.config['experiment_country'].upper() == 'EUR': #return euro sign return '€' def Currency_name(self): if self.session.config['experiment_country'].upper() == 'CAD': # return dollar sign return 'Canadian dollar' elif self.session.config['experiment_country'].upper() == 'EUR': # return euro sign return 'Euro' def Currency_contact_info(self): if self.session.config['experiment_country'].upper() == 'CAD': # return dollar sign return 'email address' elif self.session.config['experiment_country'].upper() == 'EUR': # return euro sign return 'IBAN' def Currency_exchange_on(self): if self.session.config['currency_exchange_on'] == 1: # return dollar sign return 1 elif self.session.config['currency_exchange_on'] == 0: # return euro sign return 0 def purchased_dollar(self): return round(self.exchange_btc * self.group.dollar_btc_rate, 2) def purchased_btc(self): if self.group.dollar_btc_rate != 0 : return round(self.exchange_dollar / self.group.dollar_btc_rate, 2) else: return 0 def previous_btc(self): return self.allocation_btc - self.purchased_btc() + self.exchange_btc def previous_dollar(self): return self.allocation_dollar - self.purchased_dollar() + self.exchange_dollar def count_online_players(self): i = 0 for p in self.group.get_players(): if p.disconnection_flag ==0: i = i+1 return i def drop_one(self): self.disconnection_flag = 1 for i in range(self.round_number,Constants.num_rounds): self.in_round(i).subsession.num_sellers -= 1 self.in_round(i).subsession.num_buyers -= 1 if self.role() == 'buyer': for p in self.group.get_players(): if p.role() == 'seller': p.disconnection_flag =2 p.drop_sequence = p.group.sequence_number if p.group.sequence_number <= 1: p.drop_sequence = 1 for i in range(p.round_number): p.in_round(i + 1).payoff = 0 p.payoff = c(15) elif p.group.sequence_number == 2: p.drop_sequence = 2 for i in range(p.round_number): if p.in_round(i+1).group.sequence_number in [1]: p.in_round(i+1).payoff = p.in_round(i+1).thisround_euro else: p.in_round(i + 1).payoff = 0 p.payoff = c(12) elif p.group.sequence_number >= 3: p.drop_sequence = 3 for i in range(p.round_number): if p.in_round(i+1).group.sequence_number in [1,2]: p.in_round(i+1).payoff = p.in_round(i+1).thisround_euro else: p.in_round(i + 1).payoff = 0 break elif self.role() == 'seller': for p in self.group.get_players(): if p.role() == 'buyer': p.disconnection_flag =2 p.drop_sequence = p.group.sequence_number if p.group.sequence_number <= 1: p.drop_sequence = 1 for i in range(p.round_number): p.in_round(i + 1).payoff = 0 p.payoff = c(15) elif p.group.sequence_number == 2: p.drop_sequence = 2 for i in range(p.round_number): if p.in_round(i+1).group.sequence_number in [1]: p.in_round(i+1).payoff = p.in_round(i+1).thisround_euro else: p.in_round(i + 1).payoff = 0 p.payoff = c(12) elif p.group.sequence_number >= 3: p.drop_sequence = 3 for i in range(p.round_number): if p.in_round(i+1).group.sequence_number in [1,2]: p.in_round(i+1).payoff = p.in_round(i+1).thisround_euro else: p.in_round(i + 1).payoff = 0 break def role(self): if self.role_code == 'buyer': return 'buyer' elif self.role_code == 'seller': return 'seller' def set_units(self,house): items_available = Item.objects.filter(slot__owner=self,slot__BTC=house) if house == False: self.units_dollar= items_available.count() elif house == True: #print("How many items: ", items_available.count(), "in house: ", house) self.units_btc= items_available.count() return items_available.count() def set_this_round_payoff(self): ''' if house == False: contracts = self.get_contracts_queryset(house) #self.payoff = self.allocation_dollar #self.payoff = 0 if contracts: sum_contracts = sum([p.profit for p in contracts]) self.payoff += sum_contracts if house == True: contracts = self.get_contracts_queryset(house) #self.payoff_btc = self.allocation_btc #self.payoff_btc = 0 if contracts: sum_contracts = sum([p.profit_btc for p in contracts]) for p in contracts: print("here is the profit_btc:",p.profit_btc) #self.payoff_btc = self.payoff_btc+sum_contracts self.payoff += sum_contracts ''' #self.payoff = 0 self.thisround_profit = 0 self.thisround_euro = c(0) self.thisstage_profit = 0 #self.thissequence_profit = 0 contracts_dollar = self.get_contracts_queryset(False) if contracts_dollar: sum_contracts_dollar = sum([p.profit for p in contracts_dollar]) #self.payoff += sum_contracts_dollar self.thisstage_profit += sum_contracts_dollar self.thisround_profit += sum_contracts_dollar #self.thissequence_profit += sum_contracts_dollar contracts_btc = self.get_contracts_queryset(True) if contracts_btc: sum_contracts_btc = sum([p.profit for p in contracts_btc]) #self.payoff += sum_contracts_btc self.thisstage_profit += sum_contracts_btc self.thisround_profit += sum_contracts_btc #self.thissequence_profit += sum_contracts_btc self.thisround_euro = c(round((self.thisround_profit)**0.5/10,2)) def set_sequence_payoff(self): self.thissequence_profit += self.thisround_profit self.thissequence_euro += self.thisround_euro def set_payoff(self): self.payoff = 0 list_sequence = self.group.final_round_in_sequence() if self.disconnection_flag != 2: if self.round_number > list_sequence[self.subsession.paid_sequence_1-1] and self.round_number <=list_sequence[self.subsession.paid_sequence_1]: self.payoff = self.thisround_euro if self.round_number > list_sequence[self.subsession.paid_sequence_2 - 1] and self.round_number <= list_sequence[self.subsession.paid_sequence_2]: self.payoff = self.thisround_euro def set_payment_message(self): list_sequence = self.group.final_round_in_sequence() if self.disconnection_flag == 2: if self.drop_sequence == 1: self.payment_message = "Since you are dropped out before Sequence 2, you will be compensated with {}{}.".format(self.Currency_symbol(),c(15)) if self.drop_sequence == 2: sequence_payment_1 = self.in_round(list_sequence[1]).thissequence_euro self.payment_message = "Since you are dropped out in Sequence 2, you will earn euro you made in Sequence 1: {}{} plus a compensation: {}{}.".format(self.Currency_symbol(),sequence_payment_1,self.Currency_symbol(),c(12)) if self.drop_sequence >= 3: sequence_payment_1 = self.in_round(list_sequence[1]).thissequence_euro sequence_payment_2 = self.in_round(list_sequence[2]).thissequence_euro self.payment_message = "Since you are dropped out after Sequence 2, you will earn euro you made in Sequence 1: {}{} and Sequence 2: {}{}.".format(self.Currency_symbol(),sequence_payment_1,self.Currency_symbol(),sequence_payment_2) elif self.disconnection_flag == 1: # give disconnected guy partial pay for any unfinished paid sequence: if self.round_number < list_sequence[self.subsession.paid_sequence_1] and self.round_number > list_sequence[self.subsession.paid_sequence_1-1]: sequence_payment_1 = self.in_round(self.round_number).thissequence_euro else: sequence_payment_1 = self.in_round(list_sequence[self.subsession.paid_sequence_1]).thissequence_euro if self.round_number < list_sequence[self.subsession.paid_sequence_2] and self.round_number > list_sequence[self.subsession.paid_sequence_2-1]: sequence_payment_2 = self.in_round(self.round_number).thissequence_euro else: sequence_payment_2 = self.in_round(list_sequence[self.subsession.paid_sequence_2]).thissequence_euro self.payment_message = "Sequences {} and Sequence {} got selected for your earnings. You made {}{} in Sequences {}, and {}{} in Sequences {}.".format( self.subsession.paid_sequence_1, self.subsession.paid_sequence_2, self.Currency_symbol(), sequence_payment_1, self.subsession.paid_sequence_1, self.Currency_symbol(), sequence_payment_2, self.subsession.paid_sequence_2, ) else: sequence_payment_1 = self.in_round(list_sequence[self.subsession.paid_sequence_1]).thissequence_euro sequence_payment_2 = self.in_round(list_sequence[self.subsession.paid_sequence_2]).thissequence_euro self.payment_message = "Sequences {} and Sequence {} got selected for your earnings. You made {}{} in Sequences {}, and {}{} in Sequences {}.".format( self.subsession.paid_sequence_1, self.subsession.paid_sequence_2, self.Currency_symbol(), sequence_payment_1, self.subsession.paid_sequence_1, self.Currency_symbol(), sequence_payment_2, self.subsession.paid_sequence_2, ) def inherit_sequence_payoff(self): if self.round_number !=1: self.thissequence_profit = self.in_round(self.round_number -1).thissequence_profit self.thissequence_euro = self.in_round(self.round_number -1).thissequence_euro def clear_sequence_payoff(self): if self.round_number in self.group.first_round_in_sequence(): self.thissequence_profit = 0 self.thissequence_euro = 0 def is_active(self,house): if house == False: if self.role() == 'buyer': return self.allocation_dollar > 0 and (self.has_free_slots(house)) else: return (self.get_full_slots(house).exists()) elif house == True: if self.role() == 'buyer': return self.allocation_btc > 0 and (self.has_free_slots(house)) else: return (self.get_full_slots(house).exists() ) def get_items(self): return Item.objects.filter(slot__owner=self) def get_slots(self,house): return self.slots.filter(BTC=house) def has_free_slots(self,house): return self.slots.filter(item__isnull=True,BTC=house).exists() def get_free_slot(self,house): if self.has_free_slots(house): return self.slots.filter(item__isnull=True,BTC=house).order_by('-value').first() def get_full_slots(self,house): return self.slots.filter(item__isnull=False,BTC=house) def presence_check(self): msg = {'market_over': False} either_house_active = self.is_active(False) or self.is_active(True) if not either_house_active: if self.role() == 'buyer': msg = {'market_over': True, 'over_message': 'No funds left for trading'} else: msg = {'market_over': True, 'over_message': 'No items available for trading left'} # update January 23, 2021. allow inactive buyer to stay and watch others msg = {'market_over': False} return msg def get_repo_context(self,house): repository = self.get_slots(house).annotate(quantity=F('item__quantity')) if self.role() == 'seller': r = repository.order_by('cost') else: r = repository.order_by('-value') return r def get_repo_html(self,house): if house == False: return mark_safe(render_to_string('double_auction/includes/repo_to_render.html', { 'repository': self.get_repo_context(house) })) elif house == True: return mark_safe(render_to_string('double_auction/includes/repo_to_render_btc.html', { 'repository_btc': self.get_repo_context(house) })) def get_asks_html(self,house): asks = self.group.get_asks(house) return mark_safe(render_to_string('double_auction/includes/asks_to_render.html', {'asks': asks, 'player': self})) def get_bids_html(self,house): bids = self.group.get_bids(house) return mark_safe(render_to_string('double_auction/includes/bids_to_render.html', { 'bids': bids, 'player': self} )) def get_contracts_queryset(self,house): if house == False: contracts = self.get_contracts(house) if self.role() == 'seller': cost_value = F('cost') #formula = (F('item__contract__price') - cost_value) * F('item__quantity') formula = (cost_value) else: cost_value = F('value') #formula = (cost_value - F('item__contract__price')) * F('item__quantity') formula = (cost_value) r = contracts.annotate(profit=ExpressionWrapper(formula, output_field=models.FloatField()), cost_value=cost_value, ) elif house == True: contracts = self.get_contracts(house) if self.role() == 'seller': cost_value = F('cost') formula = (cost_value) else: cost_value = F('value') formula = (cost_value) r = contracts.annotate(profit=ExpressionWrapper(formula, output_field=models.FloatField()), cost_value=cost_value, ) return r def get_contracts_html(self,house): if house == False: return mark_safe(render_to_string('double_auction/includes/contracts_to_render.html', { 'contracts': self.get_contracts_queryset(house), 'player':self })) elif house == True: return mark_safe(render_to_string('double_auction/includes/contracts_to_render_btc.html', { 'contracts_btc': self.get_contracts_queryset(house), 'player': self })) def get_form_context(self,house): house_dollar_inactive = (self.group.no_buyers_left(False) or self.group.no_sellers_left(False)) house_btc_inactive = (self.group.no_buyers_left(True) or self.group.no_sellers_left(True)) if self.role() == 'buyer' : no_statements = not self.get_bids(house).exists() no_slots_or_funds_dollar = self.allocation_dollar <= 0 or not self.has_free_slots(False) or house_dollar_inactive no_slots_or_funds_btc = self.allocation_btc <= 0 or not self.has_free_slots(True) or house_btc_inactive else: no_slots_or_funds_dollar = not self.get_full_slots(False).exists() or house_dollar_inactive no_slots_or_funds_btc = not self.get_full_slots(True).exists() or house_btc_inactive no_statements = not self.get_asks(house).exists() return {'no_slots_or_funds_dollar': no_slots_or_funds_dollar, 'no_slots_or_funds_btc': no_slots_or_funds_btc, 'no_statements': no_statements, } def get_form_html(self,house): context = self.get_form_context(house) context['player'] = self if house == False: return mark_safe(render_to_string('double_auction/includes/form_to_render.html', context)) else: return mark_safe(render_to_string('double_auction/includes/form_to_render_btc.html', context)) def profit_block_html(self,house): self.set_units(house) if house == False: return mark_safe(render_to_string('double_auction/includes/profit_to_render.html', {'player': self})) elif house == True: return mark_safe(render_to_string('double_auction/includes/profit_to_render_btc.html', {'player': self})) def general_info_block_html(self): return mark_safe(render_to_string('double_auction/includes/general_info_to_render.html', {'player': self,'group': self.group})) # changed on May 14 2020, only get contract in this round def get_contracts(self,house): return Contract.objects.filter(Q(bid__player=self) | Q(ask__player=self),Q(bid__BTC_Statement=house) | Q(ask__BTC_Statement=house),Q(round_contract=self.round_number)) def get_bids(self,house): return Bid.active_statements.filter(player=self,BTC_Statement=house) def get_asks(self,house): return Ask.active_statements.filter(player=self,BTC_Statement=house) # return self.asks.all() def action_name(self): if self.role() == 'buyer': return 'bid' return 'ask' def action_name_reverse(self): if self.role() == 'buyer': return 'ask' return 'bid' def get_last_statement(self): try: if self.role() == 'seller': return self.asks.filter(active=True,BTC_Statement=False).latest('created_at') else: return self.bids.filter(active=True,BTC_Statement=False).latest('created_at') except ObjectDoesNotExist: return def get_last_statement_btc(self): try: if self.role() == 'seller': return self.asks.filter(active=True,BTC_Statement=True).latest('created_at') else: return self.bids.filter(active=True,BTC_Statement=True).latest('created_at') except ObjectDoesNotExist: return def item_to_sell(self,house): full_slots = self.get_full_slots(house).order_by('cost') if full_slots.exists(): return full_slots.first().item def get_personal_channel_name(self): return '{}_{}'.format(self.role(), self.id) ''' def get_participant(self): if self.role() == 'seller': return self.participant.vars['allocation_seller_dollar'] if self.role() == 'buyer': return self.participant.vars['allocation_dollar'] ''' class BaseRecord(djmodels.Model): created_at = models.DateTimeField(auto_now_add=True) updated_at = models.DateTimeField(auto_now=True) player = djmodels.ForeignKey(to=Player, related_name="%(class)ss", ) class Meta: abstract = True class ActiveStatementManager(djmodels.Manager): def get_queryset(self,): return super().get_queryset().filter(active=True) class BaseStatement(BaseRecord): # BTC = 1 if the slot is for bitcoin auction house, 0 if for dollar auction house. BTC_Statement = models.BooleanField(doc='indicator for auction houses') price = djmodels.DecimalField(max_digits=Constants.price_max_numbers, decimal_places=Constants.price_digits) quantity = models.IntegerField() quantity_initial = models.IntegerField() round_statement = models.IntegerField() # initially all bids and asks are active. when the contracts are created with their participation they got passive active = models.BooleanField(initial=True) active_statements = ActiveStatementManager() objects = djmodels.Manager() class Meta: abstract = True def __str__(self): return '{}. Price:{}, Quantity:{}.Quantity_initial:{}. Round:{}. Created at1: {}. Updated at: {}'. \ format(self.__class__.__name__, self.price, self.quantity,self.quantity_initial, self.round_statement, self.created_at, self.updated_at) def as_dict(self): return {'price': str(self.price), 'quantity': str(self.quantity), 'quantity_initial': str(self.quantity_initial), 'round_statement':str(self.round_statement) } class Ask(BaseStatement): """An ask is a request of a price for which an owner is ready to sell the items available in their repository.""" @classmethod def pre_save(cls, sender, instance, *args, **kwargs): items_available = Item.objects.filter(slot__owner=instance.player,slot__BTC=instance.BTC_Statement) num_items_available = items_available.aggregate(num_items=Sum('quantity')) if items_available.count() == 0: raise NotEnoughItemsToSell(instance.player,instance.BTC_Statement,num_items_available['num_items']) if num_items_available['num_items'] < int(instance.quantity): raise NotEnoughItemsToSell(instance.player,instance.BTC_Statement,num_items_available['num_items']) @classmethod def post_save(cls, sender, instance, created, *args, **kwargs): if not created: return group = instance.player.group Ask.active_statements.filter(Q(round_statement=instance.round_statement),Q(player=instance.player),Q(BTC_Statement=instance.BTC_Statement),~Q(pk=instance.id)).update(active=False) #print("loop:",i) #print out the query: #print(bids.query) #print out the query result #print("dprint bid under Ask button") #dprint(Bid.active_statements.filter(player__group=group)) #print("dprint ask under Ask button") #dprint(Ask.active_statements.filter(player__group=group)) bids = Bid.active_statements.filter(round_statement=instance.round_statement,player__group=group, price__gte=instance.price,BTC_Statement=instance.BTC_Statement).order_by('price','created_at') asks = Ask.active_statements.filter(round_statement=instance.round_statement,player__group=group, player=instance.player,BTC_Statement=instance.BTC_Statement).order_by('created_at') while bids.exists() and asks.exists(): bid = bids.last() ## think about it?? ask = asks.last() #print('quantity', ask.quantity, bid.quantity) #print("dprint bid under Ask button") #dprint(Bid.active_statements.filter(player__group=group)) #print("dprint ask under Ask button") #dprint(Ask.active_statements.filter(player__group=group)) if bid.BTC_Statement == False: if bid.player.allocation_dollar < float(bid.price) : raise NotEnoughFunds(bid.player, bid.BTC_Statement, bid.player.allocation_dollar) if bid.BTC_Statement == True: if bid.player.allocation_btc < float(bid.price) : raise NotEnoughFunds(bid.player, bid.BTC_Statement, bid.player.allocation_btc) items_available = Item.objects.filter(slot__owner=ask.player, slot__BTC=ask.BTC_Statement) num_items_available = items_available.aggregate(num_items=Sum('quantity')) if items_available.count() == 0: raise NotEnoughItemsToSell(ask.player, ask.BTC_Statement, num_items_available['num_items']) if num_items_available['num_items'] < int(ask.quantity): raise NotEnoughItemsToSell(ask.player, ask.BTC_Statement, num_items_available['num_items']) item = instance.player.item_to_sell(instance.BTC_Statement) #print("dprint show item detail") #dprint(item) if item: # we convert to float because in the bd decimals are stored as strings (at least in post_save they are) Contract.create(bid=bid, ask=ask, price=min([bid.price, float(ask.price)]), item=item) bids = Bid.active_statements.filter(round_statement=instance.round_statement,player__group=group, price__gte=instance.price,BTC_Statement=instance.BTC_Statement).order_by('price','created_at') asks = Ask.active_statements.filter(round_statement=instance.round_statement,player__group=group, player=instance.player,BTC_Statement=instance.BTC_Statement).order_by('created_at') class Bid(BaseStatement): """A bid is an offer of a price by which a potential buyer is ready to purchase an item.""" @classmethod def pre_save(cls, sender, instance, *args, **kwargs): buyer = instance.player print("buyer",buyer) if instance.BTC_Statement == False: if instance.player.allocation_dollar < float(instance.price) * int(instance.quantity): raise NotEnoughFunds(instance.player,instance.BTC_Statement,instance.player.allocation_dollar) if instance.BTC_Statement == True: if instance.player.allocation_btc < float(instance.price) * int(instance.quantity): raise NotEnoughFunds(instance.player,instance.BTC_Statement,instance.player.allocation_btc) @classmethod def post_save(cls, sender, instance, created, *args, **kwargs): if not created: return group = instance.player.group Bid.active_statements.filter(Q(round_statement=instance.round_statement),Q(player=instance.player),Q(BTC_Statement=instance.BTC_Statement),~Q(pk=instance.id)).update(active=False) #print("dprint bid under Bid button") #dprint(Bid.active_statements.filter(player__group=group)) #print("dprint ask under Bid button") #dprint(Ask.active_statements.filter(player__group=group)) asks = Ask.active_statements.filter(round_statement=instance.round_statement,player__group=group, price__lte=instance.price,BTC_Statement=instance.BTC_Statement).order_by('-price','created_at') bids = Bid.active_statements.filter(round_statement=instance.round_statement,player__group=group, player=instance.player,BTC_Statement=instance.BTC_Statement).order_by('created_at') while asks.exists() and bids.exists(): ask = asks.last() bid = bids.last() if bid.BTC_Statement == False: if bid.player.allocation_dollar < float(bid.price) : raise NotEnoughFunds(bid.player, bid.BTC_Statement, bid.player.allocation_dollar) if bid.BTC_Statement == True: if bid.player.allocation_btc < float(bid.price) : raise NotEnoughFunds(bid.player, bid.BTC_Statement, bid.player.allocation_btc) items_available = Item.objects.filter(slot__owner=ask.player, slot__BTC=ask.BTC_Statement) num_items_available = items_available.aggregate(num_items=Sum('quantity')) if items_available.count() == 0: raise NotEnoughItemsToSell(ask.player, ask.BTC_Statement, num_items_available['num_items']) if num_items_available['num_items'] < int(ask.quantity): raise NotEnoughItemsToSell(ask.player, ask.BTC_Statement, num_items_available['num_items']) #print("dprint ask under Bid button single ask") #dprint(ask) item = ask.player.item_to_sell(bid.BTC_Statement) if item: #print("bid price:",min([float(bid.price), ask.price])) Contract.create(bid=bid, ask=ask, price=min([float(bid.price), ask.price]), item=item) asks = Ask.active_statements.filter(round_statement=instance.round_statement,player__group=group, price__lte=instance.price,BTC_Statement=instance.BTC_Statement).order_by('-price','created_at') bids = Bid.active_statements.filter(round_statement=instance.round_statement,player__group=group, player=instance.player,BTC_Statement=instance.BTC_Statement).order_by('created_at') class Slot(djmodels.Model): """A slot is a space with an associated cost or value (depending on a type of a player (buyer or seller). """ created_at = models.DateTimeField(auto_now_add=True) updated_at = models.DateTimeField(auto_now=True) # BTC = 1 if the slot is for bitcoin auction house, 0 if for dollar auction house. BTC = models.BooleanField(doc='indicator for auction houses') owner = djmodels.ForeignKey(to=Player, related_name="slots", ) cost = models.FloatField(doc='this is defined for sellers only', null=True) value = models.FloatField(doc='for buyers only', null=True) class Item(djmodels.Model): """This is a repository of items people have for trading in the trading sessions.""" created_at = models.DateTimeField(auto_now_add=True) updated_at = models.DateTimeField(auto_now=True) #slot = djmodels.ForeignKey(to=Slot, related_name='item') slot = djmodels.OneToOneField(to=Slot, related_name='item') quantity = models.IntegerField() class Contract(djmodels.Model): """This model contains information about all contracts done during the trading session (aka round).""" created_at = models.DateTimeField(auto_now_add=True) updated_at = models.DateTimeField(auto_now=True) round_contract = models.IntegerField() # the o2o field to item should be reconsidered if we make quantity flexible item = djmodels.OneToOneField(to=Item) #item = djmodels.ForeignKey(to=Item, related_name='item') bid = djmodels.ForeignKey(to=Bid) ask = djmodels.ForeignKey(to=Ask) price = djmodels.DecimalField(max_digits=Constants.price_max_numbers, decimal_places=Constants.price_digits) cost = models.FloatField() value = models.FloatField() def get_seller(self): return self.ask.player def get_buyer(self): return self.bid.player def __str__(self): return '{}. Price:{}, Quantity:{}. BID by: {}. ASK BY: {}'. \ format(self.__class__.__name__, str(self.price), self.item.quantity, self.bid.player.id, self.ask.player.id) @classmethod def create(cls, item, bid, ask, price): buyer = bid.player seller = ask.player cost = item.slot.cost new_slot = buyer.get_free_slot(bid.BTC_Statement) item.slot = new_slot value = new_slot.value contract = cls(round_contract=bid.round_statement, item=item, bid=bid, ask=ask, price=price, cost=cost, value=value) item.save() #print("dprint bid under Contract ") #dprint(Bid.active_statements.filter(player=buyer)) #print("dprint ask under Contract") #dprint(Ask.active_statements.filter(player=seller)) #not deactivate, but reduce quantity by 1 #print("Ask0:",Ask.active_statements.filter(player=seller,BTC_Statement=ask.BTC_Statement).values_list('quantity', flat=True).values()) #print("Ask1:",Ask.active_statements.filter(player=seller,BTC_Statement=ask.BTC_Statement).values_list('quantity', flat=True).get(active=True)) #print("Bid0:",Bid.active_statements.filter(player=buyer,BTC_Statement=bid.BTC_Statement).values_list('quantity', flat=True).get(active=True)) # check whether get() can only return one Bid instance? #ask_quantity = Ask.active_statements.filter(player=seller,BTC_Statement=ask.BTC_Statement).values_list('quantity', flat=True).get(active=True) #bid_quantity = Bid.active_statements.filter(player=buyer,BTC_Statement=bid.BTC_Statement).values_list('quantity', flat=True).get(active=True) print("allocation dollar3", buyer.allocation_dollar) if bid.BTC_Statement == False: buyer.allocation_dollar -= float(contract.price) seller.allocation_dollar += float(contract.price) buyer.thisround_dollar -= float(contract.price) seller.thisround_dollar += float(contract.price) if bid.BTC_Statement == True: buyer.allocation_btc -= float(contract.price) seller.allocation_btc += float(contract.price) buyer.thisround_btc -= float(contract.price) seller.thisround_btc += float(contract.price) buyer.save() seller.save() print("allocation dollar4", buyer.allocation_dollar) Ask.active_statements.filter(id=ask.id).update(quantity=F('quantity') - 1) Bid.active_statements.filter(id=bid.id).update(quantity=F('quantity') - 1) buyer.save() seller.save() #ask_quantity = Ask.active_statements.filter(id=ask.id).order_by('created_at').last().quantity #bid_quantity = Bid.active_statements.filter(id=bid.id).order_by('created_at').last().quantity ask_quantity = int(ask.quantity) bid_quantity = int(bid.quantity) print("ask_quantity",ask_quantity) print("bid_quantity",bid_quantity) if ask_quantity <= 1: Ask.active_statements.filter(id=ask.id).update(active=False) if bid_quantity <= 1: Bid.active_statements.filter(id=bid.id).update(active=False) #print("dprint ask/bid under Contract ") #dprint(Ask.active_statements.filter(player=seller)) #dprint(Bid.active_statements.filter(player=buyer)) contract_parties = [buyer, seller] #print("contract details:") #dprint(contract) #print("item details:") #dprint(Item) contract.save() exante_house_dollar_inactive = (buyer.group.no_buyers_left(False) or buyer.group.no_sellers_left(False)) exante_house_btc_inactive = (buyer.group.no_buyers_left(True) or buyer.group.no_sellers_left(True)) for p in contract_parties: p.set_this_round_payoff() p.set_units(True) p.set_units(False) #give a message if dollar house turn from active to inactive. exante_active_dollar = p.active_dollar p.active_dollar = p.is_active(False) expost_active_dollar = p.active_dollar if exante_active_dollar == True and expost_active_dollar == False: house_name = 'A' if p.role() == 'seller': things = 'package' elif p.role() == 'buyer': things = 'currency' channel = ChannelGroup(p.get_personal_channel_name()) channel.send({'text': json.dumps({'warning': 'You have no {} remaining in Market {}. Market {} is closed.'.format(things,house_name, house_name, house_name) })}) exante_active_btc = p.active_btc p.active_btc = p.is_active(True) expost_active_btc = p.active_btc if exante_active_btc == True and expost_active_btc == False: channel = ChannelGroup(p.get_personal_channel_name()) house_name = 'B' if p.role() == 'seller': things = 'package' elif p.role() == 'buyer': things = 'currency' channel.send({'text': json.dumps({'warning': 'You have no {} remaining in Market {}. Market {} is closed.'.format(things,house_name, house_name) })}) p.save() p_group = ChannelGroup(p.get_personal_channel_name()) p_group.send( {'text': json.dumps({ 'repo': p.get_repo_html(False), 'repo_btc': p.get_repo_html(True), 'contracts': p.get_contracts_html(False), 'contracts_btc': p.get_contracts_html(True), 'form': p.get_form_html(False), 'form_btc': p.get_form_html(True), 'profit': p.profit_block_html(False), 'profit_btc': p.profit_block_html(True), 'general_info': p.general_info_block_html(), 'presence': p.presence_check(), })} ) expost_house_dollar_inactive = (buyer.group.no_buyers_left(False) or buyer.group.no_sellers_left(False)) expost_house_btc_inactive = (buyer.group.no_buyers_left(True) or buyer.group.no_sellers_left(True)) print('see me 1') if exante_house_dollar_inactive == False and expost_house_dollar_inactive == True: channel = ChannelGroup(buyer.group.get_channel_group_name()) channel.send({'text': json.dumps({'warning': {'market_over': 'partialA', 'over_message': 'No trading partners remaining in Market A'} })}) if exante_house_btc_inactive == False and expost_house_btc_inactive == True: channel = ChannelGroup(buyer.group.get_channel_group_name()) channel.send({'text': json.dumps({'warning': {'market_over': 'partialB', 'over_message': 'No trading partners remaining in Market B'} })}) print('see me 2') group = buyer.group group_channel = ChannelGroup(group.get_channel_group_name()) group_channel.send({'text': json.dumps({'presence': group.presence_check()})}) for p in group.get_players(): group_channel = ChannelGroup(p.get_personal_channel_name()) group_channel.send({'text': json.dumps({'asks': p.get_asks_html(False), 'bids': p.get_bids_html(False), 'asks_btc': p.get_asks_html(True), 'bids_btc': p.get_bids_html(True) })}) return contract post_save.connect(Ask.post_save, sender=Ask) post_save.connect(Bid.post_save, sender=Bid) pre_save.connect(Ask.pre_save, sender=Ask) pre_save.connect(Bid.pre_save, sender=Bid)