def convert_snapshot_message_to_order_book_row( message: OrderBookMessage ) -> Tuple[List[OrderBookRow], List[OrderBookRow]]: update_id = message.update_id data = [] if "data" in message.content: # From REST API data: List[Dict[str, Any]] = message.content["data"] elif "order_books" in message.content: # From Websocket API data: List[Dict[str, Any]] = message.content["order_books"] bids, asks = [], [] for entry in data: order_row = OrderBookRow(float(entry["price"]), float(entry["quantity"]), update_id) if entry["side"] == "buy": bids.append(order_row) else: # entry["type"] == "Sell": asks.append(order_row) return bids, asks
def test_asset_limit(self): self.market_2.order_books[self.market_2_trading_pairs[0]].apply_diffs( [OrderBookRow(1.1, 30, 2)], [], 2 ) self.market_1.set_balance("COINALPHA", 40) self.market_2.set_balance("COINALPHA", 20) amount, profitability, bid_price, ask_price = self.strategy.find_best_profitable_amount(self.market_trading_pair_tuple_1, self.market_trading_pair_tuple_2) self.assertEqual(20.0, amount) self.assertAlmostEqual(Decimal("1.0330510429366988"), profitability) self.market_2.set_balance("COINALPHA", 0) amount, profitability, bid_price, ask_price = self.strategy.find_best_profitable_amount(self.market_trading_pair_tuple_1, self.market_trading_pair_tuple_2) self.assertEqual(Decimal("0"), amount) self.assertAlmostEqual(Decimal("1.0399044681062792"), profitability)
async def _test_spreads_adjusted_on_volatility_async(self): try: script_file = realpath(join(__file__, "../../scripts/spreads_adjusted_on_volatility_script.py")) self._script_iterator = ScriptIterator(script_file, [self.market], self.one_level_strategy, 0.01, True) self.clock.add_iterator(self._script_iterator) strategy = self.one_level_strategy self.clock.add_iterator(strategy) await self.turn_clock(1) self.assertEqual(1, len(strategy.active_buys)) self.assertEqual(1, len(strategy.active_sells)) self.assertEqual(Decimal("0.01"), strategy.bid_spread) self.assertEqual(Decimal("0.01"), strategy.ask_spread) await self.turn_clock(155) self.assertEqual(1, len(strategy.active_buys)) self.assertEqual(1, len(strategy.active_sells)) simulate_order_book_widening(self.book_data.order_book, 100, 105) self.book_data.order_book.apply_diffs([OrderBookRow(100, 30, 2)], [], 2) await self.turn_clock(160) mid_price = self.market.get_mid_price(self.trading_pair) print(mid_price) # The median volatility over the long period is at 0 # The average volatility over the short period is now at 0.00916 # So the adjustment is 0.0075 (rounded by 0.0025 increment) # await self.turn_clock(161) self.assertEqual(Decimal("0.0175"), strategy.bid_spread) self.assertEqual(Decimal("0.0175"), strategy.ask_spread) self.assertEqual(1, len(strategy.active_buys)) self.assertEqual(1, len(strategy.active_sells)) await self.turn_clock(185) # No more further price movement, which means volatility is now back to 0. # Spreads are adjusted back to the originals self.assertEqual(Decimal("0.01"), strategy.bid_spread) self.assertEqual(Decimal("0.01"), strategy.ask_spread) self.assertEqual(1, len(strategy.active_buys)) self.assertEqual(1, len(strategy.active_sells)) finally: self._script_iterator.stop(self.clock)
def _parse_raw_update(self, pair: str, raw_response: str) -> OrderBookMessage: """ Parses raw update, if price for a tracked order identified by ID is 0, then order is deleted Returns OrderBookMessage """ _, content = ujson.loads(raw_response) if isinstance(content, list) and len(content) == 3: order_id = content[0] price = content[1] amount = content[2] os = self._get_tracked_order_by_id(order_id) order = os["order"] side = os["side"] if order is not None: # this is not a new order. Either update it or delete it if price == 0: self._untrack_order(order_id) # print("-------------- Deleted order %d" % (order_id)) return self._generate_delete_message( pair, order.price, side) else: self._track_order( order_id, OrderBookRow(price, abs(amount), order.update_id), side) return None else: # this is a new order unless the price is 0, just track it and create message that # will add it to the order book if price != 0: # print("-------------- Add order %d" % (order_id)) return self._generate_add_message(pair, price, amount) return None
def bids(self) -> List[OrderBookRow]: # raise NotImplementedError("Dexfin order book messages have different semantics.") return [ OrderBookRow(float(price), float(amount), self.update_id) for price, amount, *trash in self.content.get("bids", []) ]
def asks(self) -> (List[OrderBookRow]): return [ OrderBookRow(float(ask["price"]), float(ask["quantity"]), self.update_id) for ask in self.content.get("asks", []) ]
def bids(self) -> (List[OrderBookRow]): return [ OrderBookRow(float(bid["price"]), float(bid["quantity"]), self.update_id) for bid in self.content.get("bids", []) ]
def asks(self) -> List[OrderBookRow]: results = [ OrderBookRow(float(ask[0]), float(ask[1]), self.update_id) for ask in self.content["asks"] ] sorted(results, key=lambda a: a.price) return results
def test_buy_diff_message(self): order_books: Dict[str, OrderBook] = self.order_book_tracker.order_books test_order_book: OrderBook = order_books[test_trading_pair] test_active_order_tracker = self.order_book_tracker._active_order_trackers[ test_trading_pair] # receive open buy message to be added to active orders order_id = "abc" side = "buy" price = 1337.0 open_size = 100.0 open_sequence = 1 open_message_dict: Dict[str, Any] = { "type": "open", "time": "2014-11-07T08:19:27.028459Z", "product_id": test_trading_pair, "sequence": open_sequence, "order_id": order_id, "price": str(price), "remaining_size": str(open_size), "side": side } open_message: CoinbaseProOrderBookMessage = test_order_book.diff_message_from_exchange( open_message_dict) open_ob_row: OrderBookRow = test_active_order_tracker.convert_diff_message_to_order_book_row( open_message) self.assertEqual(open_ob_row[0], [OrderBookRow(price, open_size, open_sequence)]) # receive change message change_size = 50.0 change_sequence = 2 change_message_dict: Dict[str, Any] = { "type": "change", "time": "2014-11-07T08:19:27.028459Z", "sequence": change_sequence, "order_id": order_id, "product_id": test_trading_pair, "new_size": str(change_size), "old_size": "100.0", "price": str(price), "side": side } change_message: CoinbaseProOrderBookMessage = test_order_book.diff_message_from_exchange( change_message_dict) change_ob_row: OrderBookRow = test_active_order_tracker.convert_diff_message_to_order_book_row( change_message) self.assertEqual(change_ob_row[0], [OrderBookRow(price, change_size, change_sequence)]) # receive match message match_size = 30.0 match_sequence = 3 match_message_dict: Dict[str, Any] = { "type": "match", "trade_id": 10, "sequence": match_sequence, "maker_order_id": order_id, "taker_order_id": "132fb6ae-456b-4654-b4e0-d681ac05cea1", "time": "2014-11-07T08:19:27.028459Z", "product_id": test_trading_pair, "size": str(match_size), "price": str(price), "side": side } match_message: CoinbaseProOrderBookMessage = test_order_book.diff_message_from_exchange( match_message_dict) match_ob_row: OrderBookRow = test_active_order_tracker.convert_diff_message_to_order_book_row( match_message) self.assertEqual( match_ob_row[0], [OrderBookRow(price, change_size - match_size, match_sequence)]) # receive done message done_size = 0.0 done_sequence = 4 done_message_dict: Dict[str, Any] = { "type": "done", "time": "2014-11-07T08:19:27.028459Z", "product_id": test_trading_pair, "sequence": done_sequence, "price": str(price), "order_id": order_id, "reason": "filled", "side": side, "remaining_size": "0" } done_message: CoinbaseProOrderBookMessage = test_order_book.diff_message_from_exchange( done_message_dict) done_ob_row: OrderBookRow = test_active_order_tracker.convert_diff_message_to_order_book_row( done_message) self.assertEqual(done_ob_row[0], [OrderBookRow(price, done_size, done_sequence)])
def test_minimum_spread_param(self): strategy = self.strategy self.clock.add_iterator(strategy) self.clock.backtest_til(self.start_timestamp + self.clock_tick_size) self.assertEqual(1, len(strategy.active_buys)) self.assertEqual(1, len(strategy.active_sells)) old_bid = strategy.active_buys[0] old_ask = strategy.active_sells[0] # t = 2, No Change => orders should stay the same self.clock.backtest_til(self.start_timestamp + 2 * self.clock_tick_size) self.assertEqual(1, len(strategy.active_buys)) self.assertEqual(1, len(strategy.active_sells)) self.assertEqual(old_bid.client_order_id, strategy.active_buys[0].client_order_id) self.assertEqual(old_ask.client_order_id, strategy.active_sells[0].client_order_id) # Minimum Spread Threshold Cancellation # t = 3, Mid Market Price Moves Down - Below Min Spread (Old Bid) => Buy Order Cancelled self.maker_data.order_book.apply_diffs([OrderBookRow(50, 1000, 2)], [OrderBookRow(50, 1000, 2)], 2) self.clock.backtest_til(self.start_timestamp + 3 * self.clock_tick_size) self.assertEqual(0, len(strategy.active_buys)) self.assertEqual(1, len(strategy.active_sells)) # t = 30, New Set of Orders self.clock.backtest_til(self.start_timestamp + 32 * self.clock_tick_size) self.assertEqual(1, len(strategy.active_buys)) self.assertEqual(1, len(strategy.active_sells)) new_bid = strategy.active_buys[0] new_ask = strategy.active_sells[0] self.assertNotEqual(old_bid.client_order_id, new_bid.client_order_id) self.assertNotEqual(old_ask.client_order_id, new_ask.client_order_id) old_ask = new_ask old_bid = new_bid # t = 35, No Change self.clock.backtest_til(self.start_timestamp + 36 * self.clock_tick_size) self.assertEqual(1, len(strategy.active_buys)) self.assertEqual(1, len(strategy.active_sells)) self.assertEqual(old_bid.client_order_id, strategy.active_buys[0].client_order_id) self.assertEqual(old_ask.client_order_id, strategy.active_sells[0].client_order_id) # t = 36, Mid Market Price Moves Up - Below Min Spread (Old Ask) => Sell Order Cancelled # Clear Order Book (setting all orders above price 0, to quantity 0) simulate_order_book_widening(self.maker_data.order_book, 0, 0) # New Mid-Market Price self.maker_data.order_book.apply_diffs([OrderBookRow(99, 1000, 3)], [OrderBookRow(101, 1000, 3)], 3) # Check That Order Book Manipulations Didn't Affect Strategy Orders Yet self.assertEqual(1, len(strategy.active_buys)) self.assertEqual(1, len(strategy.active_sells)) self.assertEqual(old_bid.client_order_id, strategy.active_buys[0].client_order_id) self.assertEqual(old_ask.client_order_id, strategy.active_sells[0].client_order_id) # Simulate Minimum Spread Threshold Cancellation self.clock.backtest_til(self.start_timestamp + 40 * self.clock_tick_size) self.assertEqual(1, len(strategy.active_buys)) self.assertEqual(0, len(strategy.active_sells))
def test_buy_diff_message(self): order_books: Dict[str, OrderBook] = self.order_book_tracker.order_books test_order_book: OrderBook = order_books[test_trading_pair] test_active_order_tracker = self.order_book_tracker._active_order_trackers[ test_trading_pair] # receive open buy message to be added to active orders order_id = "abc" side = 1 price = 1337.0 open_size = 100.0 market_id = 51 open_sequence = int(datetime.now().timestamp() * 1000) open_message_dict: Dict[str, Any] = { "type": "o_placed", "timestamp": open_sequence, "marketId": market_id, "orderId": order_id, "limitPrice": str(price), "qty": str(open_size), "oType": 2, "side": side } open_message: EterbaseOrderBookMessage = test_order_book.diff_message_from_exchange( open_message_dict) open_ob_row: OrderBookRow = test_active_order_tracker.convert_diff_message_to_order_book_row( open_message) self.assertEqual(open_ob_row[0], [OrderBookRow(price, open_size, open_sequence)]) # receive match message match_size = 30.0 match_sequence = int(datetime.now().timestamp() * 1000) match_message_dict: Dict[str, Any] = { "type": "o_fill", "tradeId": 10, "orderId": order_id, "timestamp": match_sequence, "marketId": market_id, "qty": str(match_size), "remainingQty": str(open_size - match_size), "limitPrice": str(price), "side": side } match_message: EterbaseOrderBookMessage = test_order_book.diff_message_from_exchange( match_message_dict) match_ob_row: OrderBookRow = test_active_order_tracker.convert_diff_message_to_order_book_row( match_message) self.assertEqual( match_ob_row[0], [OrderBookRow(price, open_size - match_size, match_sequence)]) # receive done message done_size = 0.0 done_sequence = int(datetime.now().timestamp() * 1000) done_message_dict: Dict[str, Any] = { "type": "o_closed", "timestamp": done_sequence, "marketId": market_id, "price": str(price), "orderId": order_id, "closeReason": "FILLED", "side": side, "remainingQty": "0" } done_message: EterbaseOrderBookMessage = test_order_book.diff_message_from_exchange( done_message_dict) done_ob_row: OrderBookRow = test_active_order_tracker.convert_diff_message_to_order_book_row( done_message) self.assertEqual(done_ob_row[0], [OrderBookRow(price, done_size, done_sequence)])
def bids(self) -> List[OrderBookRow]: results = [ OrderBookRow(float(bid[0]), float(bid[1]), self.update_id) for bid in self.content["bids"] ] sorted(results, key=lambda a: a.price) return results
def test_ask_side_profitable(self): self.maker_data.order_book.apply_diffs([OrderBookRow(1.0, 5, 2)], [], 2) self.clock.backtest_til(self.start_timestamp + 5) self.assertEqual(0, len(self.strategy.active_bids)) self.assertEqual(1, len(self.strategy.active_asks))
def bids(self) -> List[OrderBookRow]: return [ OrderBookRow(float(price), float(size), self.update_id) for price, size, *trash in self.content["bid"] ]
def bids(self) -> List[OrderBookRow]: return [ OrderBookRow(float(price), float(amount), self.update_id, self.timestamp) for price, amount, *trash in self.content["bids"] ]
def bids(self) -> List[OrderBookRow]: bids = map(self.content["bids"], lambda bid: {"price": bid[0], "amount": bid[1]}) return [ OrderBookRow(float(price), float(amount), self.update_id) for price, amount in bids ]
def asks(self) -> List[OrderBookRow]: asks = map(self.content["asks"], lambda ask: {"price": ask[0], "amount": ask[1]}) return [ OrderBookRow(float(price), float(amount), self.update_id) for price, amount in asks ]
def bids(self) -> (List[OrderBookRow]): return [ OrderBookRow(float(price), float(amount), self.update_id) for price, amount, *trash in self.content.get("bids", []) ]
def test_insert_update_delete_messages(self): active_tracker = BeaxyActiveOrderTracker() # receive INSERT message to be added to active orders side = "BID" price = 1337.4423423404 quantity: float = 1 update_id = 123 message_dict: Dict[str, Any] = { "action": "INSERT", "quantity": quantity, "price": price, "side": side, "sequenceNumber": update_id, "sequrity": test_trading_pair } insert_message = BeaxyOrderBook.diff_message_from_exchange( message_dict, float(12345)) insert_ob_row: OrderBookRow = active_tracker.convert_diff_message_to_order_book_row( insert_message) self.assertEqual(insert_ob_row[0], [OrderBookRow(price, quantity, update_id)]) # receive UPDATE message updated_quantity: float = 3.2 update_message_dict: Dict[str, Any] = { "action": "UPDATE", "quantity": updated_quantity, "price": price, "side": side, "sequenceNumber": update_id + 1, "sequrity": test_trading_pair } change_message = BeaxyOrderBook.diff_message_from_exchange( update_message_dict, float(12345)) change_ob_row: OrderBookRow = active_tracker.convert_diff_message_to_order_book_row( change_message) self.assertEqual( change_ob_row[0], [OrderBookRow(price, float(updated_quantity), update_id + 1)]) # receive DELETE message delete_quantity = 1 delete_message_dict: Dict[str, Any] = { "action": "DELETE", "quantity": delete_quantity, "price": price, "side": side, "sequenceNumber": update_id + 1 + 1, "sequrity": test_trading_pair } delete_message: BeaxyOrderBookMessage = BeaxyOrderBook.diff_message_from_exchange( delete_message_dict, float(12345)) delete_ob_row: OrderBookRow = active_tracker.convert_diff_message_to_order_book_row( delete_message) self.assertEqual(delete_ob_row[0], [ OrderBookRow(price, float(updated_quantity) - float(delete_quantity), update_id + 1 + 1) ])
def test_arbitrage_not_profitable(self): self.market_2_data.order_book.apply_diffs([OrderBookRow(1.05, 1.0, 2)], [], 2) self.clock.backtest_til(self.start_timestamp + 1) taker_orders = self.strategy.tracked_limit_orders + self.strategy.tracked_market_orders self.assertTrue(len(taker_orders) == 0)
async def _test_dynamic_price_band_price_async(self): try: script_file = realpath( join(__file__, "../../../../scripts/dynamic_price_band_script.py")) self._script_iterator = ScriptIterator(script_file, [self.market], self.multi_levels_strategy, 0.01, True) self.clock.add_iterator(self._script_iterator) strategy = self.multi_levels_strategy self.clock.add_iterator(strategy) await self.turn_clock(1) self.assertEqual(3, len(strategy.active_buys)) self.assertEqual(3, len(strategy.active_sells)) await self.turn_clock(4) # self.book_data.order_book.apply_diffs([OrderBookRow(99.97, 30, 2)], [OrderBookRow(100.3, 30, 2)], 2) simulate_order_book_widening(self.book_data.order_book, 85, self.mid_price) mid_price = self.market.get_mid_price(self.trading_pair) await self.turn_clock(10) mid_price = self.market.get_mid_price(self.trading_pair) print(mid_price) self.assertLess(mid_price, Decimal(100 * 0.97)) # mid_price is now below 3% from the original (at 92.5), but band script won't kick in until at least 50s self.assertEqual(3, len(strategy.active_buys)) self.assertEqual(3, len(strategy.active_sells)) await self.turn_clock(55) self.assertEqual(3, len(strategy.active_buys)) self.assertEqual(3, len(strategy.active_sells)) simulate_order_book_widening(self.book_data.order_book, 75, self.mid_price) self.book_data.order_book.apply_diffs([], [OrderBookRow(76, 30, 2)], 2) mid_price = self.market.get_mid_price(self.trading_pair) print(mid_price) await self.turn_clock(80) self.assertEqual(3, len(strategy.active_buys)) self.assertEqual(0, len(strategy.active_sells)) # after another 40 ticks, the mid price avg is now at 75.25, both buys and sells back on the market await self.turn_clock(110) self.assertEqual(3, len(strategy.active_buys)) self.assertEqual(3, len(strategy.active_sells)) simulate_order_book_widening(self.book_data.order_book, 76, 90) self.book_data.order_book.apply_diffs([OrderBookRow(85, 30, 2)], [OrderBookRow(86, 30, 2)], 3) mid_price = self.market.get_mid_price(self.trading_pair) print(mid_price) # Market now move up over 10% await self.turn_clock(120) self.assertEqual(0, len(strategy.active_buys)) self.assertEqual(3, len(strategy.active_sells)) # As no further movement in market prices, avg starts to catch up to the mid price await self.turn_clock(160) self.assertEqual(3, len(strategy.active_buys)) self.assertEqual(3, len(strategy.active_sells)) finally: self._script_iterator.stop(self.clock)
def asks(self) -> List[OrderBookRow]: return [OrderBookRow(float(price), float(amount), self.update_id) for price, amount, *trash in self.content["asks"]]