Reconstructing trades and the order book

The parsed messages allow us to rebuild the order flow for the given day. The 'R' message type contains a listing of all stocks traded during a given day, including information about initial public offerings (IPOs) and trading restrictions.

Throughout the day, new orders are added, and orders that are executed and canceled are removed from the order book. The proper accounting for messages that reference orders placed on a prior date would require tracking the order book over multiple days, but we are ignoring this aspect here.

The get_messages() function illustrates how to collect the orders for a single stock that affects trading (refer to the ITCH specification for details about each message, slightly simplified, see notebook):

def get_messages(date, stock=stock):
"""Collect trading messages for given stock"""
with pd.HDFStore(itch_store) as store:
stock_locate = store.select('R', where='stock =
stock'
).stock_locate.iloc[0]
target = 'stock_locate = stock_locate'

data = {}
# relevant message types
messages = ['A', 'F', 'E', 'C', 'X', 'D', 'U', 'P', 'Q']
for m in messages:
data[m] = store.select(m,
where=target).drop('stock_locate', axis=1).assign(type=m)

order_cols = ['order_reference_number', 'buy_sell_indicator',
'shares', 'price']
orders = pd.concat([data['A'], data['F']], sort=False,
ignore_index=True).loc[:, order_cols]

for m in messages[2: -3]:
data[m] = data[m].merge(orders, how='left')

data['U'] = data['U'].merge(orders, how='left',
right_on='order_reference_number',
left_on='original_order_reference_number',
suffixes=['', '_replaced'])

data['Q'].rename(columns={'cross_price': 'price'}, inplace=True)
data['X']['shares'] = data['X']['cancelled_shares']
data['X'] = data['X'].dropna(subset=['price'])

data = pd.concat([data[m] for m in messages], ignore_index=True,
sort=False)

Reconstructing successful trades, that is, orders that are executed as opposed to those that were canceled from trade-related message types, C, E, P, and Q, is relatively straightforward:

def get_trades(m):
"""Combine C, E, P and Q messages into trading records"""
trade_dict = {'executed_shares': 'shares', 'execution_price':
'price'}
cols = ['timestamp', 'executed_shares']
trades = pd.concat([m.loc[m.type == 'E', cols +
['price']].rename(columns=trade_dict),
m.loc[m.type == 'C', cols +
['execution_price']].rename(columns=trade_dict),
m.loc[m.type == 'P', ['timestamp', 'price', 'shares']],
m.loc[m.type == 'Q', ['timestamp', 'price',
'shares']].assign(cross=1),
], sort=False).dropna(subset=['price']).fillna(0)
return trades.set_index('timestamp').sort_index().astype(int)

The order book keeps track of limit orders, and the various price levels for buy and sell orders constitute the depth of the order book. To reconstruct the order book for a given level of depth requires the following steps:

  1. The add_orders() function accumulates sell orders in ascending, and buy orders in descending order for a given timestamp up to the desired level of depth:
def add_orders(orders, buysell, nlevels):
new_order = []
items = sorted(orders.copy().items())
if buysell == -1:
items = reversed(items)
for i, (p, s) in enumerate(items, 1):
new_order.append((p, s))
if i == nlevels:
break
return orders, new_order
  1. We iterate over all ITCH messages and process orders and their replacements as required by the specification:
for message in messages.itertuples():
i = message[0]
if np.isnan(message.buy_sell_indicator):
continue
message_counter.update(message.type)

buysell = message.buy_sell_indicator
price, shares = None, None

if message.type in ['A', 'F', 'U']:
price, shares = int(message.price), int(message.shares)

current_orders[buysell].update({price: shares})
current_orders[buysell], new_order =
add_orders(current_orders[buysell], buysell, nlevels)
order_book[buysell][message.timestamp] = new_order

if message.type in ['E', 'C', 'X', 'D', 'U']:
if message.type == 'U':
if not np.isnan(message.shares_replaced):
price = int(message.price_replaced)
shares = -int(message.shares_replaced)
else:
if not np.isnan(message.price):
price = int(message.price)
shares = -int(message.shares)

if price is not None:
current_orders[buysell].update({price: shares})
if current_orders[buysell][price] <= 0:
current_orders[buysell].pop(price)
current_orders[buysell], new_order =
add_orders(current_orders[buysell], buysell, nlevels)
order_book[buysell][message.timestamp] = new_order

The number of orders at different price levels, highlighted in the following screenshot using different intensities for buy and sell orders, visualizes the depth of liquidity at any given point in time. The left panel shows how the distribution of limit order prices was weighted toward buy orders at higher prices. The right panel plots the evolution of limit orders and prices throughout the trading day: the dark line tracks the prices for executed trades during market hours, whereas the red and blue dots indicate individual limit orders on a per-minute basis (see notebook for details):

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.219.112.111