def on_order_book_update_message(self, instrument: int, sequence_number: int, ask_prices: List[int], ask_volumes: List[int], bid_prices: List[int], bid_volumes: List[int]) -> None: """Called periodically to report the status of an order book. The sequence number can be used to detect missed or out-of-order messages. The five best available ask (i.e. sell) and bid (i.e. buy) prices are reported along with the volume available at each of those price levels. """ if instrument == Instrument.FUTURE: future_order_book = [ list(zip(ask_prices, ask_volumes)), list(zip(bid_prices, bid_volumes)) ] all_prices = np.array(ask_prices + bid_prices) all_volumes = np.array(ask_volumes + bid_volumes) # Weighted average of both bid and ask prices and volumes fair_value = int( round( np.sum( np.multiply( all_prices, np.divide(all_volumes, np.sum(all_volumes)))) / 100) * 100) if len(self.future_data) < NUM_POINTS: self.future_data.append(fair_value) else: self.future_data = self.future_data[1:] + [fair_value] if self.position == 0 and self.waiting_for_server is False: self.can_amend_order = True if len(self.etf_data) == NUM_POINTS: slope, intercept, r_value, p_value, std_err = lin_reg( range(1, 11), self.future_data) r_squared = r_value**2 # Case - no trend in futures market --> market maker role # p-value for a hypothesis test whose null hypothesis is that the slope is zero if p_value >= CRIT_VAL: total_volume = self.ideal_trade_volume( self.etf_data) side_volume = total_volume // 2 self.logger.warning( f"Total trade volume: {total_volume}") self.ask_id = next(self.order_ids) self.bid_id = next(self.order_ids) # Subcases - low volatility, confident market vs. high volatility, uncertain market ask_price = (fair_value + SPREAD // 2 if r_squared > R_SQUARED_THRESH else fair_value + SPREAD) self.send_insert_order(self.ask_id, Side.SELL, ask_price, side_volume, Lifespan.GOOD_FOR_DAY) bid_price = (fair_value - SPREAD // 2 if r_squared > R_SQUARED_THRESH else fair_value - SPREAD) self.send_insert_order(self.bid_id, Side.BUY, bid_price, side_volume, Lifespan.GOOD_FOR_DAY) self.waiting_for_server = True # Case - upward trending futures market/arbitrage opportunities --> market taker role elif np.abs(self.position) > BUY_SELL_DIFF_THRESH and self.waiting_for_server is False and \ self.can_amend_order is True: self.logger.warning( f"Warning - cornering market with ETF position: {self.position}" ) # Too many buy orders, need to readjust ask price if self.position > 0: self.send_cancel_order(self.ask_id) self.ask_id = next(self.order_ids) ask_price = self.etf_order_book[0][1][0] self.send_insert_order(self.ask_id, Side.SELL, ask_price, self.ask_remaining_volume, Lifespan.GOOD_FOR_DAY) # Too many sell orders, need to readjust bid price else: self.send_cancel_order(self.bid_id) self.bid_id = next(self.order_ids) bid_price = self.etf_order_book[1][1][0] self.send_insert_order(self.bid_id, Side.BUY, bid_price, self.bid_remaining_volume, Lifespan.GOOD_FOR_DAY) self.waiting_for_server = True self.can_amend_order = False else: self.etf_order_book = [ list(zip(ask_prices, ask_volumes)), list(zip(bid_prices, bid_volumes)) ] if len(self.etf_data) < NUM_POINTS: self.etf_data.append(np.var(ask_prices + bid_prices)) else: self.etf_data = self.etf_data[1:] + [ np.var(ask_prices + bid_prices) ]
def on_order_book_update_message(self, instrument: int, sequence_number: int, ask_prices: List[int], ask_volumes: List[int], bid_prices: List[int], bid_volumes: List[int]) -> None: """Called periodically to report the status of an order book. The sequence number can be used to detect missed or out-of-order messages. The five best available ask (i.e. sell) and bid (i.e. buy) prices are reported along with the volume available at each of those price levels. """ if instrument == Instrument.FUTURE: best_ask = ask_prices[0] best_bid = bid_prices[0] # Average of best ask and bid price rounded to nearest multiple of tick size fair_value = int(round(((best_ask + best_bid) / 2) / 100) * 100) if len(self.future_data) < NUM_POINTS: self.future_data.append(fair_value) else: self.future_data = self.future_data[1:] + [fair_value] else: self.etf_order_book = [ list(zip(ask_prices, ask_volumes)), list(zip(bid_prices, bid_volumes)) ] if len(self.etf_data) < NUM_POINTS: self.etf_data.append(np.var(ask_prices + bid_prices)) else: self.etf_data = self.etf_data[1:] + [ np.var(ask_prices + bid_prices) ] # Primary function for inserting new pairs of orders (bid and ask) if len(self.future_data) == NUM_POINTS and len( self.etf_data) == NUM_POINTS: # Most recent stored fair value of future fair_value = self.future_data[-1] # self.logger.warning(f"Fair value of future: {fair_value}") if self.waiting_for_server is False and len( self.active_orders) == 0: slope, intercept, r_value, p_value, std_err = lin_reg( range(NUM_POINTS), self.future_data) r_squared = r_value**2 # Case 1) - no trend in futures market --> market maker role # p-value for a hypothesis test whose null hypothesis is that the slope is zero if p_value >= CRIT_VAL: volume = self.ideal_trade_volume(self.etf_data) # self.logger.warning(f"Trade volume: {volume*2}") ask_id = next(self.order_ids) bid_id = next(self.order_ids) # Subcase 1) - low volatility, confident market # self.logger.warning(f"R squared: {r_squared}") if r_squared > R_SQUARED_THRESH: ask_price = int(fair_value + UPPER_SPREAD) bid_price = int(fair_value - LOWER_SPREAD) # Subcase 2) - high volatility, uncertain market else: # Best ask and bid price in etf order book ask_price = self.etf_order_book[0][0][0] bid_price = self.etf_order_book[1][0][0] self.send_insert_order(ask_id, Side.SELL, ask_price, volume, Lifespan.GOOD_FOR_DAY) self.send_insert_order(bid_id, Side.BUY, bid_price, volume, Lifespan.GOOD_FOR_DAY) # Store ask volume as negative, bid volume as positive self.active_orders[ask_id] = [-volume] self.active_orders[bid_id] = [volume] self.waiting_for_server = True # Case 2) - upward trending futures market/arbitrage opportunities --> market taker role # IMPLEMENTATION CORRECT if len(self.active_orders) == 2: for order_id in self.active_orders: self.active_orders[order_id].append( self.active_orders[order_id][-1]) # Ensure list will always only store 2 volumes at any given time if len(self.active_orders[order_id]) == 4: self.active_orders[order_id] = self.active_orders[ order_id][1:] # IMPLEMENTATION INCORRECT if self.net_position > 0: for order_id in list(self.active_orders): # Ensuring we obtain the relevant ask order if self.active_orders[order_id][-1] < 0 and len(self.active_orders[order_id]) == 3 and \ self.active_orders[order_id][-1] == self.active_orders[order_id][-3]: self.send_cancel_order(order_id) self.active_orders.pop(order_id) ask_id = next(self.order_ids) # Best bid price in etf order book ask_price = self.etf_order_book[0][0][ 0] # Might change this to cross the spread if doesn't w volume = np.abs(self.net_position) self.send_insert_order(ask_id, Side.SELL, ask_price, volume, Lifespan.GOOD_FOR_DAY) self.active_orders[ask_id] = [-volume] elif self.net_position < 0: for order_id in list(self.active_orders): # Ensuring we obtain the relevant bid order if self.active_orders[order_id][-1] > 0 and len(self.active_orders[order_id]) == 3 and \ self.active_orders[order_id][-1] == self.active_orders[order_id][-3]: self.send_cancel_order(order_id) self.active_orders.pop(order_id) bid_id = next(self.order_ids) # Best ask price in etf order book bid_price = self.etf_order_book[1][0][ 0] # Might change this to cross the spread if doesn't w volume = np.abs(self.net_position) self.send_insert_order(bid_id, Side.BUY, bid_price, volume, Lifespan.GOOD_FOR_DAY) self.active_orders[bid_id] = [volume] volume = np.abs(self.net_position) # IMPLEMENTATION CORRECT # Can open new set of orders once net ETF position is 0 if np.abs(self.net_position ) < BUY_SELL_DIFF_THRESH and self.waiting_for_server is True: order_volume_zero = True # Secondary check to ensure volume on both sides has been filled for order_id in self.active_orders: if self.active_orders[order_id][-1] != 0: order_volume_zero = False break if order_volume_zero: self.active_orders = {} self.waiting_for_server = False # IMPLEMENTATION CORRECT # Cornering market case elif np.abs( self.net_position ) > BUY_SELL_DIFF_THRESH and self.waiting_for_server is True: can_cancel = True for order_id in self.active_orders: if len(self.active_orders[order_id]) != 3: can_cancel = False elif len(self.active_orders[order_id]) == 3 and len( set(self.active_orders[order_id])) > 1: can_cancel = False if can_cancel: for order_id in self.active_orders: self.send_cancel_order(order_id) if self.net_position > 0: ask_id = next(self.order_ids) # Best ask price in etf order book ask_price = self.etf_order_book[0][0][0] self.send_insert_order(ask_id, Side.SELL, ask_price, volume, Lifespan.FILL_AND_KILL) elif self.net_position < 0: bid_id = next(self.order_ids) # Best bid price in etf order book bid_price = self.etf_order_book[1][0][0] self.send_insert_order(bid_id, Side.BUY, bid_price, volume, Lifespan.FILL_AND_KILL) self.active_orders = {} if len(self.active_orders) == 0: self.waiting_for_server = False self.logger.warning(f"Active orders: {self.active_orders}")
def on_order_book_update_message(self, instrument: int, sequence_number: int, ask_prices: List[int], ask_volumes: List[int], bid_prices: List[int], bid_volumes: List[int]) -> None: """Called periodically to report the status of an order book. The sequence number can be used to detect missed or out-of-order messages. The five best available ask (i.e. sell) and bid (i.e. buy) prices are reported along with the volume available at each of those price levels. """ # print(f"Pre execution ask orders: {self.active_ask_orders}") # print(f"Pre execution bid orders: {self.active_bid_orders}") if instrument == Instrument.FUTURE: self.fair_value_future = (ask_prices[0] + bid_prices[0]) / 2 self.best_future_ask_price = ask_prices[0] self.best_future_bid_price = bid_prices[0] if len(self.future_data) < NUM_POINTS: self.future_data.append(self.fair_value_future) else: self.future_data = self.future_data[1:] + [ self.fair_value_future ] else: self.fair_value_etf = (ask_prices[0] + bid_prices[0]) / 2 self.best_etf_ask_price = ask_prices[0] self.best_etf_bid_price = bid_prices[0] volume = V_MAX # print(f"Volume: {volume}") if np.abs(volume + self.net_position) >= NET_POS_THRESHOLD: self.dump_position() if len(self.active_ask_orders) < ACTIVE_ORDER_COUNT_LIM//2 and len(self.active_bid_orders) < \ ACTIVE_ORDER_COUNT_LIM//2 and len(self.future_data) == NUM_POINTS: slope, intercept, r_value, p_value, std_err = lin_reg( range(NUM_POINTS), self.future_data) # Case 1) - futures market not trending, p-value is for hypothesis test that slope is equal to 0 if p_value >= CRIT_VAL: ask_id = next(self.order_ids) # print(f"Ask price: {self.best_etf_ask_price}") self.send_insert_order(ask_id, Side.SELL, self.best_etf_ask_price, volume, Lifespan.GOOD_FOR_DAY) self.active_ask_orders[ask_id] = [ self.best_etf_ask_price, volume, 0 ] bid_id = next(self.order_ids) # print(f"Bid price: {self.best_etf_bid_price}") self.send_insert_order(bid_id, Side.BUY, self.best_etf_bid_price, volume, Lifespan.GOOD_FOR_DAY) self.active_bid_orders[bid_id] = [ self.best_etf_bid_price, volume, 0 ] # Case 2) - trending futures market else: # Upward trending futures market if slope > 0: # Fair value of future higher than fair value of etf if self.fair_value_future > self.fair_value_etf: ask_id = next(self.order_ids) # print(f"Ask price: {self.best_future_ask_price}") self.send_insert_order(ask_id, Side.SELL, self.best_future_ask_price, volume, Lifespan.GOOD_FOR_DAY) self.active_ask_orders[ask_id] = [ self.best_future_ask_price, volume, 0 ] bid_id = next(self.order_ids) bid_price = max(self.best_future_bid_price, self.best_etf_ask_price) # print(f"Bid price: {bid_price}") self.send_insert_order(bid_id, Side.BUY, bid_price, volume, Lifespan.GOOD_FOR_DAY) self.active_bid_orders[bid_id] = [bid_price, volume, 0] # Fair value of future lower than fair value of etf else: ask_id = next(self.order_ids) # print(f"Ask price: {self.best_etf_ask_price}") self.send_insert_order(ask_id, Side.SELL, self.best_etf_ask_price, volume, Lifespan.GOOD_FOR_DAY) self.active_ask_orders[ask_id] = [ self.best_etf_ask_price, volume, 0 ] bid_id = next(self.order_ids) bid_price = max(self.best_future_ask_price, self.best_etf_bid_price) # print(f"Bid price: {bid_price}") self.send_insert_order(bid_id, Side.BUY, bid_price, volume, Lifespan.GOOD_FOR_DAY) self.active_bid_orders[bid_id] = [bid_price, volume, 0] # Downward trending futures market else: # Fair value of future higher than fair value of etf if self.fair_value_future > self.fair_value_etf: ask_id = next(self.order_ids) ask_price = min(self.best_future_bid_price, self.best_etf_ask_price) # print(f"Ask price: {ask_price}") self.send_insert_order(ask_id, Side.SELL, ask_price, volume, Lifespan.GOOD_FOR_DAY) self.active_ask_orders[ask_id] = [ask_price, volume, 0] bid_id = next(self.order_ids) # print(f"Bid price: {self.best_etf_bid_price}") self.send_insert_order(bid_id, Side.BUY, self.best_etf_bid_price, volume, Lifespan.GOOD_FOR_DAY) self.active_bid_orders[bid_id] = [ self.best_etf_bid_price, volume, 0 ] else: ask_id = next(self.order_ids) ask_price = min(self.best_future_ask_price, self.best_etf_bid_price) # print(f"Ask price: {ask_price}") self.send_insert_order(ask_id, Side.SELL, ask_price, volume, Lifespan.GOOD_FOR_DAY) self.active_ask_orders[ask_id] = [ask_price, volume, 0] bid_id = next(self.order_ids) # print(f"Bid price: {self.best_future_bid_price}") self.send_insert_order(bid_id, Side.BUY, self.best_future_bid_price, volume, Lifespan.GOOD_FOR_DAY) self.active_bid_orders[bid_id] = [ self.best_future_bid_price, volume, 0 ] # Dealing with stale orders for ask_order_id in list(self.active_ask_orders): self.active_ask_orders[ask_order_id][2] += 1 if self.active_ask_orders[ask_order_id][2] >= STALE_THRESHOLD: self.send_cancel_order(ask_order_id) self.active_ask_orders.pop(ask_order_id) for bid_order_id in list(self.active_bid_orders): self.active_bid_orders[bid_order_id][2] += 1 if self.active_bid_orders[bid_order_id][2] >= STALE_THRESHOLD: self.send_cancel_order(bid_order_id) self.active_bid_orders.pop(bid_order_id)