A Zero-Intelligence Agent-Based Model of a Modern Limit Order Market


The code in this GitHub repository was used to generate the results in "An application of agent-based modeling to market structure policy: the case of the U.S. Tick Size Pilot Program and market maker profitability" by Charles Collver.

You can download or fork the code and run it yourself.

Or, you can use one of the conda packages by downloading and installing or by: conda install -c dennycrane pyziabm. For now, there are Python3.6 versions in Windows, Unix and Mac.

Please visit radicalmarketsimulation for a more general discussion of agent-based modeling and an opportunity to contribute to the discussion.

Code Layout

There are seven files:

  7. and and

Working With Conda

The easiest way to get started is to use IPython or a Jupyter Notebook:

>>>import pyziabm as pzi


This will take a few minutes to run and will generate an hdf5 file named test.h5 in the working directory.

The code in GitHub provides some example Jupyter Notebooks for working with the output data.

The Runner instantiation takes all keyword arguments with defaults.

class Runner(object):
    def __init__(self, prime1=20, num_mms=1, mm_maxq=1, mm_quotes=12, mm_quote_range=60, mm_delta=0.025, 
                 num_takers=50, taker_maxq=1, num_providers=38, provider_maxq=1, q_provide=0.5,
                 alpha=0.0375, mu=0.001, delta=0.025, lambda0=100, wn=0.001, c_lambda=1.0, run_steps=100000,
                 mpi=5, h5filename='test.h5', pj=False, alpha_pj=0):

To change the inputs, pass them into the Runner call:

>>>pzi.Runner(mpi=1, h5filename='test2.h5', pj=True, alpha_pj=0.01)

To run in a loop and collect output consistent with the paper, see the details for working with the runwrappers below.

To replicate the results in the paper, you must implement the random seeds as described in the runwrapper files.

Jupyter Notebooks for generating the final charts are available on the Anaconda Cloud.

Working With Source

In general, the Orderbook shouldn't be modified. However, adding additional functionality (like hidden orders) requires working with the Traders, too. Traders communicate with the Orderbook via add and cancel messages carried to the Orderbook via dicts:

def _make_add_quote(self, time, quantity, side, price):
        '''Make one add quote (dict)'''
        self._quote_sequence += 1
        order_id = '%s_%d' % (self._trader_id, self._quote_sequence)
        return {'order_id': order_id, 'timestamp': time, 'type': 'add', 'quantity': quantity, 
                'side': side, 'price': price}
def _make_cancel_quote(self, q, time):
        return {'type': 'cancel', 'timestamp': time, 'order_id': q['order_id'], 'quantity': q['quantity'],
                'side': q['side'], 'price': q['price']}

The Orderbook communicates with Traders via confirm messages, also dicts:

def _confirm_trade(self, timestamp, order_side, order_quantity, order_id, order_price):
        '''Add trade confirmation to confirm_trade_collector list.'''
        trader = order_id.partition('_')[0]
        self.confirm_trade_collector.append({'timestamp': timestamp, 'trader': trader, 'order_id': order_id, 
                                             'quantity': order_quantity, 'side': order_side, 'price': order_price})
def _confirm_modify(self, timestamp, order_side, order_quantity, order_id):
        '''Add modify confirmation to confirm_modify_collector list.'''
        trader = order_id.partition('_')[0]
        self.confirm_modify_collector.append({'timestamp': timestamp, 'trader': trader, 'order_id': order_id, 
                                              'quantity': order_quantity, 'side': order_side})

Much of the internal Orderbook bookkeeping methods would have to be modified as well. As the official bookkeeper, the Orderbook is also responsible for providing post-event information (via the SIP - also a dict):

def report_top_of_book(self, now_time):
        '''Update the top-of-book prices and sizes'''
        best_bid_price = self._bid_book_prices[-1]
        best_bid_size = self._bid_book[best_bid_price]['size']   
        best_ask_price = self._ask_book_prices[0]
        best_ask_size = self._ask_book[best_ask_price]['size']
        tob = {'timestamp': now_time, 'best_bid': best_bid_price, 'best_ask': best_ask_price, 'bid_size': best_bid_size, 'ask_size': best_ask_size}
        return tob

Traders can be modified to do anything you want. The only requirement is that the new Trader communicates with the Orderbook. In other words, the new Trader must be able to generate at least one of the two messages described above and handle the messages coming from the Orderbook. and runner2017_r4

The Runner class instantiates the agents, creates the Orderbook and runs the simulation in a loop. It also handles some of the results storage. and runwrapper2017_r3x

The runwrapper modules define and create the output data sets needed for the paper. It also provides a loop for running multiple simulations in sequence. runwrapper2017_r3x accepts input arguments in an executable file - this facilitates running all of the groups of simulations simultaneously on a large server.