def test_no_matching_orders(self): # Orderbook for ABC book = Orderbook('ABC') bob = Client('Bob') alice = Client('Alice') eve = Client('Eve') # Bob offers @ 9, alice asks @ 10, bob cancels then eve asks @ 8.5 bob_order = Order("ABC", 9.00, 2, Side.BUY, bob.client_id) book.submit_order(bob_order) book.submit_order(Order("ABC", 10.00, 1, Side.SELL, alice.client_id)) book.cancel_order(bob_order.order_id) book.submit_order(Order("ABC", 8.50, 1, Side.SELL, eve.client_id)) # Check that the orders weren't matched bob_fills = book.get_filled_by_client_id(bob.client_id) alice_fills = book.get_filled_by_client_id(alice.client_id) eve_fills = book.get_filled_by_client_id(eve.client_id) self.assertEqual(len(bob_fills), 0) self.assertEqual(len(alice_fills), 0) self.assertEqual(len(eve_fills), 0) self.assertFalse(book.is_matched())
def test_matching_order(self): # Orderbook for ABC book = Orderbook('ABC') bob = Client('Bob') alice = Client('Alice') # Bob buys at 10, Alice sells at 10 book.submit_order(Order("ABC", 10.00, 1, Side.BUY, bob.client_id)) book.submit_order(Order("ABC", 10.00, 1, Side.SELL, alice.client_id)) # Check that the orders were matched bob_fills = book.get_filled_by_client_id(bob.client_id) alice_fills = book.get_filled_by_client_id(alice.client_id) self.assertEqual(len(bob_fills), 1) self.assertEqual(len(alice_fills), 1) # Check that the fills are correctly recorded bob_fill = bob_fills[0] alice_fill = bob_fills[0] self.assertEqual(bob_fill.maker, bob.client_id) self.assertEqual(bob_fill.taker, alice.client_id) self.assertEqual(bob_fill.size, 1) self.assertEqual(bob_fill.side, Side.SELL) self.assertEqual(bob_fill.instrument, 'ABC') self.assertEqual(bob_fill, alice_fill)
def test_price_time_priority(self): # Orderbook for ABC book = Orderbook('ABC') bob = Client('Bob') alice = Client('Alice') eve = Client('Eve') # Bob buys at 10, Alice sells at 10, Eve tries to sell at 10 as well book.submit_order(Order("ABC", 10.00, 1, Side.BUY, bob.client_id)) book.submit_order(Order("ABC", 10.00, 1, Side.SELL, alice.client_id)) book.submit_order(Order("ABC", 10.00, 1, Side.SELL, eve.client_id)) # Check that the orders were matched bob_fills = book.get_filled_by_client_id(bob.client_id) alice_fills = book.get_filled_by_client_id(alice.client_id) eve_fills = book.get_filled_by_client_id(eve.client_id) self.assertEqual(len(bob_fills), 1) self.assertEqual(len(alice_fills), 1) self.assertEqual(len(eve_fills), 0) # Check that the fills are correctly recorded bob_fill = bob_fills[0] alice_fill = bob_fills[0] self.assertEqual(bob_fill.maker, bob.client_id) self.assertEqual(bob_fill.taker, alice.client_id) self.assertEqual(bob_fill.size, 1) self.assertEqual(bob_fill.side, Side.SELL) self.assertEqual(bob_fill.instrument, 'ABC') self.assertEqual(bob_fill, alice_fill) # Check that Eve's order is still on the book self.assertEqual(len(book.get_orders_by_client_id(eve.client_id)), 1)
def test_invalid_side(self): # Try making an order with side of 0 with self.assertRaises(ValueError): o = Order("ABC", 10.00, 5, 0, 123) # Try making an order with side of 'BUY' with self.assertRaises(ValueError): o = Order("ABC", 10.00, 5, 'BUY', 123)
def test_non_integer_size(self): # Try making an order with size 'a' with self.assertRaises(ValueError): o = Order("ABC", 10.00, 'a', Side.BUY, 123) # Try making an order with size 1.5 with self.assertRaises(ValueError): o = Order("ABC", 10.00, 1.5, Side.BUY, 123)
def test_serialize_order(self): o = Order("ABC", 10.00, 10, Side.BUY, 123) o_serialized = Order.serialize(o) self.assertEqual(o_serialized, "o|ABC|10.0000|10|BUY|123") o = Order("CBA", 12.333, 432, Side.SELL, 4242) o_serialized = Order.serialize(o) self.assertEqual(o_serialized, "o|CBA|12.3330|432|SELL|4242")
def generate_order(instrument, client_id): size = int(np.random.normal(SIZE_MEAN, SIZE_SD)) size = max(1, size) if random.random() > 0.5: price = np.random.normal(99.00, PRICE_SD) return Order(instrument, price, size, Side.BUY, client_id) else: price = np.random.normal(101.00, PRICE_SD) return Order(instrument, price, size, Side.SELL, client_id)
def test_price_time_order(self): """ Test the price-time ordering of orders. Orders should be sorted by price first (highest to lowest for buy orders and reverse for sell orders) and then by time. """ early_order = Order("ABC", 10.00, 1, Side.BUY, 123) late_order = Order("ABC", 10.00, 1, Side.BUY, 123) self.assertTrue(early_order < late_order, 'Early orders should come first') low_order = Order("ABC", 10.00, 1, Side.BUY, 123) high_order = Order("ABC", 11.00, 1, Side.BUY, 123) self.assertTrue(high_order < low_order, 'Buy orders with highest price come first') low_order = Order("ABC", 10.00, 1, Side.SELL, 123) high_order = Order("ABC", 11.00, 1, Side.SELL, 123) self.assertTrue(low_order < high_order, 'Sell orders with lowest price come first') high_order = Order("ABC", 11.00, 1, Side.SELL, 123) low_order = Order("ABC", 10.00, 1, Side.SELL, 123) self.assertTrue( low_order < high_order, 'Sell orders with lowest price come first, time second')
def test_best_offer(self): # Orderbook for ABC book = Orderbook('ABC') bob = Client('Bob') o1 = Order("ABC", 10.00, 1, Side.BUY, bob.client_id) book.submit_order(o1) self.assertEqual(book.best_offer(), 10.00) o2 = Order("ABC", 11.00, 1, Side.BUY, bob.client_id) book.submit_order(o2) self.assertEqual(book.best_offer(), 11.00) # Cancelling the second order should bring the best offer back to 10.00 book.cancel_order(o2.order_id) self.assertEqual(book.best_offer(), 10.00)
async def process_message(websocket, path): global messages, i, since_update msg = await websocket.recv() since_update += 1 message_type = msg.split('|', maxsplit=1)[0] if message_type == 'o': await websocket.send("ACK") o = Order.deserialize(msg) if o.size != 0: trades = book.submit_order(o) """ if len(trades) > 0: print('\n'.join(map(str, trades))) """ elif message_type == 'bb': await websocket.send(f"bb|{book.best_bid():.4f}") elif message_type == 'bo': await websocket.send(f"bo|{book.best_offer():.4f}") elif message_type == 'bbbo': await websocket.send( f"bbbo|{book.best_bid():.4f}|{book.best_offer():.4f}|{time.time_ns()}" )
def test_reject_order(self): book = Orderbook('ABC') bob = Client('Bob') # Should not be able to submit an order for another instrument with self.assertRaises(RejectedOrder): book.submit_order(Order("CBA", 9.00, 2, Side.BUY, bob.client_id))
def test_deserialize_order(self): o_serialized = "o|ABC|10.0000|10|BUY|123" o = Order.deserialize(o_serialized) self.assertEqual(o.instrument, "ABC") self.assertEqual(o.price, 10.00) self.assertEqual(o.size, 10) self.assertEqual(o.side, Side.BUY) self.assertEqual(o.client_id, 123) o_serialized = "o|CBA|12.3330|432|SELL|4242" o = Order.deserialize(o_serialized) self.assertEqual(o.instrument, "CBA") self.assertEqual(o.price, 12.333) self.assertEqual(o.size, 432) self.assertEqual(o.side, Side.SELL) self.assertEqual(o.client_id, 4242)
def test_split_orders(self): # Orderbook for ABC book = Orderbook('ABC') bob = Client('Bob') alice = Client('Alice') eve = Client('Eve') # Bob buys 2 at 10, Alice sells at 10, Eve tries to sell at 10 as well t1 = book.submit_order(Order("ABC", 10.00, 2, Side.BUY, bob.client_id)) t2 = book.submit_order( Order("ABC", 10.00, 1, Side.SELL, alice.client_id)) t3 = book.submit_order(Order("ABC", 10.00, 1, Side.SELL, eve.client_id)) # Check that the orders were matched bob_fills = book.get_filled_by_client_id(bob.client_id) alice_fills = book.get_filled_by_client_id(alice.client_id) eve_fills = book.get_filled_by_client_id(eve.client_id) self.assertEqual(len(bob_fills), 2) self.assertEqual(len(alice_fills), 1) self.assertEqual(len(eve_fills), 1) # Check that the fills are correctly recorded bob_fill = bob_fills[0] alice_fill = alice_fills[0] self.assertEqual(bob_fill.maker, bob.client_id) self.assertEqual(bob_fill.taker, alice.client_id) self.assertEqual(bob_fill.size, 1) self.assertEqual(bob_fill.side, Side.SELL) self.assertEqual(bob_fill.instrument, 'ABC') self.assertEqual(bob_fill, alice_fill) bob_fill = bob_fills[1] eve_fill = eve_fills[0] self.assertEqual(bob_fill.maker, bob.client_id) self.assertEqual(bob_fill.taker, eve.client_id) self.assertEqual(bob_fill.size, 1) self.assertEqual(bob_fill.side, Side.SELL) self.assertEqual(bob_fill.instrument, 'ABC') self.assertEqual(bob_fill, eve_fill)
def generate_order(instrument, client_id): global PRICE_MEAN price = round(np.random.normal(PRICE_MEAN, PRICE_SD), 2) PRICE_MEAN += (random.random() - 0.5) * RANDOM_WALK_COEFF size = int(25 * random.random()) size = max(1, size) side = Side.BUY if random.random() > 0.5 else Side.SELL return Order(instrument, price, size, side, client_id)
def test_cancel_order(self): # Orderbook for ABC book = Orderbook('ABC') bob = Client('Bob') o1 = Order("ABC", 10.00, 1, Side.BUY, bob.client_id) book.submit_order(o1) self.assertEqual(book.get_order_status(o1.order_id), OrderStatus.ACTIVE) book.cancel_order(o1.order_id) self.assertEqual(book.get_order_status(o1.order_id), OrderStatus.CANCELLED)
async def spam_orders(instrument, client_id): while True: o = generate_order(instrument, client_id) o_serialized = Order.serialize(o) msg = o_serialized async with websockets.connect(SERVER_ADDRESS) as websocket: await websocket.send(msg) print(f"> {msg}") resp = await websocket.recv() print(f"< {resp}") delay = np.random.normal(WAIT_MEAN, WAIT_SD) await usleep(delay)
def test_order_status(self): """ Order status should be OrderStatus.ACTIVE when the order is first created, then change to OrderStatus.FILLED if the order is successfully matched, or OrderStatus.CANCELLED if cancelled. """ o = Order("ABC", 50.00, 2, Side.BUY, 123) self.assertEqual(o.get_status(), OrderStatus.ACTIVE) o.cancel() self.assertEqual(o.get_status(), OrderStatus.CANCELLED)
def test_invalid_client_id(self): # Try making an order with client_id 'abc' with self.assertRaises(ValueError): o = Order("ABC", 10.00, 5, Side.BUY, 'abc')
def test_negative_price(self): # Try making an order at price of -10.00 with self.assertRaises(ValueError): o = Order("ABC", -10.00, 5, Side.BUY, 123)
def test_negative_size(self): # Try making an order with size -5 with self.assertRaises(ValueError): o = Order("ABC", 10.00, -5, Side.BUY, 123)