Zero Intelligence Traders: Gode and Sunder (1993)

Overview

This section covers Zero Intelligence trading (or ZI traders). The idea of putting very low intelligence traders into a simple market was originally suggested in the influential paper of [GS93]. They wanted to explore the common feature of experimental trading markets where humans often did very well in terms of market efficiency and allocation of consumer/producer surplus. This was often thought of as a strength of human behavior, and our ability to interact well with common market institutions.

However, the fraction of market success to attribute to the institutional setup rather than the humans themselves had not been tested. Gode and Sunder used standard experimental protocols, but populated their markets with simple computer robo traders as would be done in any standard agent-based model. However, the traders were assumed not to think strategically, or do any advanced learning, or statistical modeling of the world around them. They generally did follow their budget constraints and did not trade in a way that would lose them money. For these reasons they referred to them as zero intelligence (ZI) traders.

We will see them in action in this section. The results will show that ZI traders can trade very effectively in a simulated market. Market efficiency is near 100 percent, and the prices often converge to the standard equilibrium price as predicted by economic theory. This surprising result demonstrates that many economic market features may rely as much, or maybe more, on institutional design than they do on actual agent behavior.

Experimental design

Traders

The market is designed to replicate experimental markets. Keep in mind that in the experimental markets we have humans, and the markets that we run here are purely machine traders. There are \(N_b\) buyers, and \(N_s\) sellers. Each type is determined at the start of the game, and traders cannot switch from one side of the market to the other.

Induced values

In the 1970’s Vernon Smith introduced the concept of induced value to simulate demand and supply in experimental markets. Buyers are endowed with the right to sell tokens in the market. They are each assigned a random value distributed uniformly across \(v = [0,maxValue]\). Once a buyer has purchased the good for a given price, p, they can turn it in for v, and get a profit of \(v-p\).

The market is symmetric on the sell side. Sellers must incur costs, c, when they sell their tokens. These costs are assumed to be distributed \(c=[0,maxCost]\). Sellers net profits from a sale are then \(p-c\).

These heterogeneous values create a downward sloping demand, and upward sloping supply curve in the market. Efficient trading would entail sorting the agents on their valuations. Then moving from highest to lowest valued buyer, and lowest to highest valued seller, implementing a trade, and assuming that surplus \(v-c\) is distributed to the traders. This is a centralized planner solution to who should be trading with whom. The imputed demand and supply curves also give a predicted price and quantity traded for the market by simply looking at their crossing point.

The following figure gives an example of that this would look like in a market. Values works there way down the demand curve, and costs go up the supply curve. Sorting a random draw for buyers and sellers gives a representative figure which except for the discreteness should be familiar to anyone from a principles of economics class. The figure quickly predicts the final price and quantity traded. Calculating the area to the left of the market clearing quantity would give the consumer/producer surplus. Traders on this side of the market are labeled inframarignal, while the traders on the other side would be extramarginal. In the textbook market, the latter do not trade. In experiments this may not always be the case.

_images/demandSupply.png

Demand and supply

The objective of this market is to see how close the completely decentralized version will get to the stylized theoretical version. This was the key objective with human subjects, and is the same with computer algorithms.

It is important to note two of the key simplifying assumptions in this market. First, there is no retrade of the goods. Once a token is bought or sold, it cannot return to the market. This is related to locking down the buyers and sellers. They do not get to change roles. A second key assumption is that the agents only trade single fixed units of the good. They do not decide on quantities for purchase and sale. This is set to one for all transactions. We are still interested in the aggregate quantities traded in the market, but that corresponds to the number of trades that occur.

Trading

The market replicates a simple form of a double auction with a restricted limit order book. In general a limit order book keeps track of offers to buy (bids) and sell (offers). Agents can post new bids and offers, or trade with any existing live order. Obviously, a potential buyer would take the lowest offer, and a potential seller would take the highest bid. In this market several simplifying assumptions will require us to maintain the best bid (highest), and best offer (lowest).

Traders are randomly selected to enter the market in each moment. Once a trader has made one trade (purchase or sale) they are removed from the market.

In what is called the constrained simulation each buyer (i) will generate a buy limit order (bid) with a price chosen uniformly in the range,

\[b_i = [0,v_i]\]

