Created
January 22, 2014 01:16
-
-
Save dmasad/8551851 to your computer and use it in GitHub Desktop.
Revisions
-
dmasad created this gist
Jan 22, 2014 .There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -0,0 +1,326 @@ ''' Jungle Equilibrium with Zero-Intelligence Traders ================================================== Based on the Gode & Sunder (1993) paper on ZI traders, and Piccione and Rubinstein (2007) on Jungle Equilibrium. ''' from __future__ import division # Ensure that int/int isn't coerced to int import random class Seller(object): ''' A seller agent. Attributes: max_cost: The maximum cost the seller may have. cost: The seller's actual cost, drawn from a uniform distribution s.t. 0 <= cost < max_cost units: The number of units the seller has to sell; initialized at 1 sale_price: The price the agent sold at; set to None before a sale. surplus: The difference between the sale price and the cost max_strength: The maximum coercive strength that an agent may have strength: The seller's actual strength, drawn from a uniform dist. s.t. 0 <= strength <= max_strength ''' def __init__(self, max_cost, max_strength): ''' Instantiate a new Seller Agent. Args: max_cost: The maximum allowed cost the agent may have ''' self.max_cost = max_cost self.cost = random.random() * max_cost self.strength = random.random() * max_strength self.units = 1 self.sale_price = None # Hold the price the unit is finally sold at self.surplus = None def ask_price(self): ''' Form the asking price in a negotiation. Returns: An asking price for the unit for sale, drawn from a uniform distribution s.t.: cost <= asking_price < max_cost ''' profit = random.random() * (self.max_cost - self.cost) return self.cost + profit def make_trade(self, sale_price): ''' Close the sale: set remaining units to 0, and record the sale price. Args: sale_price: The price the unit was sold at. ''' self.units = 0 self.sale_price = sale_price self.surplus = self.sale_price - self.cost def reset(self): ''' Return the seller agent to its initial condition. ''' self.units = 1 self.sale_price = None self.surplus = None class Buyer: ''' A Buyer agent. Attributes: max_value: The maximum value the buyer may have. value: The buyer's actual cost, drawn from a uniform distribution s.t. 0 <= value < max_value units: The number of units the buyer has; initialized at 0. sale_price: The price the agent bought at; set to None before a sale. surplus: The difference between the valuation and sale price. max_strength: The maximum coercive strength that an agent may have strength: The seller's actual strength, drawn from a uniform dist. s.t. 0 <= strength <= max_strength ''' def __init__(self, max_value, max_strength): ''' Instantiate a new Buyer Agent. Args: max_value: The maximum allowed value to an agent. ''' self.max_value = max_value self.value = random.random() * max_value self.strength = random.random() * max_strength self.units = 0 self.sale_price = None # Hold the price the unit is finally bought at. self.surplus = None def bid_price(self): ''' Form a bid price in a negotiation. Returns: A bid price drawn from a uniform distribution s.t.: 0 <= bid_price < value ''' return random.random() * self.value def make_trade(self, buy_price): ''' Close the sale: set units to 1, and record the sale price. Args: buy_price: The price the unit was bought at. ''' self.units = 1 self.sale_price = buy_price self.surplus = self.value - self.sale_price def reset(self): ''' Returns the buyer to its initial state. ''' self.units = 0 self.sale_price = None self.surplus = None class Market: ''' A simulated 'jungle' market between a set of Buyer and Seller agents. A given Market instance holds a population of agents set at initializations, from which multiple trading processes may be realized via different random pairings of agents. Attributes: max_value: The maximum value both buyers and sellers will place on their goods. num_buyers: The number of buyer agents in the market. num_sellers: The number of seller agents in the market. max_rounds_wo_trade: The number of pairings NOT resulting in a trade before the market is considered in equilibrium (i.e. no more trades are possible). transactions: A list of tuples recording successful transactions, of the form: (seller_id, buyer_id, transaction_price) ''' def __init__(self, max_value, max_strength, coercion, num_buyers, num_sellers, max_rounds_wo_trade): ''' Initialize a new market. Args: max_value: The maximum value both buyers and sellers will place on their goods. max_strength: The maximum coercive strength that both buyers and sellers may have. num_buyers: The number of buyer agents in the market. num_sellers: The number of seller agents in the market. max_rounds_wo_trade: The number of pairings NOT resulting in a trade before the market is considered in equilibrium (i.e. no more trades are possible). ''' # Set market constants: self.max_value = max_value self.max_strength = max_strength self.coercion = coercion self.num_buyers = num_buyers self.num_sellers = num_sellers self.max_rounds_wo_trade = max_rounds_wo_trade # Create agents: self.buyers = [Buyer(self.max_value, self.max_strength) for i in range(self.num_buyers)] self.sellers = [Seller(self.max_value, self.max_strength) for i in range(self.num_sellers)] # Initiate market counters: self.rounds_wo_trade = 0 # Counter of rounds where no trade occured. self.transactions = [] def reset_market(self): ''' Resets the market back to its starting state. Does not change the agents' valuations. ''' self.rounds_wo_trade = 0 self.transactions = [] for agent in self.buyers + self.sellers: agent.reset() def find_supply_demand(self): ''' Find the (non-coercive) supply/demand curves, and their intersection. Returns: A dictionary containing the summary of the market, as follows: { "supply": An ordered asceding list of seller costs, "demand": An ordered descending list of buyer values, "p": The estimated mean price, where the supply and demand curves meet, "q": The estimated number of trades, where the supply and demand curves meet. } If the supply and demand curves do not cross, p and q are set to None. ''' # The supply and demand curves: supply = [seller.cost for seller in self.sellers] demand = [buyer.value for buyer in self.buyers] supply.sort() demand.sort(reverse=True) # Estimate their intersection market_size = min(self.num_buyers, self.num_sellers) for i in range(market_size): if supply[i] == demand[i]: est_trades = i + 1 # From index to count est_price = supply[i] break if demand[i] <= supply[i] and demand[i-1] > supply[i-1]: est_trades = i+1 # Compute the middle of the range in overlap: max_price = min(supply[i], demand[i-1]) min_price = max(supply[i-1], demand[i]) est_price = (min_price + max_price)/2 break else: # If no intersection found, leave the values undefined. est_trades = None est_price = None # Build the return dictionary: market_summary = { "supply": supply, "demand": demand, "p": est_price, "q": est_trades } return market_summary def try_trade(self, coercion=False): ''' A single tick of the market. Match up a random buyer and seller and see if a trade occurs. Args: coercion: (default=False) Whether trades occurs by coercion or mutual benefit Returns None if no trade occurs Otherwise, a transaction tuple (seller_id, buyer_id, transaction_price) ''' # Choose a seller and a buyer seller_id = random.randint(0, self.num_sellers-1) buyer_id = random.randint(0, self.num_buyers-1) buyer = self.buyers[buyer_id] seller = self.sellers[seller_id] # If the buyer or seller are out of the market, no trade: if buyer.units == 1 or seller.units == 0: return None if coercion: # The stronger agent determines the trade price: if seller.strength > buyer.strength: transaction_price = seller.ask_price() else: transaction_price = buyer.bid_price() else: # Without coercion, the trade occurs only if mutually beneficial: ask_price = seller.ask_price() bid_price = buyer.bid_price() if ask_price > bid_price: return None transaction_price = ask_price + random.random() * (bid_price - ask_price) # The coerced trade goes forward: buyer.make_trade(transaction_price) seller.make_trade(transaction_price) return (seller_id, buyer_id, transaction_price) def run_market(self): ''' Run the market to equilibrium and populate the transactions list. ''' while self.rounds_wo_trade < self.max_rounds_wo_trade: transaction = self.try_trade(self.coercion) if transaction is None: self.rounds_wo_trade += 1 else: self.transactions.append(transaction) self.rounds_wo_trade = 0 def get_surplus(self): ''' Return a list with the buyer and seller surpluses after trading. ''' surpluses = [] for agent in self.buyers + self.sellers: if agent.surplus is not None: surpluses.append(agent.surplus) return surpluses if __name__ == "__main__": market = Market(30, 100, True, 50, 50, 1000) market.run_market() market = Market(30, 100, False, 50, 50, 1000) market.run_market()