import json import time import logging from traderapi import ( RotmanInteractiveTraderApi, OrderType, OrderAction, ) from pprint import pprint from settings import settings import sys import threading import concurrent.futures from pathlib import Path global client prices = [] exposure = 0 leased_converter = None conversion_left = 10 # last_conversion is either buy, sell, or None. last_trade = None ETF = "ETF" STOCKS = ["SAM", "BUGS", "TREE", "SETA", "POKE"] base_multiplier = 625 logging.basicConfig(level=logging.INFO) def make_etf(quantity): global conversion_left global exposure print("attempt to create with quantity", quantity) if conversion_left <= 0: print("failed making etf") return quantity = max(quantity, 10_000) result = client.use_lease("ETF-CREATE", { stock: quantity for stock in STOCK }) print("attempt result:", result) conversion_left -= 1 exposure += quantity def destroy_etf(quantity): global conversion_left global exposure print("attempt to destroy with quantity", quantity) if conversion_left <= 0: print("failed destroying etf") return quantity = max(quantity, 10_000) result = client.use_lease("ETF-REDEEM", { ETF: quantity }) print("attempt result:", result) conversion_left -= 1 exposure -= quantity def arbitrage(timestep): global exposure portfolio = client.get_portfolio() allowed_gap = 7 etf = (portfolio[ETF]['bid'], portfolio[ETF]['ask']) stocks = ( sum(portfolio[stock]['bid'] for stock in STOCKS), sum(portfolio[stock]['ask'] for stock in STOCKS), ) quantity_multiplier = base_multiplier * 601/(601 - timestep) if stocks[0] - etf[1] > 0: print("buying etf, selling stocks") quantity = quantity_multiplier * (stocks[0] - etf[1]) if exposure + quantity > 250_000: destroy_etf(10_000) quantity = min(250_000 - exposure, quantity) with concurrent.futures.ThreadPoolExecutor(max_workers=6) as executor: executor.map(lambda args: client.place_order(*args), [ (ETF, OrderType.MARKET, quantity, OrderAction.BUY) ] + [ (stock, OrderType.MARKET, quantity, OrderAction.SELL) for stock in STOCKS ]) exposure += quantity last_trade = "BUY" elif etf[0] - stocks[1] > 0: quantity = quantity_multiplier * (etf[0] - stocks[1]) if exposure - quantity < -250_000: print("only create ETF") quantity = min(250_000 + exposure, quantity) make_etf(10_000) return print("buying stocks, selling etf") with concurrent.futures.ThreadPoolExecutor(max_workers=6) as executor: executor.map(lambda args: client.place_order(*args), [ (ETF, OrderType.MARKET, quantity, OrderAction.SELL) ] + [ (stock, OrderType.MARKET, quantity, OrderAction.BUY) for stock in STOCKS ]) exposure += -quantity last_trade = "SELL" else: print("not buying stocks, no arbitrage profit") if __name__ == "__main__": client = RotmanInteractiveTraderApi( api_key=settings["api_key"], api_host=settings["api_host"] ) # verify connection trader = client.get_trader() logging.info(f"Connected as trader {json.dumps(trader, indent=2)}") # get portfolio and available securities portfolio = client.get_portfolio() for ticker, security in portfolio.items(): logging.info(f"Security {ticker}: {json.dumps(security, indent=2)}") # check case status case = client.get_case() logging.info(json.dumps(case, indent=2)) print(client.get_assets()) # wait for case to start while case["status"] != "ACTIVE": logging.info("Case not active yet, waiting...") time.sleep(1) case = client.get_case() print("got leased converter") step = 0 while True: try: arbitrage(step) time.sleep(1) step += 1 except KeyboardInterrupt: print("hit keyboard interrupt") break