oTree Forum >

Replace sellers with bot

#1 by Rita

Hello everyone,
I want to conduct a market trading experiment with 10 sellers and 10 buyers. The sellers provide product prices and quality, and the buyers make purchasing decisions based on the information provided by the sellers. Now I want to use robots to replace the sellers. What should I do? Here is my code.

from otree.api import *
import random


class Constants(BaseConstants):
    name_in_url = 'trading_experiment2'
    players_per_group = 2
    num_rounds = 3
    SELLER_ROLE = '卖家'
    BUYER_ROLE = '买家'
    room_name = 'room1'
    cost = 10
    quality_average = 65


class Subsession(BaseSubsession):
    def handle_switch_seller(self):
        subsession = self
        if subsession.round_number == 1:
            return
        # 后续轮次根据上一轮买家的选择重新分组
        buyers = [p for p in subsession.get_players() if p.role == Constants.BUYER_ROLE]
        switchers = [buyer for buyer in buyers if buyer.in_previous_rounds()[-1].switch_seller]
        non_switchers = [buyer for buyer in buyers if not buyer.in_previous_rounds()[-1].switch_seller]

        new_groups = []

        # 保持不更换卖家的买家与卖家的分组
        for buyer in non_switchers:
            previous_seller = buyer.in_previous_rounds()[-1].group.get_player_by_role(Constants.SELLER_ROLE)
            new_groups.append([previous_seller, buyer])

        # 对更换卖家的买家进行分组
        switcher_sellers = [buyer.in_previous_rounds()[-1].group.get_player_by_role(Constants.SELLER_ROLE) for buyer in
                            switchers]
        for i, buyer in enumerate(switchers):
            new_seller = switcher_sellers[(i + 1) % len(switcher_sellers)]
            new_groups.append([new_seller, buyer])
        # 设置新的分组
        subsession.set_group_matrix(new_groups)


def creating_session(subsession: Subsession):
    players = subsession.get_players()

    # TODO 按照player.role来判断
    # 为玩家分配角色名称
    for i, player in enumerate(players, start=1):
        if i % 2 == 1:
            player.role_name = f'卖家{(i // 2) + 1}'
        else:
            player.role_name = f'买家{(i // 2)}'

    print(f"Round {subsession.round_number} group matrix: {subsession.get_group_matrix()}")
    # 打印玩家的角色名称以进行调试
    for player in players:
        print(f"Player {player.id_in_subsession}: {player.role_name}")