guaranteeing a profitable trade. The agent takes this bid to the market and takes the following action:

  1. If the bid is greater than the current best ask price, then the agent purchases the token at the best ask. The trade price is recorded, and both agents are pulled from the live trader pool.
  2. If the bid is greater than the current best bid, then it replaces that order in the book, and the agent’s id number is recorded. It now is live on the book, and standing ready to buy if a seller wants to trade.
  3. If the bid is less than the best bid then the order is forgotten and the trader returns to the pool.

A similar procedure is followed for sellers. They find a random offer

\[o_i = [c_i, maxPrice]\]

that again guarantees a profitable trade. The agent will go to the market and take the following actions:

  1. If the offer is less than the current best bid price, then the agent sells the token at the best bid. The trade price is recorded, and both agents are pulled from the live trader pool.
  2. If the offer is less than the current best offer, then it replaces that order in the book, and the agent’s id number is recorded. It now is live on the book, and standing ready to sell if a buyer wants to trade.
  3. If the offer is greater than the best offer then the order is forgotten and the trader returns to the pool.

When a trade occurs, the price is recorded, the two agents are removed from the live trading pool, and the limit order book is cleared.

Software: Python

The code is written with several simple objects sitting below a main program in the file labeled script.py. Buyer and seller are objects supporting the system, and the code in the module dotrade.py supports the trading process.

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
# -*- coding: utf-8 -*-
# Trading with Zero Intelligence Agents Model without Marshallian Path
# Python version: David Ritzwoller, Blake LeBaron
# based on original matlab code by,
# Axel Szmulewiez, Blake LeBaron, Patrick Herb
# Brandeis University
# 04/14/2015
# 07/22/2016

# We implement a dynamic model of zero intelligence traders developed by
# Gode & Sunder (1993). We evaluate the role of the market as a natural
# allocator of resources in the economy. By having near Zero Intelligence
# agents who don't maximize profits or utility functions, we exclude the
# human factor in trade and isolate the roles of demand and supply. 

# The market is structured as a double aucion order book. When a trade takes
# places, it reinitializes the order book. The price of the transaction is 
# that of the bid/ask that is submitted to match the current best standing 
# offer. Each trade is for one unit. There are 100 buyers and 100 sellers
# that submit offers for 8000 iterations. Each trader is allowed to trade
# only once. The program compares the simulation's performance with the 
# theoretical economically efficient outcome.

# Sources: 
#           Allocative Efficiency of Markets with Zero-Intelligence
#           Traders: Market as a Partial Substitute for Individual
#           Rationality, Gode & Sunder (1993)
#
#           On the Behavioral Foundations of the Law of Supply and Demand:
#           Human Convergence and Robot Randomness, Brewer, Huang, Nelson &
#           Plott (2002)
#
#           Mark E. McBride, Department of Economics, Miami University, on
#           the development of the model in NetLogo and particular
#          contribution to this program in the design of order book
#           mechanics.

import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
from Buyer import Buyer
from Seller import Seller
from initializeBook import initializeBook
from doTrade import doTrade
# Get parameters from user

# Simulation runs with 100 buyers and 100 sellers (a market big enough to 
# consider it perfectly competitive

numberTraders = 500
# refresh demands 
#  This option if set to true will draw new values/costs for all agents who 
#  have not traded.  This is like a continuous refresh and this eliminates
#  the Marshallian path (see )
refresh = False

# Get input for maximum buyer value and seller cost (shape the demand and
# supply curves). Make sure inputs are positive
maxValue = 0 
while maxValue <= 0:
    maxValue = float(input('Please enter the desired maximum buyer value(demand curve upper bound, > 10 suggested): '))

maxCost = 0
while maxCost <= 0:
    maxCost = float(input('Please enter the desired maximum seller cost (supply curve upper bound, > 10 suggested): '))

# Run simulation with 50000 iteration
iterations = 50000

# Determine whether simulation will be constrained or unconstrained. Lock
# in while loop until valid input is given
constrained = -1
while (constrained != 0) & (constrained != 1):
    constrained = int(input('Please enter ''1'' for constrained simulation , ''0'' for unconstrained:  '))

# Determine whether simulation will have a price ceiling 5% above
# theoretical equilibrium price. Lock in while loop until valid input is
# given
ceiling = -1
# while (ceiling != 0) & (ceiling != 1):
#    ceiling = int(input('Please enter ''1'' to include a price ceiling in the simulation, ''0'' otherwise: '))

