def on_new_order(self, order: ServerOrder, apply_changes=False) -> OrderBookChanges: """ Entry point to process a new order in order book apply_changes indicates either order book changes are applied directly at the end (testing purpose) """ if order.instrument_identifier != self.instrument_identifier: raise Exception( "[LOGIC FAILURE] Order instrument must match order book instrument" ) quantity_before_execution = order.get_remaining_quantity() changes = self.match_order(order) # Case 1: unmatched if quantity_before_execution == order.get_remaining_quantity(): logger.debug( f"Attacking order is unmatched, adding [{order}] to trading book" ) changes.order_to_add.append(order) # Case 2: partially executed (existing order(s) have been executed) elif order.get_remaining_quantity() > 0.0: logger.debug( f"Attacking order cannot be fully executed, adding [{order}] to trading book" ) changes.order_to_add.append(order) # Case 3: order has been fully executed else: logger.debug( f"Attacking order [{order}] has been totally executed") if apply_changes: self.apply_order_book_changes(changes) return changes
def test_fifo_matching_orders(self): """ Ensure FIFO matching is enforced """ orders = [ ServerOrder(Buy(), self.instrument.identifier, 50, 40.0, 'Trader1', timestamp=1), ServerOrder(Buy(), self.instrument.identifier, 50, 40.0, 'Trader2', timestamp=2), ServerOrder(Sell(), self.instrument.identifier, 50, 40.0, 'Trader3', timestamp=3) ] for order in orders: self.book.on_new_order(order) self.assertEqual(self.book.count_bids(), 1) self.assertEqual(self.book.count_asks(), 0) self.assertEqual(self.book.get_bids()[0].timestamp, 2) self.assertEqual(self.book.get_bids()[0].counterparty, 'Trader2')
def update_matched_orders(self, attacking_order: ServerOrder, matching_orders) -> OrderBookChanges: """ Iterate and update attacked orders, create deals upon total and partial executions """ changes = OrderBookChanges() for attacked_order in matching_orders: if self.is_attacked_order_full_executed(attacking_order, attacked_order): executed_quantity = attacked_order.get_remaining_quantity() attacking_order.executed_quantity += executed_quantity attacked_order.executed_quantity += executed_quantity self.update_statistics(last_executed_order=attacked_order) logger.debug(f"Attacker [{attacking_order.counterparty}]") logger.debug(f"Attacked [{attacked_order.counterparty}]") # Create deal deal = ServerDeal(attacking_order, attacked_order, executed_quantity) changes.deals_to_add.append(deal) # Remove executed order changes.order_to_remove.append(attacked_order) else: executed_quantity = attacking_order.get_remaining_quantity() attacking_order.executed_quantity += executed_quantity attacked_order.executed_quantity += executed_quantity self.update_statistics(last_executed_order=attacking_order) # Create a deal deal = ServerDeal(attacking_order, attacked_order, executed_quantity) changes.deals_to_add.append(attacking_order) if attacking_order.get_remaining_quantity() == 0.0: break return changes
def test_two_orders_no_match(self): buy_order = ServerOrder(Buy(), self.instrument.identifier, 50, 40.0, 'Trader1') sell_order = ServerOrder(Sell(), self.instrument.identifier, 50, 42.0, 'Trader2') self.book.on_new_order(buy_order) self.book.on_new_order(sell_order) self.assertEqual(self.book.count_bids(), 1) self.assertEqual(self.book.count_asks(), 1)
def test_two_orders_no_match(self): """ Two orders not matching (different price) """ buy_order = ServerOrder(Buy(), self.instrument.identifier, 50, 40.0, 'Trader1') sell_order = ServerOrder(Sell(), self.instrument.identifier, 50, 42.0, 'Trader2') self.book.on_new_order(buy_order, apply_changes=True) self.book.on_new_order(sell_order, apply_changes=True) self.assertEqual(self.book.count_bids(), 1) self.assertEqual(self.book.count_asks(), 1)
def test_self_execution(self): """ Ensure you cannot trade with yourself """ orders = [ ServerOrder(Buy(), self.instrument.identifier, 50, 40.0, 'Trader1'), ServerOrder(Sell(), self.instrument.identifier, 50, 40.0, 'Trader1') ] for order in orders: self.book.on_new_order(order) self.assertEqual(self.book.count_bids(), 1) self.assertEqual(self.book.count_asks(), 1)
def test_four_stacked_orders_no_match(self): orders = [ ServerOrder(Buy(), self.instrument.identifier, 50, 40.0, 'Trader1'), ServerOrder(Buy(), self.instrument.identifier, 50, 40.0, 'Trader1'), ServerOrder(Sell(), self.instrument.identifier, 50, 42.0, 'Trader2'), ServerOrder(Sell(), self.instrument.identifier, 50, 42.0, 'Trader2') ] for order in orders: self.book.on_new_order(order) self.assertEqual(self.book.count_bids(), 2) self.assertEqual(self.book.count_asks(), 2)
def handle_create_order(self, create_order, sock): client_session = self.client_sessions[sock] if client_session.status != SessionStatus.Authenticated: raise OrderRejected("Client is not authenticated") # TODO: does client session is allowed to create orders ? try: logger.info('Create order for {}'.format(client_session)) order_book = self.order_books[create_order.instrument_identifier] new_order = ServerOrder( way=create_order.way, instrument_identifier=create_order.instrument_identifier, quantity=create_order.quantity, price=create_order.price, counterparty=client_session.login) order_book_changes = order_book.on_new_order(new_order) order_book.apply_order_book_changes(order_book_changes) self.apply_order_book_changes_in_storage(order_book_changes) logger.debug(f"Changes to be applied:\n{str(order_book_changes)}") except KeyError: instrument_identifier = create_order.instrument_identifier logger.warning( f"Order book related to instrument identifier [{instrument_identifier}] does not exist" )
def test_buy_price_greater_than_sell(self): attacking_order = ServerOrder(Buy(), self.instrument.identifier, 10, 40.0, 'Trader1') attacked_order = ServerOrder(Sell(), self.instrument.identifier, 10, 38.0, 'Trader2') self.validate_one_matching(attacking_order, attacked_order) self.assertEqual(attacking_order.get_remaining_quantity(), 10) self.assertEqual(attacked_order.get_remaining_quantity(), 10)
def test_two_opposite_orders_in_order_book(self): order_book = OrderBook(self.instrument_identifier) orders = [ ServerOrder(Buy(), self.instrument_identifier, quantity=100.0, price=9.0, counterparty='Trader1'), ServerOrder(Sell(), self.instrument_identifier, quantity=100.0, price=10.0, counterparty='Trader2') ] for order in orders: order_book.add_order(order) encoded_order_book = self.marshaller.encode_order_book(order_book) message_type, body, _ = self.marshaller.decode_header( encoded_order_book) decoded_order_book = self.marshaller.decode_order_book(body) self.assertEqual(message_type, MessageTypes.OrderBook.value) self.assertEqual(encoded_order_book, self.marshaller.encode_order_book(decoded_order_book))
def test_one_buy_order_book(instrument_identifier, marshaller): simple_order_book = OrderBook(instrument_identifier) buy_order = ServerOrder(Buy(), instrument_identifier, quantity=100.0, price=10.0, counterparty='Trader1') simple_order_book.add_order(buy_order) encoded_order_book = marshaller.encode_order_book(simple_order_book) message_type, body, _ = marshaller.decode_header(encoded_order_book) decoded_order_book = marshaller.decode_order_book(body) assert message_type == MessageTypes.OrderBook.value assert encoded_order_book == marshaller.encode_order_book( decoded_order_book)
def test_buy_price_equal_to_sell(self): attacking_order = ServerOrder(Buy(), self.instrument.identifier, 10, 40.0, 'Trader1') attacked_order = ServerOrder(Sell(), self.instrument.identifier, 10, 40.0, 'Trader2') self.book.on_new_order(attacked_order, apply_changes=True) self.book.on_new_order(attacking_order, apply_changes=True) self.assertEqual(attacking_order.get_remaining_quantity(), 0) self.assertEqual(attacked_order.get_remaining_quantity(), 0)
def test_one_buy_order_book(self): simple_order_book = OrderBook(self.instrument_identifier) buy_order = ServerOrder(Buy(), self.instrument_identifier, quantity=100.0, price=10.0, counterparty='Trader1') simple_order_book.add_order(buy_order) encoded_order_book = self.marshaller.encode_order_book( simple_order_book) message_type, body, _ = self.marshaller.decode_header( encoded_order_book) decoded_order_book = self.marshaller.decode_order_book(body) self.assertEqual(message_type, MessageTypes.OrderBook.value) self.assertEqual(encoded_order_book, self.marshaller.encode_order_book(decoded_order_book))
def decode_order_book(self, encoded_order_book): order_book_message = orderbook_pb2.OrderBook() order_book_message.ParseFromString(encoded_order_book) order_book = OrderBook(order_book_message.instrument_identifier) order_book.last_price = order_book_message.statistics.last_price order_book.high_price = order_book_message.statistics.high_price order_book.low_price = order_book_message.statistics.low_price for decoded_order in order_book_message.orders: order = ServerOrder(identifier=decoded_order.identifier, way=OrderWay(decoded_order.way), instrument_identifier=order_book_message.instrument_identifier, quantity=decoded_order.quantity, canceled_quantity=decoded_order.canceled_quantity, executed_quantity=decoded_order.executed_quantity, price=decoded_order.price, counterparty=decoded_order.counterparty, timestamp=decoded_order.timestamp) order_book.add_order(order) self.logger.debug(order_book) return order_book
def test_one_full_execution(self): quantity = 10 price = 42.0 attacking_order = ServerOrder(Sell(), self.instrument.identifier, quantity, price, 'Trader1') attacked_order = ServerOrder(Buy(), self.instrument.identifier, quantity, price, 'Trader2') self.book.on_new_order(attacked_order) self.book.on_new_order(attacking_order) self.assertEqual(self.book.count_bids(), 0) self.assertEqual(self.book.count_asks(), 0) self.assertEqual(attacking_order.executed_quantity, quantity) self.assertEqual(attacked_order.executed_quantity, quantity) self.assertEqual(attacking_order.get_remaining_quantity(), 0) self.assertEqual(attacked_order.get_remaining_quantity(), 0)
def handle_create_order(self, create_order, sock): client_session = self.client_sessions[sock] if client_session.status != SessionStatus.Authenticated: raise OrderRejected('Client is not authenticated') # TODO: does client session is allowed to create orders ? try: self.logger.info('Create order for {}'.format(client_session)) order_book = self.order_books[create_order.instrument_identifier] except KeyError: self.logger.warning( 'Order book related to instrument identifier [{}] does not exist' .format(create_order.instrument_identifier)) else: new_order = ServerOrder( way=create_order.way, instrument_identifier=create_order.instrument_identifier, quantity=create_order.quantity, price=create_order.price, counterparty=client_session.login) order_book.on_new_order(new_order)
def decode_order_book(self, encoded_order_book): tokens = list(filter(None, encoded_order_book.split(self.separator))) instrument_identifier = int(tokens[0]) order_book = OrderBook(instrument_identifier) order_book.last_price = float(tokens[1]) order_book.high_price = float(tokens[2]) order_book.low_price = float(tokens[3]) order_tokens = tokens[4:] for x in range(0, len(order_tokens), 8): order_book.add_order( ServerOrder(instrument_identifier=instrument_identifier, identifier=order_tokens[x], way=OrderWay(int(order_tokens[x + 1])), quantity=float(order_tokens[x + 2]), canceled_quantity=float(order_tokens[x + 3]), executed_quantity=float(order_tokens[x + 4]), price=float(order_tokens[x + 5]), counterparty=order_tokens[x + 6], timestamp=order_tokens[x + 7])) return order_book
def test_one_partial_execution(self): attacking_quantity = 20 attacked_quantity = 10 price = 42.0 attacking_order = ServerOrder(Buy(), self.instrument.identifier, attacking_quantity, price, 'Trader1') attacked_order = ServerOrder(Sell(), self.instrument.identifier, attacked_quantity, price, 'Trader2') self.book.on_new_order(attacked_order, apply_changes=True) self.book.on_new_order(attacking_order, apply_changes=True) self.assertEqual(self.book.count_bids(), 1) self.assertEqual(self.book.count_asks(), 0) self.assertEqual(attacking_order.executed_quantity, attacking_quantity - attacked_quantity) self.assertEqual(attacked_order.executed_quantity, attacked_quantity) self.assertEqual(attacking_order.get_remaining_quantity(), attacking_quantity - attacked_quantity) self.assertEqual(attacked_order.get_remaining_quantity(), 0)
def test_multiple_partial_executions(self): attacking_quantity = 50 attacked_quantity = 10 price = 42.0 attacking_order = ServerOrder(Buy(), self.instrument.identifier, attacking_quantity, price, 'Trader1') attacked_orders = [] for _ in list(range(5)): attacked_order = ServerOrder(Sell(), self.instrument.identifier, attacked_quantity, price, 'Trader2') attacked_orders.append(attacked_order) self.book.on_new_order(attacked_order) self.book.on_new_order(attacking_order) self.assertEqual(self.book.count_bids(), 0) self.assertEqual(self.book.count_asks(), 0) self.assertEqual(attacking_order.executed_quantity, attacking_quantity) self.assertEqual(attacking_order.get_remaining_quantity(), 0) for attacked_order in attacked_orders: self.assertEqual(attacked_order.executed_quantity, attacked_quantity) self.assertEqual(attacked_order.get_remaining_quantity(), 0)
def test_sell_price_equal_to_buy(self): attacking_order = ServerOrder(Sell(), self.instrument.identifier, 10, 40.0, 'Trader1') attacked_order = ServerOrder(Buy(), self.instrument.identifier, 10, 40.0, 'Trader2') self.validate_one_matching(attacking_order, attacked_order)
def test_count_bids(self): buy_order = ServerOrder(Buy(), self.instrument.identifier, 50, 42.0, 'Trader1') self.assertEqual(self.book.count_bids(), 0) self.book.on_new_order(buy_order) self.assertEqual(self.book.count_bids(), 1)
def test_count_asks(self): sell_order = ServerOrder(Sell(), self.instrument.identifier, 50, 42.0, 'Trader1') self.assertEqual(self.book.count_asks(), 0) self.book.on_new_order(sell_order) self.assertEqual(self.book.count_asks(), 1)