def make_matrix(n_players, offset):
    p1s = list(range(1, n_players // 2 + 1))
    p2s = list(range(n_players // 2 + 1, n_players + 1))

    # Rotate p2s by the offset
    return list(zip(p1s, p2s[offset:] + p2s[:offset]))


class Group(BaseGroup):
    seller_decision_quality = models.IntegerField()
    seller_decision_price = models.IntegerField()
    seller_credibility = models.IntegerField()


class Player(BasePlayer):
    role_name = models.StringField()
    quality = models.IntegerField(
        label="商品的品质:",
        min=0
    )
    price = models.IntegerField(
        label="商品的价格:",
        min=0
    )
    individual_difference = models.IntegerField()  # 新增随机变量
    purchase_decision = models.BooleanField(
        label="你是否购买这个商品?",
        choices=[
            [True, '购买'],
            [False, '不购买']
        ],
        widget=widgets.RadioSelect
    )

    seller_credibility_rating = models.IntegerField(
        label="您为卖家的可信度打几分(1-5)",
        choices=[1, 2, 3, 4, 5],
        widget=widgets.RadioSelectHorizontal
    )

    willing_to_buy_again = models.IntegerField(
        label="如果可能,您是否还愿意在这家店购买",
        choices=[1, 2, 3, 4, 5],
        widget=widgets.RadioSelectHorizontal
    )

    switch_seller = models.BooleanField(
        label="您是否想要更换卖家?",
        choices=[
            [True, '是'],
            [False, '否']
        ],
        widget=widgets.RadioSelect
    )
    total_buyer_benefit = models.IntegerField(initial=0)  # 买家总收益
    total_seller_benefit = models.IntegerField(initial=0)  # 卖家总收益


class SwitchSeller(WaitPage):
    wait_for_all_groups = True

    template_name = 'trading_experiment2/SwitchSellerWaitPage.html'

    @staticmethod
    def is_displayed(player):
        # 页面仅在第二轮及之后展示
        return player.round_number > 1

    @staticmethod
    def after_all_players_arrive(subsession: Subsession):
        # 调用处理卖家切换的方法
        subsession.handle_switch_seller()

    @staticmethod
    def vars_for_template(player: Player):
        group = player.group
        buyer = group.get_player_by_role(Constants.BUYER_ROLE)
        seller = group.get_player_by_role(Constants.SELLER_ROLE)

        # 计算当前轮次的收益
        if buyer.field_maybe_none('purchase_decision'):
            buyer_benefit = (
                    Constants.quality_average + buyer.field_maybe_none(
                'individual_difference') - group.field_maybe_none('seller_decision_price')
            )
            seller_benefit = group.field_maybe_none('seller_decision_price') - Constants.cost
        else:
            buyer_benefit = 0
            seller_benefit = 0

        # 初始化总收益
        total_buyer_benefit = 0
        total_seller_benefit = 0
        all_rounds_data = []
        previous_round_data = None  # 上一轮次的数据

        # 统计卖家的交易次数与总收益
        seller_stats = {}

        # 遍历所有轮次,累积并展示数据
        for previous_round in player.in_all_rounds():
            previous_round_group = previous_round.group
            previous_round_buyer = previous_round_group.get_player_by_role(Constants.BUYER_ROLE)
            previous_round_seller = previous_round_group.get_player_by_role(Constants.SELLER_ROLE)

            # 每轮的收益计算
            if previous_round_buyer.field_maybe_none('purchase_decision'):
                round_buyer_benefit = (
                        Constants.quality_average
                        + previous_round_buyer.field_maybe_none('individual_difference')
                        - previous_round_group.field_maybe_none('seller_decision_price')
                )
                round_seller_benefit = previous_round_group.field_maybe_none('seller_decision_price') - Constants.cost
            else:
                round_buyer_benefit = 0
                round_seller_benefit = 0

            # 累积总收益
            total_buyer_benefit += round_buyer_benefit
            total_seller_benefit += round_seller_benefit

            # 统计卖家的交易次数和总收益
            seller_name = previous_round_seller.role_name
            if seller_name not in seller_stats:
                seller_stats[seller_name] = {'count': 0, 'total_benefit': 0}
            seller_stats[seller_name]['count'] += 1
            seller_stats[seller_name]['total_benefit'] += round_buyer_benefit

            # 收集每轮的数据
            round_data = {
                'round_number': previous_round.round_number,
                'buyer_name': previous_round_buyer.role_name,
                'seller_name': seller_name,
                'seller_quality': previous_round_group.field_maybe_none('seller_decision_quality'),
                'seller_price': previous_round_group.field_maybe_none('seller_decision_price'),
                'buyer_decision': '是' if previous_round_buyer.field_maybe_none('purchase_decision') else '否',
                'individual_difference': previous_round_buyer.field_maybe_none('individual_difference'),
                'buyer_benefit': round_buyer_benefit,
                'seller_benefit': round_seller_benefit,
                'total_buyer_benefit': total_buyer_benefit,
                'total_seller_benefit': total_seller_benefit,
                'seller_credibility_rating': previous_round_buyer.field_maybe_none(
                    'seller_credibility_rating') or '未评分',
                'willing_to_buy_again': (
                    '愿意' if previous_round_buyer.field_maybe_none('willing_to_buy_again') else '不愿意'
                ),
                'seller_credibility': previous_round_group.field_maybe_none('seller_credibility'),
            }
            all_rounds_data.append(round_data)

            # 统计上一轮数据
            if previous_round.round_number == player.round_number - 1:
                previous_round_data = round_data

        # 计算每个卖家的平均收益
        for seller_name, stats in seller_stats.items():
            stats['average_benefit'] = stats['total_benefit'] / stats['count']

        # 更新并保存玩家的总收益
        player.total_buyer_benefit = total_buyer_benefit
        player.total_seller_benefit = total_seller_benefit

        # 当前轮次的总收益
        current_total_buyer_benefit = total_buyer_benefit
        current_total_seller_benefit = total_seller_benefit

        return {
            'seller_quality': group.field_maybe_none('seller_decision_quality'),
            'seller_price': group.field_maybe_none('seller_decision_price'),
            'buyer_decision': '是' if buyer.field_maybe_none('purchase_decision') else '否',
            'individual_difference': buyer.field_maybe_none('individual_difference') if buyer.field_maybe_none(
                'purchase_decision') else '无',
            'buyer_benefit': buyer_benefit,
            'seller_benefit': seller_benefit,
            'all_rounds_data': all_rounds_data,
            'current_total_buyer_benefit': current_total_buyer_benefit,
            'current_total_seller_benefit': current_total_seller_benefit,
            'seller_credibility': group.field_maybe_none('seller_credibility'),
            'seller_stats': seller_stats,  # 传递卖家的统计数据
            'previous_round_data': previous_round_data,  # 传递上一轮的数据
        }


class Introduction(Page):
    @staticmethod
    def vars_for_template(player: Player):
        group = player.group

        seller = group.get_player_by_role(Constants.SELLER_ROLE)
        buyer = group.get_player_by_role(Constants.BUYER_ROLE)

        return {
            'player_role_name': player.role_name,
            'seller_role_name': seller.role_name if seller else 'N/A',
            'buyer_role_name': buyer.role_name if buyer else 'N/A',
        }


class SellerDecision(Page):
    form_model = 'player'
    form_fields = ['quality', 'price']

    @staticmethod
    def is_displayed(player):
        return player.role == Constants.SELLER_ROLE

    @staticmethod
    def before_next_page(player: Player, timeout_happened):
        group = player.group
        group.seller_decision_quality = player.quality
        group.seller_decision_price = player.price


class SellerWaitPage(WaitPage):
    @staticmethod
    def is_displayed(player):
        return player.role == Constants.BUYER_ROLE

    @staticmethod
    def after_all_players_arrive(group: Group):
        pass


class BuyerInteraction(Page):
    form_model = 'player'
    form_fields = ['purchase_decision']

    @staticmethod
    def is_displayed(player):
        return player.role == Constants.BUYER_ROLE

    @staticmethod
    def vars_for_template(player: Player):
        current_group = player.group
        all_rounds_data = []

        if player.round_number > 1:
            for previous_round in player.in_previous_rounds():
                previous_round_group = previous_round.group
                previous_round_player = previous_round

                if previous_round_player.role == Constants.BUYER_ROLE:
                    previous_round_buyer = previous_round_player
                    previous_round_seller = previous_round_group.get_player_by_role(Constants.SELLER_ROLE)
                else:
                    previous_round_buyer = previous_round_group.get_player_by_role(Constants.BUYER_ROLE)
                    previous_round_seller = previous_round_player

                if previous_round_buyer.purchase_decision:
                    buyer_benefit = previous_round_group.seller_decision_quality + previous_round_buyer.individual_difference - previous_round_group.seller_decision_price
                else:
                    buyer_benefit = 0

                round_data = {
                    'round_number': previous_round.round_number,
                    'seller_name': previous_round_seller.role_name,
                    'seller_quality': previous_round_group.seller_decision_quality,
                    'seller_price': previous_round_group.seller_decision_price,
                    'buyer_decision': '是' if previous_round_buyer.purchase_decision else '否',
                    'buyer_benefit': buyer_benefit,
                    'seller_credibility_rating': previous_round_buyer.seller_credibility_rating,
                    'willing_to_buy_again': '愿意' if previous_round_buyer.willing_to_buy_again else '不愿意'
                }

                all_rounds_data.append(round_data)

        current_round_data = {
            'quality': current_group.seller_decision_quality,
            'price': current_group.seller_decision_price
        }

        return {
            'quality': current_round_data['quality'],
            'price': current_round_data['price'],
            'all_rounds_data': all_rounds_data
        }

    @staticmethod
    def before_next_page(player: Player, timeout_happened):
        player.individual_difference = random.randint(-10, 10)
        # 计算卖家的可信度
        player.group.seller_credibility = player.group.seller_decision_quality - Constants.quality_average


class BuyerSwitchDecision(Page):
    form_model = 'player'
    form_fields = [ 'seller_credibility_rating', 'willing_to_buy_again','switch_seller']

    @staticmethod
    def is_displayed(player):
        return player.role == Constants.BUYER_ROLE

    @staticmethod
    def vars_for_template(player: Player):
        group = player.group
        buyer = group.get_player_by_role(Constants.BUYER_ROLE)
        seller = group.get_player_by_role(Constants.SELLER_ROLE)

        # 计算当前轮次的收益
        if buyer.purchase_decision:
            buyer_benefit = (
                    Constants.quality_average + buyer.individual_difference - group.seller_decision_price
            )
            seller_benefit = group.seller_decision_price - Constants.cost
        else:
            buyer_benefit = 0
            seller_benefit = 0

        # 初始化总收益
        total_buyer_benefit = 0
        total_seller_benefit = 0
        all_rounds_data = []

        # 统计卖家的交易次数与总收益
        seller_stats = {}

        # 遍历所有轮次,累积并展示数据
        for previous_round in player.in_all_rounds():
            previous_round_group = previous_round.group
            previous_round_buyer = previous_round_group.get_player_by_role(Constants.BUYER_ROLE)
            previous_round_seller = previous_round_group.get_player_by_role(Constants.SELLER_ROLE)

            # 每轮的收益计算
            if previous_round_buyer.purchase_decision:
                round_buyer_benefit = (
                        Constants.quality_average
                        + previous_round_buyer.individual_difference
                        - previous_round_group.seller_decision_price
                )
                round_seller_benefit = previous_round_group.seller_decision_price - Constants.cost
            else:
                round_buyer_benefit = 0
                round_seller_benefit = 0

            # 累积总收益
            total_buyer_benefit += round_buyer_benefit
            total_seller_benefit += round_seller_benefit

            # 统计卖家的交易次数和总收益
            seller_name = previous_round_seller.role_name
            if seller_name not in seller_stats:
                seller_stats[seller_name] = {'count': 0, 'total_benefit': 0}
            seller_stats[seller_name]['count'] += 1
            seller_stats[seller_name]['total_benefit'] += round_buyer_benefit

            # 收集每轮的数据
            round_data = {
                'round_number': previous_round.round_number,
                'buyer_name': previous_round_buyer.role_name,
                'seller_name': seller_name,
                'seller_quality': previous_round_group.seller_decision_quality,
                'seller_price': previous_round_group.seller_decision_price,
                'buyer_decision': '是' if previous_round_buyer.purchase_decision else '否',
                'individual_difference': previous_round_buyer.individual_difference,
                'buyer_benefit': round_buyer_benefit,
                'seller_benefit': round_seller_benefit,
                'total_buyer_benefit': total_buyer_benefit,
                'total_seller_benefit': total_seller_benefit,
                'seller_credibility_rating': previous_round_buyer.field_maybe_none(
                    'seller_credibility_rating') or '未评分',
                'willing_to_buy_again': (
                    '愿意' if previous_round_buyer.field_maybe_none('willing_to_buy_again') else '不愿意'
                ),
                'seller_credibility': previous_round_group.seller_credibility,
            }
            all_rounds_data.append(round_data)

        # 计算每个卖家的平均收益
        for seller_name, stats in seller_stats.items():
            stats['average_benefit'] = stats['total_benefit'] / stats['count']

        # 更新并保存玩家的总收益
        player.total_buyer_benefit = total_buyer_benefit
        player.total_seller_benefit = total_seller_benefit

        # 当前轮次的总收益
        current_total_buyer_benefit = total_buyer_benefit
        current_total_seller_benefit = total_seller_benefit

        return {
            'seller_quality': group.seller_decision_quality,
            'seller_price': group.seller_decision_price,
            'buyer_decision': '是' if buyer.purchase_decision else '否',
            'individual_difference': buyer.individual_difference if buyer.purchase_decision else '无',
            'buyer_benefit': buyer_benefit,
            'seller_benefit': seller_benefit,
            'all_rounds_data': all_rounds_data,
            'current_total_buyer_benefit': current_total_buyer_benefit,
            'current_total_seller_benefit': current_total_seller_benefit,
            'seller_credibility': group.seller_credibility,
            'seller_stats': seller_stats,  # 传递卖家的统计数据
        }


class ResultsWaitPage(WaitPage):
    def after_all_players_arrive(self):
        pass


class SellerResults(Page):
    @staticmethod
    def is_displayed(player):
        return player.role == Constants.SELLER_ROLE

    @staticmethod
    def vars_for_template(player: Player):
        group = player.group
        buyer = group.get_player_by_role(Constants.BUYER_ROLE)
        seller = group.get_player_by_role(Constants.SELLER_ROLE)

        # 计算当前轮次的收益
        if buyer.purchase_decision:
            buyer_benefit = (
                    Constants.quality_average + buyer.individual_difference - group.seller_decision_price
            )
            seller_benefit = group.seller_decision_price - Constants.cost
        else:
            buyer_benefit = 0
            seller_benefit = 0

        # 初始化总收益
        total_buyer_benefit = 0
        total_seller_benefit = 0
        all_rounds_data = []

        # 遍历所有轮次,累积并展示数据
        for previous_round in player.in_all_rounds():
            previous_round_group = previous_round.group
            previous_round_buyer = previous_round_group.get_player_by_role(Constants.BUYER_ROLE)
            previous_round_seller = previous_round_group.get_player_by_role(Constants.SELLER_ROLE)

            # 每轮的收益计算
            if previous_round_buyer.purchase_decision:
                round_buyer_benefit = (
                        Constants.quality_average
                        + previous_round_buyer.individual_difference
                        - previous_round_group.seller_decision_price
                )
                round_seller_benefit = previous_round_group.seller_decision_price - Constants.cost
            else:
                round_buyer_benefit = 0
                round_seller_benefit = 0

            # 累积总收益
            total_buyer_benefit += round_buyer_benefit
            total_seller_benefit += round_seller_benefit

            # 安全访问字段,使用 `.field_maybe_none()` 方法
            round_data = {
                'round_number': previous_round.round_number,
                'buyer_name': previous_round_buyer.role_name,
                'seller_name': previous_round_seller.role_name,
                'seller_quality': previous_round_group.seller_decision_quality,
                'seller_price': previous_round_group.seller_decision_price,
                'buyer_decision': '是' if previous_round_buyer.purchase_decision else '否',
                'individual_difference': previous_round_buyer.individual_difference,
                'buyer_benefit': round_buyer_benefit,
                'seller_benefit': round_seller_benefit,
                'total_buyer_benefit': total_buyer_benefit,
                'total_seller_benefit': total_seller_benefit,
                'seller_credibility_rating': previous_round_buyer.field_maybe_none(
                    'seller_credibility_rating') or '未评分',
                'willing_to_buy_again': (
                        previous_round_buyer.field_maybe_none('willing_to_buy_again') or '未评分'
                ),
                'seller_credibility': previous_round_group.seller_credibility,
            }
            all_rounds_data.append(round_data)

        # 更新并保存玩家的总收益
        player.total_buyer_benefit = total_buyer_benefit
        player.total_seller_benefit = total_seller_benefit

        # 当前轮次的总收益
        current_total_buyer_benefit = total_buyer_benefit
        current_total_seller_benefit = total_seller_benefit

        return {
            'seller_quality': group.seller_decision_quality,
            'seller_price': group.seller_decision_price,
            'buyer_decision': '是' if buyer.purchase_decision else '否',
            'individual_difference': buyer.individual_difference if buyer.purchase_decision else '无',
            'buyer_benefit': buyer_benefit,
            'seller_benefit': seller_benefit,
            'all_rounds_data': all_rounds_data,
            'current_total_buyer_benefit': current_total_buyer_benefit,
            'current_total_seller_benefit': current_total_seller_benefit,
            'seller_credibility': group.seller_credibility,
            'seller_credibility_rating': previous_round_buyer.field_maybe_none(
                'seller_credibility_rating') or '未评分',
            'willing_to_buy_again': (
                    previous_round_buyer.field_maybe_none('willing_to_buy_again') or '未评分'
            ),
        }


page_sequence = [
    SwitchSeller,
    Introduction,
    SellerDecision,
    SellerWaitPage,
    BuyerInteraction,
    BuyerSwitchDecision,
    ResultsWaitPage,
    SellerResults,
]

Write a reply

Set forum username