# Create vector holding all buyers
buyers = []    
for i in range(numberTraders):
    buyers.append(Buyer(maxValue))
    
# Initialize all buyers to not traded state and give them a reservation
# price (random variable bounded above)
valueVec = maxValue*np.random.rand(numberTraders)
for i in range(numberTraders):
    buyers[i].Value = valueVec[i]
    buyers[i].Traded = 0


    
# Create vector holding all sellers
sellers = []
for i in range(numberTraders):
    sellers.append(Seller(maxCost))
    
# Initialize all sellers to not traded state and give them a reservation
# cost (random variable bounded below)
costVec = maxCost*np.random.rand(numberTraders)
for i in range(numberTraders):
    sellers[i].Cost = costVec[i]
    sellers[i].Traded = 0

    
# Sort buyer and seller vectors to calculate equilibrium values and to 
# later plot demand and supply curves
buyerValues = []
for i in range(numberTraders):
    buyerValues.append(buyers[i].Value)
buyerValues.sort(reverse = True)

sellerCosts = []
for i in range(numberTraders):
    sellerCosts.append(sellers[i].Cost)
sellerCosts.sort()
    
# Compute theoretical equilibrium and surplus
predictedPrice = 0
predictedQuantity = 0
maximumSurplus = 0
for i in range(numberTraders):
    # If value of buyer is greater than cost of seller, there is a trade.
    # Then, quantity increases by one, there are gains for trade (surplus)
    # and the price is updated
    if(buyerValues[i] - sellerCosts[i]) > 0:
        predictedPrice = (buyerValues[i] + sellerCosts[i]) /2
        predictedQuantity = predictedQuantity + 1
        maximumSurplus = maximumSurplus + (buyerValues[i] - sellerCosts[i])
        
# Initialize the order book vector. Please see function description for the
# values held in each index
orderBookValues = initializeBook(maxCost)

# Initialize vector with transaction prices (update as iterations execute).
# Let the length of the vector be the maximum number of iterations, then
# discard leftover indexes initialized to zero for efficiency
transactionPrices = []

# Initial surplus is 0
surplus = 0

# Initial quantity traded is 0
quantity = 0

tradedValues = []
tradedCosts  = []

for i in range(iterations):
    # Stop the loop if all buyers and sellers have already traded. Note
    # that if all buyers have traded, all sellers have traded, since each
    # trader is allowed to trade only once
    if sum(buyers.Traded for buyers in buyers) == numberTraders:
        break
    
    # Attempt a trade or a new bid/ask (report update if trade occurs). 
    # Pass vectors of buyers and sellers to manipulate, the order book 
    # values to update trade information, number of traders to give upper 
    # bound for random generation of index that determines chosen trader, 
    # and constraing choice along with predicted price and max value for 
    # trader to generate bid/offer
    orderBookValues = doTrade(buyers, sellers, orderBookValues, numberTraders, predictedPrice, constrained, maxValue, maxCost)
    
    # Record transaction price, update surplus and quantity, mark traders 
    # to record that they have traded, and reinitialize the order book
    # if a trade occured
    if orderBookValues[6] > 0:
        transactionPrices.append(orderBookValues[6])
        surplus = surplus + orderBookValues[7]
        buyers[orderBookValues[1]].Traded = 1
        sellers[orderBookValues[4]].Traded = 1
        tradedValues.append(buyers[orderBookValues[1]].Value)
        tradedCosts.append(sellers[orderBookValues[4]].Cost)
        orderBookValues = initializeBook(maxCost)
        quantity = quantity +1
        # if refresh is True, then redraw values for all buyers and sellers
        #   this makes this as if at start with a complete refresh
        if refresh:
            for checkBuyer in buyers:
                if(checkBuyer.Traded == 0):
                    checkBuyer.Value = valueVec[np.random.randint(numberTraders)]
            for checkSeller in sellers:
                if(checkSeller.Traded == 0):
                    checkSeller.Cost = costVec[np.random.randint(numberTraders)]

