We are team-01 on the leaderboard. It is simply an algorithm that serves as a market maker in a simulated trading environment. It is not making positive profits. 😅 We were trying to set the rules such as comparing the bid price of ETF to ask price of stocks and vice versa. It is a teamwork of 5 of us. We have explored different methods and calibrations during the hackathon. Even though it is not working as expected, we are still happy to have this experience together and have the exposure to algorithmic trading overall.
import logging import time from typing import List from optibook import common_types as t from optibook import ORDER_TYPE_IOC, ORDER_TYPE_LIMIT, SIDE_ASK, SIDE_BID from optibook.synchronous_client import Exchange import random import json
logging.getLogger('client').setLevel('ERROR') logger = logging.getLogger(name)
BASKET_INSTRUMENT_ID = 'C2_GREEN_ENERGY_ETF' STOCK_INSTRUMENT_IDS = ['C2_SOLAR_CO', 'C2_WIND_LTD']
def print_report(e: Exchange): pnl = e.get_pnl() positions = e.get_positions() my_trades = e.poll_new_trades(BASKET_INSTRUMENT_ID) all_market_trades = e.poll_new_trade_ticks(BASKET_INSTRUMENT_ID) logger.info(f'I have done {len(my_trades)} trade(s) in {BASKET_INSTRUMENT_ID} since the last report. There have been {len(all_market_trades)} market trade(s) in total in {BASKET_INSTRUMENT_ID} since the last report.') logger.info(f'My PNL is: {pnl:.2f}') logger.info(f'My current positions are: {json.dumps(positions, indent=3)}')
""" maximum volume that can be reduced from the inventory + hedge """ def min_volume_flush_bid(e: Exchange): basket_book = e.get_last_price_book(BASKET_INSTRUMENT_ID) positions = e.get_positions_and_cash() #always even? #abs??? position_basket_curr_volume = abs(positions[BASKET_INSTRUMENT_ID]['volume']) basket_curr_bid_volume = basket_book.bids[0].volume
bov = position_basket_curr_volume
if position_basket_curr_volume > basket_curr_bid_volume:
bov = basket_curr_bid_volume
return bov
""" maximum volume that can be reduced from the inventory + hedge """ def min_volume_flush_ask(e: Exchange): #TODO volume??? basket_book = e.get_last_price_book(BASKET_INSTRUMENT_ID) positions = e.get_positions_and_cash() #always even? #abs??? position_basket_curr_volume = abs(positions[BASKET_INSTRUMENT_ID]['volume']) basket_curr_ask_volume = basket_book.asks[0].volume
bov = position_basket_curr_volume
if position_basket_curr_volume > basket_curr_ask_volume:
bov = basket_curr_ask_volume
return bov
""" Limits check overbought """ def overbought_limit_check(e: Exchange): res = { 'pass' : True, 'max_volume' : 0 } positions = e.get_positions_and_cash() if positions[BASKET_INSTRUMENT_ID]['volume'] < -500 or positions[STOCK_INSTRUMENT_IDS[0]]['volume'] > 500 or positions[STOCK_INSTRUMENT_IDS[1]]['volume'] > 500: res['pass'] = False res['max_volume'] = 0 else: res['max_volume'] = -500 - positions[BASKET_INSTRUMENT_ID]['volume'] return res
"""
Limits check oversold
"""
def oversold_limit_check(e: Exchange):
res = {
'pass' : True,
'max_volume' : 0
}
positions = e.get_positions_and_cash()
if positions[BASKET_INSTRUMENT_ID]['volume'] > 500 or positions[STOCK_INSTRUMENT_IDS[0]]['volume'] < -500 or positions[STOCK_INSTRUMENT_IDS[1]]['volume'] < -500:
res['pass'] = False
res['max_volume'] = 0
else:
res['max_volume'] = 500 - positions[BASKET_INSTRUMENT_ID]['volume']
return res
def hedge_(bov, e: Exchange): return 2
def trade_cycle(e: Exchange): # because we use limit orders, always delete existing orders that remain from the previous iteration e.delete_orders(BASKET_INSTRUMENT_ID)
logger.info(f'Start cycle')
#TODO they should depend from the LOB conditions??
basket_order_volume = 20
tick = 0.1
#The orders are deleted only if the prices change! Spread reduction
basket_book = e.get_last_price_book(BASKET_INSTRUMENT_ID)
stock1_book = e.get_last_price_book(STOCK_INSTRUMENT_IDS[0])
stock2_book = e.get_last_price_book(STOCK_INSTRUMENT_IDS[1])
if basket_book and basket_book.bids and basket_book.asks and stock1_book and stock1_book.bids and stock1_book.asks and stock2_book and stock2_book.bids and stock2_book.asks:
logger.info(f'Order book for {BASKET_INSTRUMENT_ID}: best bid={basket_book.bids[0].price:.2f}, best ask={basket_book.asks[0].price:.2f}')
logger.info(f'Order book for {STOCK_INSTRUMENT_IDS[0]}: best bid={stock1_book.bids[0].price:.2f}, best ask={stock1_book.asks[0].price:.2f}')
logger.info(f'Order book for {STOCK_INSTRUMENT_IDS[1]}: best bid={stock2_book.bids[0].price:.2f}, best ask={stock2_book.asks[0].price:.2f}')
overbought = basket_book.bids[0].price - (stock1_book.asks[0].price * 0.5 + stock2_book.asks[0].price * 0.5)
oversold = basket_book.asks[0].price - (stock1_book.bids[0].price * 0.5 + stock2_book.bids[0].price * 0.5)
#Buy in SW and sell in ETF is convinient
if overbought > 0:
logger.info(f'Overbought={overbought}')
limits = overbought_limit_check(e)
if limits['pass']:
bov = int(basket_order_volume * (1+overbought))
hedge = hedge_(bov,e)
logger.info(f'bov = {bov}')
#MARKET OR LIMIT?
#try to improve the best bid and best ask by 10 cents. When?
if overbought > 0.3: #TODO spread vs risk of not closing the order???
#TODOvolume proportional to the spread
e.insert_order(BASKET_INSTRUMENT_ID, price=basket_book.bids[0].price - tick, volume=bov, side=SIDE_ASK, order_type=ORDER_TYPE_LIMIT)
#TODO new price also for the stocks????
e.insert_order(STOCK_INSTRUMENT_IDS[0], price=stock1_book.asks[0].price, volume=hedge, side=SIDE_BID, order_type=ORDER_TYPE_LIMIT)
e.insert_order(STOCK_INSTRUMENT_IDS[1], price=stock2_book.asks[0].price, volume=hedge, side=SIDE_BID, order_type=ORDER_TYPE_LIMIT)
else:
e.insert_order(BASKET_INSTRUMENT_ID, price=basket_book.bids[0].price, volume=bov, side=SIDE_ASK, order_type=ORDER_TYPE_LIMIT)
e.insert_order(STOCK_INSTRUMENT_IDS[0], price=stock1_book.asks[0].price, volume=hedge, side=SIDE_BID, order_type=ORDER_TYPE_LIMIT)
e.insert_order(STOCK_INSTRUMENT_IDS[1], price=stock2_book.asks[0].price, volume=hedge, side=SIDE_BID, order_type=ORDER_TYPE_LIMIT)
elif oversold < 0:
logger.info(f'Oversold={oversold}')
limits = oversold_limit_check(e)
if limits['pass']:
bov = int(basket_order_volume * (1+abs(oversold)))
hedge = hedge_(bov,e)
logger.info(f'bov = {bov}')
#try to improve the best bid and best ask by 10 cents. When?
#market or limit?
if oversold < -0.3:
e.insert_order(BASKET_INSTRUMENT_ID, price=basket_book.asks[0].price + tick, volume=bov, side=SIDE_BID, order_type=ORDER_TYPE_LIMIT)
#TODO new price also for the stocks????
e.insert_order(STOCK_INSTRUMENT_IDS[0], price=stock1_book.bids[0].price, volume=hedge, side=SIDE_ASK, order_type=ORDER_TYPE_LIMIT)
e.insert_order(STOCK_INSTRUMENT_IDS[1], price=stock2_book.bids[0].price, volume=hedge, side=SIDE_ASK, order_type=ORDER_TYPE_LIMIT)
else:
e.insert_order(BASKET_INSTRUMENT_ID, price=basket_book.asks[0].price, volume=bov, side=SIDE_BID, order_type=ORDER_TYPE_LIMIT)
e.insert_order(STOCK_INSTRUMENT_IDS[0], price=stock1_book.bids[0].price, volume=hedge, side=SIDE_ASK, order_type=ORDER_TYPE_LIMIT)
e.insert_order(STOCK_INSTRUMENT_IDS[1], price=stock2_book.bids[0].price, volume=hedge, side=SIDE_ASK, order_type=ORDER_TYPE_LIMIT)
else:
logger.info(f'Normal')
positions = e.get_positions_and_cash()
if positions[BASKET_INSTRUMENT_ID]['volume'] < 0:
bov = min_volume_flush_bid(e)
if bov > 0:
logger.info(f'min_volume_flush_bid = {bov}')
hedge = hedge_(bov,e)
e.insert_order(BASKET_INSTRUMENT_ID, price=basket_book.asks[0].price , volume=bov, side=SIDE_BID, order_type=ORDER_TYPE_LIMIT)
e.insert_order(STOCK_INSTRUMENT_IDS[0], price=stock1_book.bids[0].price, volume=hedge, side=SIDE_ASK, order_type=ORDER_TYPE_LIMIT)
e.insert_order(STOCK_INSTRUMENT_IDS[1], price=stock2_book.bids[0].price, volume=hedge, side=SIDE_ASK, order_type=ORDER_TYPE_LIMIT)
elif positions[BASKET_INSTRUMENT_ID]['volume'] > 0:
bov = min_volume_flush_ask(e)
if bov > 0:
logger.info(f'min_volume_flush_ask = {bov}')
hedge = hedge_(bov,e)
e.insert_order(BASKET_INSTRUMENT_ID, price=basket_book.bids[0].price, volume=bov, side=SIDE_ASK, order_type=ORDER_TYPE_LIMIT)
e.insert_order(STOCK_INSTRUMENT_IDS[0], price=stock1_book.asks[0].price, volume=hedge, side=SIDE_BID, order_type=ORDER_TYPE_LIMIT)
e.insert_order(STOCK_INSTRUMENT_IDS[1], price=stock2_book.asks[0].price, volume=hedge, side=SIDE_BID, order_type=ORDER_TYPE_LIMIT)
else:
logger.info('No top bid/ask or no book at all for the basket instrument')
print_report(e)
logger.info(f'End cycle')
def main(): exchange = Exchange() exchange.connect()
# you can also define host/user/pass yourself
# when not defined, it is taken from ~/.optibook file if it exists
# if that file does not exists, an error is thrown
# exchange = Exchange(host='host-to-connect-to')
# exchange.connect(username='your-username', password='your-password')
sleep_duration_sec = 60/25
while True:
trade_cycle(exchange)
logger.info(f'Iteration complete. Sleeping for {sleep_duration_sec} seconds')
time.sleep(sleep_duration_sec)
if name == 'main': main()
Log in or sign up for Devpost to join the conversation.