Execution Algorithms 101
Problem Definition
- Two variables: Time and Price
- Liquidity
- Bid-Ask Spread
A simple algorithm
def buy_sell_logic(self, update):
# Check if the time limit has exceeded
if self.watch.ts >= self.exec_request.end_time:
self._time_exceeded(update)
return
if self.om.num_orders() == 0:
# Send limit order
order_price = self.book.passive_price(self.buysell, num_ticks=0)
order = self.om.create_order('L', self.buysell, self.size, order_price)
self.om.send_order(order)
How to check if we did a good job?
Metrics
- Arrival Mid Price
- Arrival Aggressive Price
- VWAP
- TWAP
- Implementation Shortfall
Execution Algorithm Improvements
def divide_orders(self):
self._sub_orders = []
for i in range(self.num_orders):
order_size = max(self.size // self._num_orders, 100)
self._sub_orders.append(order_size)
def buy_sell_logic(self, update):
# Check if the time limit has exceeded
if self.watch.ts >= self.exec_request.end_time:
self._time_exceeded(update)
return
# Get the current bucket number
bucket = self._get_bucket()
if self.om.num_orders() == 0 and bucket > self._current_bucket:
# Send limit order
order_size = self._sub_orders[bucket]
order_price = self.book.passive_price(self.buysell, num_ticks=0)
order = self.om.create_order('L', self.buysell, order_size, order_price)
self.om.send_order(order)
More Improvements
Passive TWAP algorithm
def divide_orders(self):
self._sub_orders = []
for i in range(self.num_orders):
order_size = max(self.size // self._num_orders, 100)
self._sub_orders.append(order_size)
def buy_sell_logic(self, update):
# Check if the time limit has exceeded
if self.watch.ts >= self.exec_request.end_time:
self._time_exceeded(update)
return
# Get the latest top price
order_price = self.book.passive_price(self.buysell, 0)
if self.om.num_orders() > 0:
if time_diff >= self._interval:
# Modify to improve/aggressive price
self.om.modify_price(self.buysell, order_price)
# Get the current bucket number
bucket = self._get_bucket()
if self.om.num_orders() == 0 and bucket > self._current_bucket:
# Send limit order
order_size = self._sub_orders[bucket]
order_price = self.book.passive_price(self.buysell, num_ticks=0)
order = self.om.create_order('L', self.buysell, order_size, order_price)
self.om.send_order(order)
Production setup
- API server receives the orders and passes them to the run_execs.py process through RabbitMQ.
- API server maintains the status of the order including the benchmarks.
- Benchmarks as well as the order status updates are sent from run_execs.py process.
Production setup
Issues with prices
- Nanex process fetches the live prices. We push the "important" updates in binary format to kafka. (We have not benchmarked this operation.)
- For NASDAQ, the prices are fetched from Google/Yahoo/Robinhood. This affects both the trading and the benchmarking.
- There are often issues with the price APIs - creating more issues in reliably measuring the performance.
- Backtests are likely to have inferior results on average - this is due to the presence of hidden orders in market.
Issues with IB
- BUY orders have to wait till SELL orders get completed.
APEX results by securities
APEX results by securities
Security | Average PNL (bps) |
---|---|
FSTA | 6.11 |
FMAT | 5.86 |
VOX | 5.49 |
VBR | 4.61 |
FIDU | 4.27 |
APEX results by securities
Security | Average PNL (bps) |
---|---|
IVV | -2.68 |
VUG | -1.45 |
IVE | -1.27 |
XLE | -1.00 |
REM | -0.69 |
APEX results by date
APEX results by date
Date | Total PNL ($) |
---|---|
2017-07-27 | 4435 |
2017-08-15 | 2642 |
2017-08-23 | 1725 |
2017-03-15 | 1066 |
2017-08-18 | 932 |
APEX results by date
Date | Total PNL ($) |
---|---|
2017-08-28 | -2353 |
2017-06-14 | -1964 |
2017-06-09 | -685 |
2017-06-22 | -293 |
2017-09-06 | -267 |
APEX results by size
Size | Average PNL (bps) |
---|---|
<500 | 1.20 |
>=500 | 1.05 |
Next steps
Execution Algorithms 101
By Hardik Patel
Execution Algorithms 101
- 861