# Calculate simulation surplus as percentage of total possible surplus
# This is a prototype area for the refresh option
# This should probably be eliminated in production
# no great way to estimate theoretical surplus under refresh
if refresh:
    tradedValnp = np.array(tradedValues)
    tradedCostnp = np.array(tradedCosts)
    tradedValnp = np.sort(tradedValnp)[::-1]
    tradedCostnp.sort()
    infraMarginal = (tradedValnp>tradedCostnp)
    maximumSurplus = np.sum(tradedValnp[infraMarginal]-tradedCostnp[infraMarginal])


surplusPercentage = surplus / maximumSurplus * 100.
SimulationPrice = np.mean(transactionPrices[-50:])

# Generate rolling means and variances with Panda (needs version 0.18)
priceTS = pd.Series(transactionPrices,index=range(len(transactionPrices)))
priceRoll = priceTS.rolling(window=50,min_periods=10)
priceVar = priceRoll.var()
priceMean = priceRoll.mean()

################################
#   REPORT RESULTS AND GRAPH   #
################################

# Plot demand and supply curves and price pattern
# Plot Supply and Demand using sorted values and costs vectors initialized
# to calculate equlibrium values. Also plot price path

xPrice = [1*x for x in range(len(transactionPrices))]
x = [1*x for x in range(numberTraders)]
fig,ax = plt.subplots()
ax.plot(x, buyerValues, 'r')
ax.plot(x, sellerCosts, 'g')
ax.plot(xPrice, transactionPrices, 'k')
ax.plot(xPrice, priceMean.values, 'b')

    
# Adjust and label axes and title
ax.set_xlabel('Quantity')
ax.set_ylabel('Price')
ax.set_title('Market For Traded Asset')
ax.grid()


# ax
fig2, a2 = plt.subplots()
a2.plot(priceVar)
# a2.plot(priceMean)
a2.set_xlabel('Period')
a2.set_ylabel('Price variance')
a2.grid()
plt.show()



# Report statistics results
print('Simulation Results:')
print('The predicted quantity was '+str(predictedQuantity)+', and the predicted price was '+ str(predictedPrice)[0:5])
print('The simulation quantity is '+str(quantity)+' ,and the simulation price is '+ str(SimulationPrice)[0:5])    
print('The simulation achieved '+str(surplusPercentage)[0:5]+' ,of the total available surplus')



    




    
    
    
    

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
# -*- coding: utf-8 -*-
import numpy as np
# Trading with Zero Intelligence Agents Model without Marshallian Path
# Axel Szmulewiez, Blake LeBaron, Patrick Herb
# Brandeis University
# 04/14/2015


