Skip to content

Instantly share code, notes, and snippets.

@dmasad
Created January 22, 2014 01:16
Show Gist options
  • Save dmasad/8551851 to your computer and use it in GitHub Desktop.
Save dmasad/8551851 to your computer and use it in GitHub Desktop.
Code for Zero Intelligence Traders in the Jungle. http://davidmasad.com/blog/zi-traders-in-the-jungle/
Display the source blob
Display the rendered blob
Raw
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
'''
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()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment