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")

Built With

Share this project:

Updates