def doTrade(buyers, sellers, bookValues, numberTraders, predictedPrice, constrained, maxValue, maxCost):
    # This function operates the double auction order book. The function
    # determines with 50% probability whether the next trader to submit an
    # offer is a buyer or a seller. Then, it compares the new bid/ask and
    # executes a trade if it satisfies the counterposing best standing offer,
    # or it takes the place as best offer of its kind if it is better than the
    # currently standing one (for example, if a new bid is submitted and it is
    # higher than the best ask, a trade will occur, but if it's not higher than
    # the best ask but higher than the best standing bid it will take its
    # place. A similar process takes place if a seller submits an ask). The
    # function returns an updated state of the order book to update simulation.
    
    # Initialize return vector to current book values
    updatedValues = bookValues
    
    # Randomly choose between a buyer and a seller (belor 0.5 we select a
    # buyer, otherwise we select a seller).
    traderDeterminer = np.random.rand()
    
    # Process as buyer
    if traderDeterminer <0.5:
        
        # Initialize a new buyer ID (at first 0)
        newBuyer = -1
        
        #Choose a random buyer that has yet not traded. Lock selection of a
        # buyer in a while loop until it chooses a buyer that hasn't traded
        while newBuyer == -1:
            # Choose a random index in the buyers vector to select a buyer
            randomIndex = np.random.randint(0, numberTraders)
            if buyers[randomIndex].Traded == 0:
                newBuyer = randomIndex
    
        
        # Make buyer generate a random bid based on its reservation price and
        # simulation constraint
        newBid = buyers[newBuyer].formBidPrice(constrained, predictedPrice, maxValue)
        
        # Do a trade if there is currently a standing ask that the bid can
        # trade with. Check the logical variable and the value of the best
        # standing ask
        if (updatedValues[5] == 1) & (newBid > updatedValues[3]):
            # Set the transaction price to the best ask value
            updatedValues[6] = updatedValues[3]
            # Record surplus added by the trade
            updatedValues[7] = buyers[newBuyer].Value - sellers[updatedValues[4]].Cost
            # Update ID of buyer
            updatedValues[1] = newBuyer
        
        # If the there is no trade, set the bid as best bid if it is higher
        # than the currently standing best bid, even if it doesn't satisfy the
        # ask or if there currently is no ask
        else:
            if newBid > updatedValues[0]:
                # Set new bid as best bid, and update the ID of bidder
                updatedValues[0]= newBid
                updatedValues[1] = newBuyer
                # Set logical variable for standing bid to true
                updatedValues[2] = 1
                # If new bid is lower than best bid, do nothing
    # Process a seller
    else:
        # Initialize a new seller ID (at first 0)
        newSeller = -1
        # Choose a random seller that has yet not traded. Lock selection of a
        # seller in a while loop until it chooses a seller that hasn't traded
        while newSeller == -1:
            # Choose a random index in the sellers vector to select a seller
            randomIndex = np.random.randint(0, numberTraders)
            if sellers[randomIndex].Traded == 0:
                newSeller = randomIndex
        
        # Make seller generate a random ask based on its reservation cost and
        # simulation constraint
        newAsk = sellers[newSeller].formAskPrice(constrained, predictedPrice, maxValue, maxCost)
        
        # Do a trade if there is currently a standing bid that the ask can
        # trade with. Check the logical variable and the value of the best
        # standing bid
        if (updatedValues[2] == 1) & (updatedValues[0] > newAsk):
            # Set the transaction price to the best bid value
            updatedValues[6] = updatedValues[0]
            # Record surplus added by the trade
            updatedValues[7] = buyers[updatedValues[1]].Value - sellers[newSeller].Cost
            # Record ID of seller
            updatedValues[4] = newSeller
        # If the there is no trade, set the ask as best ask if it is lower
        # than the currently standing best ask, even if it doesn't satisfy the
        # bid or if there currently is no bid
        else:
            if newAsk < updatedValues[3]:
                # Set new ask as best ask, and update the ID of bidder
                updatedValues[3] = newAsk
                updatedValues[4] = newSeller
                # Set logical variable for standing ask to true
                updatedValues[5] = 1
                # If new ask is higher than best ask, do nothing
       
    return updatedValues

        
            
                
                

simMarket simMarket simMarket simMarket simMarket

Results

For all runs we set \(N_b=N_s=500\). The market is run for 50,000 iterations, or until all traders have traded. These parameters do not align with those from experiments in that they increase the size of the market. For computer traders we have the luxury of using many of them, and making them stay around for a long time. The basic results are robust to the changes, and are often clearer with the larger samples.

Constrained agents

In the first case we will run the code with agents constrained by their token values as described. Starting the script.py program will prompt you for two values which set the max and min range for the buyer’s tokens (demand) and seller’s tokens (supply).

For your first run try setting both max values to 25. The program also asked if you want the agents to be constrained, and for this answer 1 (yes).

Your will get a plot with the theoretical demand and supply curves along with the traded prices against the cumulative traded values as in Basic convergence. The figure also shows a rolling average of the traded prices. In most of your runs, you should see a pretty clear convergence to the equilibrium market price. There are obvious random fluctuations which generally are larger at first, but diminish as trades go by. This reduction in price variability is displayed in Rolling Variance. This displays a rolling variance of the transaction prices using a moving average over 50 periods. The price variability falls as it does in human experiments.

_images/ZIsym.png

Basic convergence

_images/ZIsymvar.png

Rolling Variance

The program also reports a summary of the market performance. Here is an example of a run:

The predicted quantity was 255, and the predicted price was 12.43
The simulation quantity is 290 ,and the simulation price is 12.69
The simulation achieved 96.74 ,of the total available surplus

You can see both the theoretical predictions for price and volume are very close to the actual levels. Also, market efficiency is pretty high with a level over 95 percent. This is all very impressive when one considers that the traders are behaving completely randomly.

What exactly is the cause for this excellent market performance and replication of our economic predictions? (The latter we may have hoped to have come from strong assumptions about utility maximization, or other important assumptions about agent behavior.) One obvious possibility might be the easy symmetry of this problem. The supply and demand curves are exactly matched. What would happen if we make them slightly different. We do this by now typing in different parameters, 25 and 10 for the supply and demand maximum. An example plot is displayed in Asymmetric Curves.

