Inspiration
Arbitrage is a chance to gain risk-free profit, but everyone knows how to capitalise on this opportunity. With this in mind, we have came up with unique algorithms to maximise our profits.
What it does
Our algorithm tries to make the most profit out of the existing arbitrage (price difference between PHILLIPS_A and PHILLIPS_B in the market). We evaluate quickly the volume of the instruments we would like to trade by evaluating the market demand and supply, as well as our current positions to ensure that we are in a good risk position (position limit and unhedged delta).
How we built it
We first implemented code to ensure we don't go over the predefined limits to ensure we don't get locked out of the market. Then we identified our goals to be: choosing arbitrage with decent earnings and making sure our delta is 0. Therefore, we compare the bid and ask prices and set our bid price a little bit higher than the best bid; our ask price slightly lower than the best ask.
Challenges we ran into
We needed to debug quite a lot because concepts bid and ask can get quite confusing. It is also difficult to think of a creative strategy. And we ran into the case where we were at the position limit and could no longer cash in on the oppportunities of arbitrage in the market.
Accomplishments that we're proud of
The logic of our code is quite sophisticated and can handle a wide range of situations in the market.
What we learned
We have learnt a lot of technical terms in finance; and got a taste as to what algorithmic trading is like. In addition, we also learnt how to communicate with our teammates effectively since it is difficult to describe math and code.
What's next for Optiver Trading
We can extend our strategy to more stocks.
Code
import datetime as dt
import time
import random
import logging
from datetime import datetime
import numpy as np
import pandas as pd
from optibook.synchronous_client import Exchange
exchange = Exchange()
exchange.connect()
def trade_would_breach_position_limit(instrument_id, volume, side, position_limit=201):
positions = exchange.get_positions()
position_instrument = positions[instrument_id]
if side == 'bid':
return position_instrument + volume < position_limit
elif side == 'ask':
return position_instrument - volume > -position_limit
else:
raise Exception(f'''Invalid side provided: {side}, expecting 'bid' or 'ask'.''')
def print_positions_and_pnl():
positions = exchange.get_positions()
pnl = exchange.get_pnl()
print('Positions:')
for instrument_id in positions:
print(f' {instrument_id:10s}: {positions[instrument_id]:4.0f}')
print(f'\nPnL: {pnl:.2f}')
STOCK_A_ID = 'PHILIPS_A'
STOCK_B_ID = 'PHILIPS_B'
while True:
print(f'')
print(f'-----------------------------------------------------------------')
print(f'TRADE LOOP ITERATION ENTERED AT {str(dt.datetime.now()):18s} UTC.')
print(f'-----------------------------------------------------------------')
print_positions_and_pnl()
print(f'')
# Obtain order book and only trade if there are both bids and offers available on that stock
stock_order_book = exchange.get_last_price_book(STOCK_A_ID)
if not (stock_order_book and stock_order_book.bids and stock_order_book.asks):
print(f'Order book for {STOCK_A_ID} does not have bids or offers. Skipping iteration.')
continue
stock_order_book = exchange.get_last_price_book(STOCK_B_ID)
if not (stock_order_book and stock_order_book.bids and stock_order_book.asks):
print(f'Order book for {STOCK_B_ID} does not have bids or offers. Skipping iteration.')
continue
# Obtain best bid and ask prices from order book
try:
bidA = exchange.get_last_price_book(STOCK_A_ID).bids[0].price
askB = exchange.get_last_price_book(STOCK_B_ID).asks[0].price
bidB = exchange.get_last_price_book(STOCK_B_ID).bids[0].price
askA = exchange.get_last_price_book(STOCK_A_ID).asks[0].price
volAskA=exchange.get_last_price_book(STOCK_A_ID).asks[0].volume
volAskB=exchange.get_last_price_book(STOCK_B_ID).asks[0].volume
volBidA=exchange.get_last_price_book(STOCK_A_ID).bids[0].volume
volBidB=exchange.get_last_price_book(STOCK_B_ID).bids[0].volume
if askB<bidA:
stock_tobuy=STOCK_B_ID
stock_tosell=STOCK_A_ID
buyPrice = askB+(bidA-askB)*0.005
sellPrice = bidA-(bidA-askB)*0.005
if sellPrice-buyPrice<0.2:
print('Too cheap')
continue
else:
volume=min(volAskB,volBidA,200-exchange.get_positions()['PHILIPS_B'])
elif askA<bidB:
stock_tobuy=STOCK_A_ID
stock_tosell=STOCK_B_ID
buyPrice = askA+(bidB-askA)*0.005
sellPrice = bidB-(bidB-askA)*0.005
if sellPrice-buyPrice<0.1:
print('Too cheap')
continue
else:
volume=min(volAskA,volBidB,200-exchange.get_positions()['PHILIPS_A'])
else:
print('No opportunities')
continue
print(f'Selected stock {stock_tobuy} to buy, stock {stock_tosell} to sell.')
# Insert an IOC order to trade the opposing top-level
if volume>0 and trade_would_breach_position_limit(stock_tosell, volume, 'ask') and trade_would_breach_position_limit(stock_tobuy, volume, 'bid'): #and not trade_would_breach_unhedged_delta(stock_tosell, volume, side):
print(f'''Inserting {'ask'} for {stock_tosell}: {volume:.0f} lot(s) at price {sellPrice:.2f}.''')
exchange.insert_order(
instrument_id=stock_tosell,
price=sellPrice,
volume=volume,
side='ask',
order_type='limit')
print(f'''Inserting {'bid'} for {stock_tobuy}: {volume:.0f} lot(s) at price {buyPrice:.2f}.''')
exchange.insert_order(
instrument_id=stock_tobuy,
price=buyPrice,
volume=volume,
side='bid',
order_type='limit')
else:
print(f'''Not inserting {volume:.0f} lot {'ask'} for {stock_tosell} to avoid position-limit breach. Not inserting {volume:.0f} lot {'bid'} for {stock_tobuy} to avoid position-limit breach OR to avoid unhedged delta breach.''')
continue
except IndexError as e:
print("List index out of range")
print("Retrying later")
Log in or sign up for Devpost to join the conversation.