def test_book_delta_simple(): a = {BID: {1.0: 1, 0.9: 0.5, 0.8: 2}, ASK: {1.1: 1.1, 1.2: 0.6, 1.3: 2.1}} b = {BID: {0.9: 0.5, 0.8: 2}, ASK: {1.1: 1.1, 1.2: 0.6, 1.3: 2.1}} assert book_delta(a, b) == {'bid': [(1.0, 0)], 'ask': []} assert book_delta(b, a) == {'bid': [(1.0, 1)], 'ask': []}
def test_book_delta_empty(): a = {BID: {1.0: 1, 0.9: 0.5, 0.8: 2}, ASK: {1.1: 1.1, 1.2: 0.6, 1.3: 2.1}} b = {BID: {}, ASK: {}} assert book_delta(a, b) == { 'bid': [(0.9, 0), (1.0, 0), (0.8, 0)], 'ask': [(1.2, 0), (1.1, 0), (1.3, 0)] } assert book_delta(b, a) == { 'ask': [(1.2, 0.6), (1.1, 1.1), (1.3, 2.1)], 'bid': [(0.9, 0.5), (1.0, 1), (0.8, 2)] }
async def book_callback(self, book, book_type, pair, forced, delta, timestamp): """ Three cases we need to handle here 1. Book deltas are enabled (application of max depth here is trivial) 1a. Book deltas are enabled, max depth is not, and exchange does not support deltas. Rare 2. Book deltas not enabled, but max depth is enabled 3. Neither deltas nor max depth enabled 2 and 3 can be combined into a single block as long as application of depth modification happens first For 1, need to handle separate cases where a full book is returned vs a delta """ if self.do_deltas: if not forced and self.updates[pair] < self.book_update_interval: if self.max_depth: delta, book = await self.apply_depth(book, True, pair) if not (delta[BID] or delta[ASK]): return elif not delta: # this will only happen in cases where an exchange does not support deltas and max depth is not enabled. # this is an uncommon situation. Exchanges that do not support deltas will need # to populate self.previous internally to avoid the unncesessary book copy on all other exchanges delta = book_delta(self.previous_book[pair], book, book_type=book_type) if not (delta[BID] or delta[ASK]): return self.updates[pair] += 1 await self.callback(BOOK_DELTA, feed=self.id, pair=pair, delta=delta, timestamp=timestamp) if self.updates[pair] != self.book_update_interval: return elif forced and self.max_depth: # We want to send a full book update but need to apply max depth first _, book = await self.apply_depth(book, False, pair) elif self.max_depth: changed, book = await self.apply_depth(book, False, pair) if not changed: return if book_type == L2_BOOK: await self.callback(L2_BOOK, feed=self.id, pair=pair, book=book, timestamp=timestamp) else: await self.callback(L3_BOOK, feed=self.id, pair=pair, book=book, timestamp=timestamp) self.updates[pair] = 0
async def apply_depth(self, book: dict, do_delta: bool, symbol: str): ret = depth(book, self.max_depth) if not do_delta: delta = self.previous_book[symbol] != ret self.previous_book[symbol] = ret return delta, ret delta = book_delta(self.previous_book[symbol], ret) self.previous_book[symbol] = ret return delta, ret
async def apply_depth(self, book: dict, do_delta: bool, pair: str): ret = depth(book, self.max_depth) if not do_delta: delta = self.previous_book[pair] != ret self.previous_book[pair] = ret return delta, ret delta = [] delta = book_delta(self.previous_book[pair], ret) self.previous_book[pair] = ret return delta, ret
async def apply_depth(self, book: dict, do_delta: bool) -> Tuple[list, dict]: ret = depth(book, self.max_depth) if not do_delta: self.previous_book = ret return {BID: [], ASK: []}, ret delta = [] delta = book_delta(self.previous_book, ret) self.previous_book = ret return delta, ret
async def book_callback(self, book: dict, book_type: str, pair: str, forced: bool, delta: dict, timestamp: float, receipt_timestamp: float): """ Three cases we need to handle here 1. Book deltas are enabled (application of max depth here is trivial) 1a. Book deltas are enabled, max depth is not, and exchange does not support deltas. Rare 2. Book deltas not enabled, but max depth is enabled 3. Neither deltas nor max depth enabled 4. Book deltas disabled and snapshot intervals enabled (with/without max depth) 2 and 3 can be combined into a single block as long as application of depth modification happens first For 1, need to handle separate cases where a full book is returned vs a delta """ if self.do_deltas: if not forced and self.updates[pair] < self.book_update_interval: if self.max_depth: delta, book = await self.apply_depth(book, True, pair) if not (delta[BID] or delta[ASK]): return elif not delta: # this will only happen in cases where an exchange does not support deltas and max depth is not enabled. # this is an uncommon situation. Exchanges that do not support deltas will need # to populate self.previous internally to avoid the unncesessary book copy on all other exchanges delta = book_delta(self.previous_book[pair], book, book_type=book_type) if not (delta[BID] or delta[ASK]): return self.updates[pair] += 1 if self.cross_check: self.check_bid_ask_overlapping(book, pair) await self.callback(BOOK_DELTA, feed=self.id, pair=pair, delta=delta, timestamp=timestamp, receipt_timestamp=receipt_timestamp) if self.updates[pair] != self.book_update_interval: return elif forced and self.max_depth: # We want to send a full book update but need to apply max depth first _, book = await self.apply_depth(book, False, pair) elif self.max_depth: if not self.snapshot_interval or (self.snapshot_interval and self.updates[pair] >= self.snapshot_interval): changed, book = await self.apply_depth(book, False, pair) if not changed: return # case 4 - incremement skiped update, and exit if self.snapshot_interval and self.updates[pair] < self.snapshot_interval: self.updates[pair] += 1 return if self.cross_check: self.check_bid_ask_overlapping(book, pair) if book_type == L2_BOOK: await self.callback(L2_BOOK, feed=self.id, pair=pair, book=book, timestamp=timestamp, receipt_timestamp=receipt_timestamp) else: await self.callback(L3_BOOK, feed=self.id, pair=pair, book=book, timestamp=timestamp, receipt_timestamp=receipt_timestamp) self.updates[pair] = 0