from otree.api import ( models, widgets, BaseConstants, BaseSubsession, BaseGroup, BasePlayer, Currency as c, currency_range, ) import random # なにかコードについて質問がある場合は、長瀬までご連絡ください。 # メールアドレスは、odenshacho@icloud.comです class Constants(BaseConstants): # 実験appと同じ名前にする name_in_url = 'carbon_a' # 何人1組にするか設定 players_per_group = 4 # ラウンド数の設定 # なおターンという概念がotreeにないので、ラウンドごとに処理を分けて実質ターンを作れば良い。 num_rounds = 6 # 実験説明用にincludeするテンプレートファイルの場所を指定 # 通常は、実験appの名前/instructions.htmlとし、instructions.htmlというテンプレートファイルをつくる instructions_template = 'carbon_a/instructions.html' # 政府から配布される排出枠の上限 gov_co2 = [400,400,300,300,200,200] # 政府から配布される排出枠の上限 2022年度バージョン。最終的にはこっちを使いたい Jyogen_list = [400,400,300,300,200,200] #売り上げ生産能力 2022年度追加機能 Unit_price_list = [150,150,120,120] # 各入札者の資本金 shihonkin = 30000 # 技術導入に対する投資額 invest_price = 3500 # 生産における必要枠数それぞれ expand_need = 120 normal_need = 100 shrink_need = 80 non_need = 0 # 生産における必要枠数それぞれ(チケット使用時) expand_need_ticket = 96 normal_need_ticket = 80 shrink_need_ticket = 64 non_need_ticket = 0 # 生産における売り上げそれぞれ expand_profit = expand_need * Unit_price_list normal_profit = normal_need* Unit_price_list shrink_profit = shrink_need* Unit_price_list non_profit = 0 # 辞書の指定した値のキーをリストとして取り出す関数 def get_keys_by_value(dict, value): return [key for key, val in dict.items() if val == value] class Subsession(BaseSubsession): game_app = models.StringField(initial="均一") Jyogen = models.IntegerField() def creating_session(self): self.Jyogen = Constants.Jyogen_list[self.round_number-1] import random Unit_list = random.sample(Constants.Unit_price_list, 4) for p in self.get_players(): if self.round_number == 1: p.unit_price_ability = Unit_list[p.id_in_group-1] else: p.unit_price_ability = p.in_round(1).unit_price_ability class Group(BaseGroup): # 1200枠超えてたらここをTrueに変える can_haibun = models.BooleanField(initial=False) # 全員で何枠入札してるか数えたやつ total_purchase_num = models.IntegerField(initial=0) # 政府から配布される排出枠の上限 current_gov_co2 = models.IntegerField(initial=Constants.gov_co2) # 均衡価格 kinko_price = models.IntegerField() # 枠トレードが起こったかどうかフラグ waku_trade_flag = models.BooleanField(initial=False) # 企業ごとの初期配分量を決定するための処理 def do_shoki_haibun(self): # データベースからプレイヤーのデータを持ってくる players = self.get_players() # playersの中身[P1, P2, P3, P4] # 変数定義 purchase_price_lst = [] purchase_player_id = [] purchase_num_lst = [] # priceでまとめたpurchase_num辞書 purchase_priceSum_num_dict = {} for player in players: purchase_price_lst.append(player.purchase_price) purchase_player_id.append(player.id_in_group) purchase_num_lst.append(player.purchase_num) if player.purchase_price in purchase_priceSum_num_dict: purchase_priceSum_num_dict[player.purchase_price] += player.purchase_num else: purchase_priceSum_num_dict[player.purchase_price] = player.purchase_num # この時点でpurchase_num_lstの中身は、[P1の希望枠数,P2の希望枠数,P3の希望枠数,P4の希望枠数] # sum関数を使ってリストの要素をすべて足し合わせ、total_purchase_numに代入 self.total_purchase_num = sum(purchase_num_lst) # 1200枠以上あれば初期配分可能、そうじゃなかったらやり直し。 # pages.pyでcan_haibunがTrueのとき初期配分ページをすべてスキップするようにしている。 if self.total_purchase_num >= Constants.gov_co2: self.can_haibun = True else: # このFalseに特に意味はないのですが、こうするとこれ以下の関数の処理を強制的に行わないようにできる。 return False # 1200枠以上になってて配分が可能だったら下の処理が走る。 # price高い順でソート purchase_priceSum_num_takaijun_dict = dict(sorted(purchase_priceSum_num_dict.items(), key=lambda x:x[0], reverse=True)) # デバック用 print("do_shokihaibun関数") print("purchase_priceSum_num_takaijun_dict") print(purchase_priceSum_num_takaijun_dict) # 1200枠になったときの価格を調べる用変数定義 count = 0 self.kinko_price = 0 buyers_price_takaijun = [] # 均衡価格の計算 for price, num in purchase_priceSum_num_takaijun_dict.items(): count += num buyers_price_takaijun.append(price) if count >= Constants.gov_co2: self.kinko_price = price break print("buyers_price_takaijun") print(buyers_price_takaijun) # debug用 debug_count = 0 # 初期配分の計算 for price in buyers_price_takaijun: debug_count += 1 print(str(debug_count)+"週目") # 残り枠がない場合計算を抜ける if self.current_gov_co2 == 0: break # 同じ価格をつけている人がいるかいないかで場合分け if purchase_price_lst.count(price) == 1: idx = purchase_price_lst.index(price) player_id = purchase_player_id[idx] player = self.get_player_by_id(player_id) if self.current_gov_co2 >= player.purchase_num: player.shojikin -= self.kinko_price * player.purchase_num player.shoki_haibun = player.purchase_num self.current_gov_co2 -= player.purchase_num else: player.shojikin -= self.kinko_price * self.current_gov_co2 player.shoki_haibun = self.current_gov_co2 self.current_gov_co2 -= player.purchase_num print("player") print(player) else: buyers = [] buyers_total_purchase_num = 0 for idx, price2 in enumerate(purchase_price_lst): if price2 == price: player_id = purchase_player_id[idx] buyer = self.get_player_by_id(player_id) buyers.append(buyer) buyers_total_purchase_num += buyer.purchase_num if self.current_gov_co2 >= buyers_total_purchase_num: for player in buyers: player.shojikin -= self.kinko_price * player.purchase_num player.shoki_haibun = player.purchase_num self.current_gov_co2 -= player.purchase_num else: bunbo = buyers_total_purchase_num now_gov_waku = self.current_gov_co2 for player in buyers: true_purchase_num = now_gov_waku * player.purchase_num // bunbo player.shojikin -= self.kinko_price * true_purchase_num player.shoki_haibun = true_purchase_num self.current_gov_co2 -= true_purchase_num # 枠が余っていたらプレイヤーにランダムで余りを売らせる if self.current_gov_co2 > 0: p = random.choice(players) p.shoki_haibun += self.current_gov_co2 p.shojikin -= self.kinko_price * self.current_gov_co2 # 誰にランダムでいくつ余り割り振ったか保存 p.took_amari = self.current_gov_co2 self.current_gov_co2 = 0 # 初期配分量を現在の枠数のところに代入 for player in players: player.waku_num = player.shoki_haibun def do_emission_trade(self): # 枠トレード前の状態を保存 self.save_desire() players = self.get_players() # playersの中身[P1, P2, P3, P4,] # 買いたい人と売りたい人がそれぞれ一人以上いないとトレードできないので関数を抜けるようにする emission_lst = [] for player in players: player_choice = player.emission emission_lst.append(player_choice) if not ('買う' in emission_lst and '売る' in emission_lst): return False # 買う側の変数定義 buy_price_lst = [] buy_player_id = [] buy_priceSum_num_dict = {} for player in players: if player.buy_num > 0: buy_price_lst.append(player.buy_price) buy_player_id.append(player.id_in_group) if player.buy_price in buy_priceSum_num_dict: buy_priceSum_num_dict[player.buy_price] += player.buy_num else: buy_priceSum_num_dict[player.buy_price] = player.buy_num # 買う数に0を入れた場合リストが空になって後にエラーが出るのでここで回避 if len(buy_price_lst) == 0: return False # 売る側の変数定義 sell_price_lst = [] sell_player_id = [] sell_priceSum_num_dict = {} for player in players: if player.sell_num > 0: sell_price_lst.append(player.sell_price) sell_player_id.append(player.id_in_group) if player.sell_price in sell_priceSum_num_dict: sell_priceSum_num_dict[player.sell_price] += player.sell_num else: sell_priceSum_num_dict[player.sell_price] = player.sell_num # 売る数に0を入れた場合リストが空になって後にエラーが出るのでここで回避 if len(sell_price_lst) == 0: return False # 辞書作成 buy_id_price_dict = {} for idx, price in enumerate(buy_price_lst): buy_id_price_dict[buy_player_id[idx]] = price # price高い順でソート buy_priceSum_num_takaijun_dict = dict(sorted(buy_priceSum_num_dict.items(), key=lambda x:x[0], reverse=True)) # デバッグ用 print("デバッグ用買う側") print("buy_price_lst") print(buy_price_lst) print("buy_player_id") print(buy_player_id) print("buy_id_price_dict") print(buy_id_price_dict) print("buy_priceSum_num_takaijun_dict") print(buy_priceSum_num_takaijun_dict) # 辞書作成 sell_id_price_dict = {} for idx, price in enumerate(sell_price_lst): sell_id_price_dict[sell_player_id[idx]] = price # price低い順でソート sell_priceSum_num_hikuijun_dict = dict(sorted(sell_priceSum_num_dict.items(), key=lambda x:x[0])) # デバッグ用 print("デバッグ用売る側") print("sell_price_lst") print(sell_price_lst) print("sell_player_id") print(sell_player_id) print("sell_id_price_dict") print(sell_id_price_dict) print("sell_priceSum_num_hikuijun_dict") print(sell_priceSum_num_hikuijun_dict) buy_num_priceSumTakaijun_lst = list(buy_priceSum_num_takaijun_dict.values()) sell_num_priceSumHikuijun_lst = list(sell_priceSum_num_hikuijun_dict.values()) buy_price_priceSumTakaijun_lst = list(buy_priceSum_num_takaijun_dict.keys()) sell_price_priceSumHikuijun_lst = list(sell_priceSum_num_hikuijun_dict.keys()) # 必要条件を満たしているかチェック if sell_price_priceSumHikuijun_lst[0] > buy_price_priceSumTakaijun_lst[0]: return False ''' # 必要条件について 例えば、最低価格100円、120円で売りたいと言ってる人が1人ずついて、 買いたい2人が、最高価格90円、80円で買いたいといっているとする。 買いたいと言っている人たちが提示している最高価格の最大値(この場合90円)が、 売りたいと言っている人たちが提示している最低価格の最小値(この場合100円)を上回らないと絶対成立しない。 売り手の提示価格の最小値<買い手の提示価格の最大値がTrueのときだけ処理をする。 ''' sell_idx = 0 buy_idx = 0 count = 1 # 売る量と買う量のリストの値の最後の要素がどちらか片方0になるまでループ while sell_num_priceSumHikuijun_lst[-1] > 0 and buy_num_priceSumTakaijun_lst[-1] > 0: # デバッグ用 print("whileループ"+str(count)+"週目") print("sell_num_priceSumHikuijun_lst") print(sell_num_priceSumHikuijun_lst) print("buy_num_priceSumTakaijun_lst") print(buy_num_priceSumTakaijun_lst) # 売る量が買う量よりも少ない場合 if sell_num_priceSumHikuijun_lst[sell_idx] <= buy_num_priceSumTakaijun_lst[buy_idx]: # 買い手、売り手の提示価格 buy_price_offer = list(buy_priceSum_num_takaijun_dict.keys())[buy_idx] sell_price_offer = list(sell_priceSum_num_hikuijun_dict.keys())[sell_idx] # 取引をする枠数 trade_num = sell_num_priceSumHikuijun_lst[sell_idx] trade_num_shoki = trade_num # 取引価格 trade_price = (sell_price_priceSumHikuijun_lst[sell_idx] + buy_price_priceSumTakaijun_lst[buy_idx])//2 # 買い手と売り手それぞれのリスト buy_player_ids = Constants.get_keys_by_value(dict=buy_id_price_dict, value=buy_price_offer) sell_player_ids = Constants.get_keys_by_value(dict=sell_id_price_dict, value=sell_price_offer) buy_players = [self.get_player_by_id(p_id) for p_id in buy_player_ids] sell_players = [self.get_player_by_id(p_id) for p_id in sell_player_ids] # トレードに際しての比率を計算するようの分母を設定 bunbo = buy_num_priceSumTakaijun_lst[buy_idx] print("buy_players") print(buy_players) # 購入側の処理 for buy_player in buy_players: true_buy_num = trade_num_shoki * buy_player.buy_num // bunbo print("true_buy_num") print(true_buy_num) buy_player.waku_num += true_buy_num buy_player.shojikin -= trade_price * true_buy_num buy_player.buy_num -= true_buy_num trade_num -= true_buy_num # 売る側の処理 for sell_player in sell_players: sell_player.waku_num -= sell_player.sell_num sell_player.shojikin += trade_price * sell_player.sell_num sell_player.sell_num = 0 # 購入するときの比率計算で余りが出ている場合 if trade_num > 0: # 同じ価格をつけているプレイヤーにランダムで余りを付与する p = random.choice(buy_players) p.waku_num += trade_num p.buy_num -= trade_num p.shojikin -= trade_price * trade_num buy_num_priceSumTakaijun_lst[buy_idx] -= trade_num_shoki sell_idx += 1 # 売る量が買う量よりも多い場合 elif sell_num_priceSumHikuijun_lst[sell_idx] > buy_num_priceSumTakaijun_lst[buy_idx]: # 買い手、売り手の提示価格 buy_price_offer = list(buy_priceSum_num_takaijun_dict.keys())[buy_idx] sell_price_offer = list(sell_priceSum_num_hikuijun_dict.keys())[sell_idx] # 取引をする枠数 trade_num = buy_num_priceSumTakaijun_lst[buy_idx] trade_num_shoki = trade_num # 取引価格 trade_price = (sell_price_priceSumHikuijun_lst[sell_idx] + buy_price_priceSumTakaijun_lst[buy_idx])//2 # 買い手と売り手それぞれのリスト buy_player_ids = Constants.get_keys_by_value(dict=buy_id_price_dict, value=buy_price_offer) sell_player_ids = Constants.get_keys_by_value(dict=sell_id_price_dict, value=sell_price_offer) buy_players = [self.get_player_by_id(p_id) for p_id in buy_player_ids] sell_players = [self.get_player_by_id(p_id) for p_id in sell_player_ids] # トレードに際しての比率を計算するようの分母を設定 bunbo = sell_num_priceSumHikuijun_lst[sell_idx] # 購入側の処理 for buy_player in buy_players: buy_player.waku_num += buy_player.buy_num buy_player.shojikin -= buy_player.buy_num * trade_price buy_player.buy_num = 0 # 売る側の処理 for sell_player in sell_players: true_sell_num = trade_num_shoki * sell_player.sell_num // bunbo print("true_sell_num") print(true_sell_num) sell_player.waku_num -= true_sell_num sell_player.shojikin += trade_price * true_sell_num sell_player.sell_num -= true_sell_num trade_num -= true_sell_num # 売るときの比率計算で余りが出ている場合 if trade_num > 0: # 同じ価格をつけているプレイヤーにランダムで余りを売らせる p = random.choice(sell_players) p.waku_num -= trade_num p.sell_num -= trade_num p.shojikin += trade_price * trade_num sell_num_priceSumHikuijun_lst[sell_idx] -= trade_num_shoki buy_idx += 1 # 終了条件 if buy_idx == len(buy_num_priceSumTakaijun_lst): break if sell_idx == len(sell_num_priceSumHikuijun_lst): break count += 1 # 実際の購買量を計算 self.cal_nums() # 枠トレードが起こったことを保存 waku_trade_flag = True # 枠の売買のときの最初の希望価格、希望数を保存する関数 def save_desire(self): players = self.get_players() for p in players: p.desired_buy_num = p.buy_num p.desired_buy_price = p.buy_price p.desired_sell_num = p.sell_num p.desired_sell_price = p.sell_price p.shojikin_before_trade = p.shojikin # 実際に買った量や売った量を計算する関数 def cal_nums(self): players = self.get_players() for p in players: p.bought_num = p.desired_buy_num - p.buy_num p.sold_num = p.desired_sell_num - p.sell_num class Player(BasePlayer): # 資産(いわゆる最終の利得になる部分) shisan = models.IntegerField(initial=0) # 個人ごとの売り上げ生産能力 unit_price_ability = models.IntegerField() # 排出枠に対する入札量 purchase_num = models.IntegerField(label='') # 排出枠に対する入札価格 purchase_price = models.IntegerField(label='') # 初期配分量 shoki_haibun = models.IntegerField(initial=0) # 余りを受け取った人は余り枠数表示 took_amari = models.IntegerField(initial=0) # 各入札者の所持金 shojikin = models.IntegerField(initial=Constants.shihonkin) # 所持金枠トレード前 shojikin_before_trade=models.IntegerField() # 現在の枠数 waku_num = models.IntegerField() '''枠トレード前の状態を保存するフィールド''' # 枠を買う量(保存用) desired_buy_num=models.IntegerField() # 枠を買う価格(保存用) desired_buy_price=models.IntegerField() # 枠を売る量(保存用) desired_sell_num=models.IntegerField() # 枠を売る価格(保存用) desired_sell_price=models.IntegerField() # 実際買った量 bought_num = models.IntegerField(initial=0) # 実際売った量 sold_num = models.IntegerField(initial=0) # 枠を買う量 buy_num=models.IntegerField(min=0,label='',) # 枠を買う価格 buy_price=models.IntegerField(min=0,label='',) # 枠を売る量 sell_num=models.IntegerField(min=0,label='',) # 枠を売る価格 sell_price=models.IntegerField(min=0,label='',) invest = models.StringField( choices=[['はい', 'はい'], ['いいえ', 'いいえ']], label='', widget=widgets.RadioSelectHorizontal, ) seisan = models.StringField( choices=[['拡大', '拡大生産'], ['通常', '通常生産'], ['縮小', '縮小生産'], ['しない', '生産しない']], label='', widget=widgets.RadioSelectHorizontal, ) emission = models.StringField( choices=[['買う', '排出枠を買う'], ['しない', '売買しない'], ['売る', '排出枠を売る'],], label='', widget=widgets.RadioSelectHorizontal, )