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