def test_to_dict_returns_expected_dict(self): # Arrange snapshot = OrderBookSnapshot( instrument_id=AUDUSD, book_type=BookType.L2_MBP, bids=[[1010, 2], [1009, 1]], asks=[[1020, 2], [1021, 1]], update_id=123456789, ts_event=0, ts_init=1_000_000_000, ) # Act result = OrderBookSnapshot.to_dict(snapshot) # Assert assert result == { "type": "OrderBookSnapshot", "instrument_id": "AUD/USD.SIM", "book_type": "L2_MBP", "bids": b"[[1010,2],[1009,1]]", "asks": b"[[1020,2],[1021,1]]", "update_id": 123456789, "ts_event": 0, "ts_init": 1_000_000_000, }
def test_add_order_book_snapshots_adds_to_engine(self, capsys): # Arrange engine = BacktestEngine() engine.add_instrument(ETHUSDT_BINANCE) snapshot1 = OrderBookSnapshot( instrument_id=ETHUSDT_BINANCE.id, book_type=BookType.L2_MBP, bids=[[1550.15, 0.51], [1580.00, 1.20]], asks=[[1552.15, 1.51], [1582.00, 2.20]], ts_event=0, ts_init=0, ) snapshot2 = OrderBookSnapshot( instrument_id=ETHUSDT_BINANCE.id, book_type=BookType.L2_MBP, bids=[[1551.15, 0.51], [1581.00, 1.20]], asks=[[1553.15, 1.51], [1583.00, 2.20]], ts_event=1_000_000_000, ts_init=1_000_000_000, ) # Act engine.add_order_book_data([snapshot2, snapshot1]) # <-- reverse order # Assert log = "".join(capsys.readouterr()) assert "Added 2 ETHUSDT.BINANCE OrderBookData elements." in log
def test_apply(empty_l2_book, clock): snapshot = OrderBookSnapshot( instrument_id=empty_l2_book.instrument_id, book_type=BookType.L2_MBP, bids=[[150.0, 0.51]], asks=[[160.0, 1.51]], ts_event=0, ts_init=0, ) empty_l2_book.apply_snapshot(snapshot) assert empty_l2_book.best_ask_price() == 160 delta = OrderBookDelta( instrument_id=TestStubs.audusd_id(), book_type=BookType.L2_MBP, action=BookAction.ADD, order=Order( 155.0, 672.45, OrderSide.SELL, "4a25c3f6-76e7-7584-c5a3-4ec84808e240", ), ts_event=clock.timestamp(), ts_init=clock.timestamp(), ) empty_l2_book.apply(delta) assert empty_l2_book.best_ask_price() == 155
def test_order_book_when_order_book_exists_returns_expected(self): # Arrange snapshot = OrderBookSnapshot( instrument_id=ETHUSDT_BINANCE.id, book_type=BookType.L2_MBP, bids=[[1550.15, 0.51], [1580.00, 1.20]], asks=[[1552.15, 1.51], [1582.00, 2.20]], ts_event=0, ts_init=0, ) order_book = L2OrderBook( instrument_id=ETHUSDT_BINANCE.id, price_precision=2, size_precision=8, ) order_book.apply_snapshot(snapshot) self.cache.add_order_book(order_book) # Act result = self.cache.order_book(ETHUSDT_BINANCE.id) # Assert assert result == order_book
def test_from_dict_returns_expected_tick(self): # Arrange snapshot = OrderBookSnapshot( instrument_id=AUDUSD, book_type=BookType.L2_MBP, bids=[[1010, 2], [1009, 1]], asks=[[1020, 2], [1021, 1]], update_id=123456789, ts_event=0, ts_init=1_000_000_000, ) # Act result = OrderBookSnapshot.from_dict( OrderBookSnapshot.to_dict(snapshot)) # Assert assert result == snapshot
def test_orderbook_snapshot(empty_l2_book): snapshot = OrderBookSnapshot( instrument_id=empty_l2_book.instrument_id, book_type=BookType.L2_MBP, bids=[[1550.15, 0.51], [1580.00, 1.20]], asks=[[1552.15, 1.51], [1582.00, 2.20]], ts_event=0, ts_init=0, ) empty_l2_book.apply_snapshot(snapshot) assert empty_l2_book.best_bid_price() == 1580.0 assert empty_l2_book.best_ask_price() == 1552.15
def _handle_market_snapshot(selection, instrument, ts_event, ts_init): updates = [] # Check we only have one of [best bets / depth bets / all bets] bid_keys = [k for k in B_BID_KINDS if k in selection] or ["atb"] ask_keys = [k for k in B_ASK_KINDS if k in selection] or ["atl"] if set(bid_keys) == {"batb", "atb"}: bid_keys = ["atb"] if set(ask_keys) == {"batl", "atl"}: ask_keys = ["atl"] assert len(bid_keys) <= 1 assert len(ask_keys) <= 1 # OrderBook Snapshot # TODO(bm): Clean this up if bid_keys[0] == "atb": bids = selection.get("atb", []) else: bids = [(p, v) for _, p, v in selection.get(bid_keys[0], [])] if ask_keys[0] == "atl": asks = selection.get("atl", []) else: asks = [(p, v) for _, p, v in selection.get(ask_keys[0], [])] if bids or asks: snapshot = OrderBookSnapshot( book_type=BookType.L2_MBP, instrument_id=instrument.id, bids=[(price_to_probability(str(p)), v) for p, v in asks if p], asks=[(price_to_probability(str(p)), v) for p, v in bids if p], ts_event=ts_event, ts_init=ts_init, ) updates.append(snapshot) if "trd" in selection: updates.extend( _handle_market_trades( runner=selection, instrument=instrument, ts_event=ts_event, ts_init=ts_init, ) ) if "spb" in selection or "spl" in selection: updates.extend( _handle_bsp_updates( runner=selection, instrument=instrument, ts_event=ts_event, ts_init=ts_init, ) ) return updates
def parse_book_snapshot_ws(instrument_id: InstrumentId, msg: Dict, update_id: int, ts_init: int) -> OrderBookSnapshot: ts_event: int = ts_init return OrderBookSnapshot( instrument_id=instrument_id, book_type=BookType.L2_MBP, bids=[[float(o[0]), float(o[1])] for o in msg.get("bids")], asks=[[float(o[0]), float(o[1])] for o in msg.get("asks")], ts_event=ts_event, ts_init=ts_init, update_id=update_id, )
def parse_book_snapshot( instrument_id: InstrumentId, data: BinanceOrderBookData, ts_init: int, ) -> OrderBookSnapshot: return OrderBookSnapshot( instrument_id=instrument_id, book_type=BookType.L2_MBP, bids=[[float(o[0]), float(o[1])] for o in data.bids], asks=[[float(o[0]), float(o[1])] for o in data.asks], ts_event=millis_to_nanos(data.T), ts_init=ts_init, update_id=data.u, )
def _on_top_level_snapshot(self, ticker: Ticker): instrument_id = self._instrument_provider.contract_id_to_instrument_id[ ticker.contract.conId] ts_event = dt_to_unix_nanos(ticker.time) ts_init = self._clock.timestamp_ns() snapshot = OrderBookSnapshot( book_type=BookType.L1_TBBO, instrument_id=instrument_id, bids=[(ticker.bid, ticker.bidSize)], asks=[(ticker.ask, ticker.askSize)], ts_event=ts_event, ts_init=ts_init, ) self._handle_data(snapshot)
def parse_book_partial_ws( instrument_id: InstrumentId, data: Dict[str, Any], ts_init: int, ) -> OrderBookSnapshot: return OrderBookSnapshot( instrument_id=instrument_id, book_type=BookType.L2_MBP, bids=[[o[0], o[1]] for o in data.get("bids")], asks=[[o[0], o[1]] for o in data.get("asks")], ts_event=secs_to_nanos(data["time"]), ts_init=ts_init, update_id=data["checksum"], )
def _build_order_book_snapshot(values): # First value is a CLEAR message, which we ignore assert len(set([v["instrument_id"] for v in values])) == 1 assert len(values) >= 2, f"Not enough values passed! {values}" return OrderBookSnapshot( instrument_id=InstrumentId.from_str(values[1]["instrument_id"]), book_type=BookTypeParser.from_str_py(values[1]["book_type"]), bids=[(order["order_price"], order["order_size"]) for order in values[1:] if order["order_side"] == "BUY"], asks=[(order["order_price"], order["order_size"]) for order in values[1:] if order["order_side"] == "SELL"], ts_event=values[1]["ts_event"], ts_init=values[1]["ts_init"], )
def parse_spot_book_snapshot( instrument_id: InstrumentId, data: BinanceSpotOrderBookDepthData, ts_init: int, ) -> OrderBookSnapshot: return OrderBookSnapshot( instrument_id=instrument_id, book_type=BookType.L2_MBP, bids=[[float(o[0]), float(o[1])] for o in data.bids], asks=[[float(o[0]), float(o[1])] for o in data.asks], ts_event=ts_init, ts_init=ts_init, update_id=data.lastUpdateId, )
def test_handle_order_book_snapshot_sends_to_data_engine(self): # Arrange snapshot = OrderBookSnapshot( instrument_id=ETHUSDT_BINANCE.id, book_type=BookType.L2_MBP, bids=[[1000, 1]], asks=[[1001, 1]], ts_event=0, ts_init=0, ) # Act self.client._handle_data_py(snapshot) # Assert assert self.data_engine.data_count == 1
def _on_order_book_snapshot(self, ticker: Ticker, book_type: BookType = BookType.L2_MBP): instrument_id = self._instrument_provider.contract_id_to_instrument_id[ ticker.contract.conId] ts_event = dt_to_unix_nanos(ticker.time) ts_init = self._clock.timestamp_ns() if not (ticker.domBids or ticker.domAsks): return snapshot = OrderBookSnapshot( book_type=book_type, instrument_id=instrument_id, bids=[(level.price, level.size) for level in ticker.domBids], asks=[(level.price, level.size) for level in ticker.domAsks], ts_event=ts_event, ts_init=ts_init, ) self._handle_data(snapshot)
def test_hash_str_and_repr(self): # Arrange snapshot = OrderBookSnapshot( instrument_id=AUDUSD, book_type=BookType.L2_MBP, bids=[[1010, 2], [1009, 1]], asks=[[1020, 2], [1021, 1]], ts_event=0, ts_init=0, ) # Act, Assert assert isinstance(hash(snapshot), int) assert ( str(snapshot) == "OrderBookSnapshot('AUD/USD.SIM', book_type=L2_MBP, bids=[[1010, 2], [1009, 1]], asks=[[1020, 2], [1021, 1]], update_id=0, ts_event=0, ts_init=0)" # noqa ) assert ( repr(snapshot) == "OrderBookSnapshot('AUD/USD.SIM', book_type=L2_MBP, bids=[[1010, 2], [1009, 1]], asks=[[1020, 2], [1021, 1]], update_id=0, ts_event=0, ts_init=0)" # noqa )
def order_book_snapshot( instrument_id=None, bid_price=10, ask_price=15, bid_levels=3, ask_levels=3, bid_volume=10, ask_volume=10, book_type=BookType.L2_MBP, ) -> OrderBookSnapshot: err = "Too many levels generated; orders will be in cross. Increase bid/ask spread or reduce number of levels" assert bid_price < ask_price, err return OrderBookSnapshot( instrument_id=instrument_id or TestStubs.audusd_id(), book_type=book_type, bids=[(float(bid_price - i), float(bid_volume * (1 + i))) for i in range(bid_levels)], asks=[(float(ask_price + i), float(ask_volume * (1 + i))) for i in range(ask_levels)], ts_event=0, ts_init=0, )
async def _subscribe_order_book( self, instrument_id: InstrumentId, book_type: BookType, depth: Optional[int] = None, ) -> None: if book_type == BookType.L3_MBO: self._log.error( "Cannot subscribe to order book deltas: " "L3_MBO data is not published by Binance. " "Valid book types are L1_TBBO, L2_MBP.", ) return if depth is None or depth == 0: depth = 20 # Add delta stream buffer self._book_buffer[instrument_id] = [] if depth <= 20: if depth not in (5, 10, 20): self._log.error( "Cannot subscribe to order book snapshots: " f"invalid depth, was {depth}. " "Valid depths are 5, 10 or 20.", ) return self._ws_client.subscribe_partial_book_depth( symbol=instrument_id.symbol.value, depth=depth, speed=100, ) else: self._ws_client.subscribe_diff_book_depth( symbol=instrument_id.symbol.value, speed=100, ) while not self._ws_client.is_connected: await self.sleep0() data: Dict[str, Any] = await self._http_market.depth( symbol=instrument_id.symbol.value, limit=depth, ) ts_event: int = self._clock.timestamp_ns() last_update_id: int = data.get("lastUpdateId") snapshot = OrderBookSnapshot( instrument_id=instrument_id, book_type=BookType.L2_MBP, bids=[[float(o[0]), float(o[1])] for o in data.get("bids")], asks=[[float(o[0]), float(o[1])] for o in data.get("asks")], ts_event=ts_event, ts_init=ts_event, update_id=last_update_id, ) self._handle_data(snapshot) book_buffer = self._book_buffer.pop(instrument_id) for deltas in book_buffer: if deltas.update_id <= last_update_id: continue self._handle_data(deltas)
def test_fully_qualified_name(self): # Arrange, Act, Assert assert (OrderBookSnapshot.fully_qualified_name() == "nautilus_trader.model.orderbook.data.OrderBookSnapshot")