Created
January 22, 2014 01:16
-
-
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/
This file contains 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 characters
''' | |
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