_images/ZIasym.png

Asymmetric Curves

_images/ZIasymvar.png

Rolling variance

The figure again shows a strong convergence to the expected price, and a steady reduction in the variance as the market continues. The asymmetry in the supply and demand curves is also clear. Predictions and efficiency are again reported in the program by:

The predicted quantity was 354, and the predicted price was 7.29
The simulation quantity is 394 ,and the simulation price is 6.71
The simulation achieved 97.19 ,of the total available surplus

Again we have a good theoretical forecasts from the classic economic model, and market efficiency well over 90 percent.

Unconstrained agents

In the previous examples agents were not completely random in their behavior. They were constrained to eventually make money in the market. In other words, their budget constraints were binding. Buyers did not purchase tokens that they could not afford, and sellers did not sell tokens for less than they cost to acquire. These are simply basic economic common sense, but how much do they matter in these markets. In this section we will release these constraints.

Figure Unconstrained repeats the simulation for the case where the constraints have been removed and agents simply bid randomly. Buyers in the range (0,maxValue), and sellers in the range (0,maxCost). The demand and supply values are skewed to (25,10) to further emphasize the results which are displayed in Unconstrained and Rolling variance.

_images/ZIuncon.png

Unconstrained

_images/ZIunconvar.png

Rolling variance

The figure displays what one might expect from completely random behavior. There is no indication of any convergence to the equilibrium price, and prices continue to wildly fluctuate through the entire run. The price variance shows no trend toward converging to zero. The market performance and predictive statistics show a similar picture as in:

The predicted quantity was 357, and the predicted price was 7.16
The simulation quantity is 500 ,and the simulation price is 8.80
The simulation achieved 83.82, of the total available surplus

The quantity and price forecasts from economic theory are way off target, and trading efficiency is about 80 percent.

Summary

At this point we have the intriguing result that zero intelligence traders, constrained by standard budget constraints, generate a market which appears relatively orderly and possibly intelligent. The institutional structure seems to be giving us a lot here. The next section explores some of the robustness relative the market structure.

Continuous order inflow

Several authors have critiqued the ZI result. [BHNP02] detail the existence of a Marshallian path as being a key element of the ZI trader convergence. They describe how random trading proceeds in the market. As traders are eliminated from the market this leads often to the elimination of the most extreme values and costs of traders. The new distribution of traders is now distributed more tightly about the equilibrium price. The random mechanism alone leads to a path toward equilibrium.

There are several ways to eliminate this problem, and [BHNP02] use an experimental framework. In the computational world this can be done more easily. The idea is to refresh the market with the same starting distributions of values and costs after each trade is made. Setting the flag refresh to True will accomplish this in the code. After a trade takes place, the order book is cleared and all agent valuations are redrawn from the initial distribution. Trading then proceeds as usual. Bids and offers are constrained as they were in all the early runs of the model.

Results for the asymmetric valuations (25,10) are displayed in figure Continuous inflow. It is clear that there is no convergence to the equilibrium price, and the system stays well above the theoretical price. The price variance also shows no convergence as well. in Price variance.

_images/ZIrefresh.png

Continuous inflow

_images/ZIrefreshvar.png

Price variance

The market quantitatively shows these results and the lack of prediction of the theoretical values for prices and trading volume. (Calculating theoretical consumer surplus in the continuously refreshed case not an easy calculation. See [BHNP02] for discussions.):

The predicted quantity was 370, and the predicted price was 7.36
The simulation quantity is 500 ,and the simulation price is 10.61

Summary

Continuously refreshed demands opens interesting questions about the ZI convergence result. If markets are best represented by a fixed set of agents who arrive at the market, and depart once their trades are completed, then the Gode and Sunder version of ZI trader convergence seems to be a relatively robust picture of markets. However, if one views markets as being continuously fed with new traders arriving with heterogeneous valuations, then ZI convergence may not be a good representation.

In real markets it seems like both these situations are possible.

Regardless of how we think about markets, the ZI results of [GS93] remain an important part of agent-based finance, and agent-based modeling in general. In any modeling situation it is always a good idea to see which features might be generated by near random behavior. Institutional mechanisms along with some endogenous self-organization of agents may generate surprising empirical structure.