예제 #1
0
class Market(object):
    def __init__(self):
        self.good_types = ["food", "wood", "ore", "metal", "tools"]  # These should be blank in not test method
        self.agent_types = ["miner", "farmer", "refiner", "woodcutter", "blacksmith"]  # Also not needed later

        self.history = History()
        self.new_history()

        self.bid_book = {good: [] for good in self.good_types}
        self.ask_book = {good: [] for good in self.good_types}

        self.agents = {}  # For now just add this manually this should be added with api

    @classmethod
    def from_file(cls):
        pass  # alternate constructor load from file
        # two methods:
        # return cls(extra fields)
        # alternate
        # m = Market() # do stuff to it
        # return m

    def simulate(self, rounds=1):

        for agent in self.agents.values():  # TODO: actually append offers here instead of in agent
            agent.simulate()

            for good in self.good_types:
                agent.generate_offer(self, good)

        for good in self.good_types:
            self.resolve_offers(good)

        for agent in self.agents.values():
            if agent.money <= 0:
                # TODO: implement bankrupt
                self.replace_agent(agent)

    def resolve_offers(self, good):
        bids = self.bid_book[good]
        asks = self.ask_book[good]

        # Bids and asks are shuffled to avoid agent order bias
        # Shuffle bids and sort highest to lowest
        random.shuffle(bids)
        bids.sort(key=lambda bid: bid.unit_price, reverse=True)

        # Shuffle asks and sort lowest to highest
        random.shuffle(asks)
        asks.sort(key=lambda ask: ask.unit_price, reverse=False)

        num_bids = sum([bid.units for bid in bids])
        num_asks = sum([ask.units for ask in asks])
        successful_trades = 0
        money_traded = 0
        units_traded = 0
        average_price = 0

        while bids and asks:
            bid = bids[0]
            ask = asks[0]
            quantity_traded = min(bid.units, ask.units)
            clearing_price = (bid.unit_price + ask.unit_price) / 2

            if quantity_traded > 0:
                ask.units -= quantity_traded  # Change ask/bid to reflect trade
                bid.units -= quantity_traded

                # This just changes numbers, should be changed so actually moves goods back and forth
                self.agents[bid.agent_id].inventory.goods[good] += quantity_traded  # Transfer inventory of buyer
                self.agents[ask.agent_id].inventory.goods[good] -= quantity_traded

                self.agents[bid.agent_id].money -= clearing_price * quantity_traded
                self.agents[ask.agent_id].money += clearing_price * quantity_traded

                money_traded += quantity_traded * clearing_price
                units_traded += quantity_traded
                successful_trades += 1

                self.agents[bid.agent_id].update_price_model(self, "buy", good, True, clearing_price)
                self.agents[ask.agent_id].update_price_model(self, "sell", good, True, clearing_price)

                # TODO: Update buyer and seller price model here
            if bid.units == 0:
                bids.pop(0)
            if ask.units == 0:
                asks.pop(0)

        self.history.asks.add(good, num_asks)
        self.history.bids.add(good, num_bids)
        self.history.trades.add(good, units_traded)

        if units_traded > 0:
            average_price = money_traded / units_traded

        else:  # special case no goods traded this round, use last round's average price
            average_price = self.history.prices.average(good, 1)

        self.history.prices.add(good, average_price)

        # TODO: might want to log rejected offers somewhere

        # Reject all remaining offers
        for bid in bids:
            self.agents[bid.agent_id].update_price_model(self, "buy", good, False)

        for ask in asks:
            self.agents[ask.agent_id].update_price_model(self, "sell", good, False)

        # Clear trade books
        self.bid_book[good] = []
        self.ask_book[good] = []

    # TODO: This is from the load data method, change name and rethink
    def new_history(self):
        for good in self.good_types:
            self.history.register(good)
            self.history.prices.add(good, 1.0)  # start bidding at 1
            self.history.asks.add(good, 1.0)  # start history with one fake buy/sell
            self.history.bids.add(good, 1.0)
            self.history.trades.add(good, 1.0)

        for agent_type in self.agent_types:
            self.history.profits.register(agent_type)

    def transfer_good(self):
        pass

    def transfer_money(self):
        pass

    def replace_agent(self, agent):
        """This should replace an agent with the most successful agent type"""
        del self.agents[agent.id]
        agent = Agent(random.choice(self.agent_types))  # for now it just is random
        self.agents[agent.id] = agent

    # TODO: market reports should just return data not format it
    # TODO: market report should run on market history, not just rejected offers
    def market_report(self):  # this could be a __str__ function or return actual values for plotting
        # NOTE: now that orders are resolved, this only reflects rejected offers
        report = "Market Report:".center(62, "=") + "\n"
        report += "Bids".center(62) + "\n"

        for good, offers in self.bid_book.items():
            total_offers = len(offers)
            sum_price = sum(offer.unit_price for offer in offers)
            total_units = sum(offer.units for offer in offers)
            if total_offers > 0:
                average_price = round(sum_price / total_offers, 2)
            else:
                average_price = 0

            report += "{:<7} {:>5} total offers; {:>5.2f} avg price; {:>5} total units\n".format(
                good + ":", total_offers, average_price, total_units
            )

        report += "Asks".center(62) + "\n"
        for good, offers in self.ask_book.items():
            total_offers = len(offers)
            sum_price = sum(offer.unit_price for offer in offers)
            total_units = sum(offer.units for offer in offers)
            if total_offers > 0:
                average_price = round(sum_price / total_offers, 2)
            else:
                average_price = 0

            report += "{:<7} {:>5} total offers; {:>5.2f} avg price; {:>5} total units\n".format(
                good + ":", total_offers, average_price, total_units
            )

        return report

    def agent_report(self):
        s = "|"  # default seperator
        report = "Agent Report:".center(68, "=") + "\n"
        report += "agents".center(11) + s + "total".center(7) + s + "money".center(7) + s

        for good in self.good_types:
            report += good.center(7) + s

        report += "\n"
        # This might not be the most efficent way to do this.. loops through all agents for each type
        for agent_type in self.agent_types:
            total_agents = total_money = total_food = total_wood = total_ore = total_metal = total_tools = 0
            for agent in self.agents.values():
                if agent.class_name == agent_type:
                    total_agents += 1
                    total_money += agent.money
                    total_food += agent.inventory.goods["food"]
                    total_wood += agent.inventory.goods["wood"]
                    total_ore += agent.inventory.goods["ore"]
                    total_metal += agent.inventory.goods["metal"]
                    total_tools += agent.inventory.goods["tools"]

            report += "{:<11}|{:>7}|{:>7}|{:>7}|{:>7}|{:>7}|{:>7}|{:>7}|\n".format(
                agent_type, total_agents, total_money, total_food, total_wood, total_ore, total_metal, total_tools
            )

        return report