Skip to content

Instantly share code, notes, and snippets.

@dmasad
Created January 22, 2014 01:16
Show Gist options
  • Select an option

  • Save dmasad/8551851 to your computer and use it in GitHub Desktop.

Select an option

Save dmasad/8551851 to your computer and use it in GitHub Desktop.

Revisions

  1. dmasad created this gist Jan 22, 2014.
    468 changes: 468 additions & 0 deletions ZI_Jungle.ipynb
    468 additions, 0 deletions not shown because the diff is too large. Please use a local Git client to view these changes.
    326 changes: 326 additions & 0 deletions ZI_Model.py
    Original 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()