async def _trade(self, msg: dict, timestamp: float): """ example message: {"channel": "trades", "market": "BTC-PERP", "type": "update", "data": [{"id": null, "price": 10738.75, "size": 0.3616, "side": "buy", "liquidation": false, "time": "2019-08-03T12:20:19.170586+00:00"}]} """ for trade in msg['data']: await self.callback(TRADES, feed=self.id, symbol=symbol_exchange_to_std(msg['market']), side=BUY if trade['side'] == 'buy' else SELL, amount=Decimal(trade['size']), price=Decimal(trade['price']), order_id=None, timestamp=float(timestamp_normalize(self.id, trade['time'])), receipt_timestamp=timestamp) if bool(trade['liquidation']): await self.callback(LIQUIDATIONS, feed=self.id, symbol=symbol_exchange_to_std(msg['market']), side=BUY if trade['side'] == 'buy' else SELL, leaves_qty=Decimal(trade['size']), price=Decimal(trade['price']), order_id=None, timestamp=float(timestamp_normalize(self.id, trade['time'])), receipt_timestamp=timestamp )
async def _trade(self, msg: dict, timestamp: float): """ example message: {"channel": "trades", "market": "BTC-PERP", "type": "update", "data": [{"id": null, "price": 10738.75, "size": 0.3616, "side": "buy", "liquidation": false, "time": "2019-08-03T12:20:19.170586+00:00"}]} """ # THis part is for merging trades which are supposed to be from same taker order reconst_trades = [] for trade in msg['data']: inl = in_dictlist('time', trade['time'], reconst_trades) if inl is None: trade['fills'] = 1 reconst_trades.append(trade) else: reconst_trades[inl]['size'] = Decimal( reconst_trades[inl]['size']) + Decimal(trade['size']) reconst_trades[inl]['fills'] = reconst_trades[inl]['fills'] + 1 # reconst_trades[inl]['amount'] = reconst_trades[inl]['amount'] + trade['amount'] for trade in reconst_trades: await self.callback( TRADES, feed=self.id, pair=symbol_exchange_to_std(msg['market']), side=BUY if trade['side'] == 'buy' else SELL, amount=Decimal(trade['size']), price=Decimal(trade['price']), order_id=None, timestamp=float(timestamp_normalize(self.id, trade['time'])), fills=trade['fills'], recons=True, receipt_timestamp=timestamp) ''' await self.callback(TRADES, feed=self.id, pair=pair_exchange_to_std(msg['market']), side=BUY if trade['side'] == 'buy' else SELL, amount=Decimal(trade['size']), price=Decimal(trade['price']), order_id=None, timestamp=float(timestamp_normalize(self.id, trade['time'])), receipt_timestamp=timestamp)''' if bool(trade['liquidation']): await self.callback( LIQUIDATIONS, feed=self.id, pair=symbol_exchange_to_std(msg['market']), side=BUY if trade['side'] == 'buy' else SELL, leaves_qty=Decimal(trade['size']), price=Decimal(trade['price']), order_id=None, timestamp=float(timestamp_normalize( self.id, trade['time'])), receipt_timestamp=timestamp)
async def _book(self, msg: dict, timestamp: float): """ example messages: snapshot: {"channel": "orderbook", "market": "BTC/USD", "type": "partial", "data": {"time": 1564834586.3382702, "checksum": 427503966, "bids": [[10717.5, 4.092], ...], "asks": [[10720.5, 15.3458], ...], "action": "partial"}} update: {"channel": "orderbook", "market": "BTC/USD", "type": "update", "data": {"time": 1564834587.1299787, "checksum": 3115602423, "bids": [], "asks": [[10719.0, 14.7461]], "action": "update"}} """ check = msg['data']['checksum'] if msg['type'] == 'partial': # snapshot pair = symbol_exchange_to_std(msg['market']) self.l2_book[pair] = { BID: sd({ Decimal(price): Decimal(amount) for price, amount in msg['data']['bids'] }), ASK: sd({ Decimal(price): Decimal(amount) for price, amount in msg['data']['asks'] }) } if self.checksum_validation and self.__calc_checksum( pair) != check: raise BadChecksum await self.book_callback(self.l2_book[pair], L2_BOOK, pair, True, None, float(msg['data']['time']), timestamp) else: # update delta = {BID: [], ASK: []} pair = symbol_exchange_to_std(msg['market']) for side in ('bids', 'asks'): s = BID if side == 'bids' else ASK for price, amount in msg['data'][side]: price = Decimal(price) amount = Decimal(amount) if amount == 0: delta[s].append((price, 0)) del self.l2_book[pair][s][price] else: delta[s].append((price, amount)) self.l2_book[pair][s][price] = amount if self.checksum_validation and self.__calc_checksum( pair) != check: raise BadChecksum await self.book_callback(self.l2_book[pair], L2_BOOK, pair, False, delta, float(msg['data']['time']), timestamp)
async def _book_update(self, msg: dict, timestamp: float): ''' { 'type': 'match', or last_match 'trade_id': 43736593 'maker_order_id': '2663b65f-b74e-4513-909d-975e3910cf22', 'taker_order_id': 'd058d737-87f1-4763-bbb4-c2ccf2a40bde', 'side': 'buy', 'size': '0.01235647', 'price': '8506.26000000', 'product_id': 'BTC-USD', 'sequence': 5928276661, 'time': '2018-05-21T00:26:05.585000Z' } ''' pair = symbol_exchange_to_std(msg['product_id']) ts = timestamp_normalize(self.id, msg['time']) if self.keep_l3_book and ('full' in self.channels or ('full' in self.subscription and pair in self.subscription['full'])): delta = {BID: [], ASK: []} price = Decimal(msg['price']) side = ASK if msg['side'] == 'sell' else BID size = Decimal(msg['size']) maker_order_id = msg['maker_order_id'] _, new_size = self.order_map[maker_order_id] new_size -= size if new_size <= 0: del self.order_map[maker_order_id] self.order_type_map.pop(maker_order_id, None) delta[side].append((maker_order_id, price, 0)) del self.l3_book[pair][side][price][maker_order_id] if len(self.l3_book[pair][side][price]) == 0: del self.l3_book[pair][side][price] else: self.order_map[maker_order_id] = (price, new_size) self.l3_book[pair][side][price][maker_order_id] = new_size delta[side].append((maker_order_id, price, new_size)) await self.book_callback(self.l3_book[pair], L3_BOOK, pair, False, delta, ts, timestamp) order_type = self.order_type_map.get(msg['taker_order_id']) await self.callback(TRADES, feed=self.id, symbol=symbol_exchange_to_std(msg['product_id']), order_id=msg['trade_id'], side=SELL if msg['side'] == 'buy' else BUY, amount=Decimal(msg['size']), price=Decimal(msg['price']), timestamp=ts, receipt_timestamp=timestamp, order_type=order_type )
async def _book(self, msg: dict, timestamp: float): if msg['action'] == 'partial': # snapshot for update in msg['data']: pair = symbol_exchange_to_std(update['instrument_id']) self.l2_book[pair] = { BID: sd({ Decimal(price): Decimal(amount) for price, amount, *_ in update['bids'] }), ASK: sd({ Decimal(price): Decimal(amount) for price, amount, *_ in update['asks'] }) } if self.checksum_validation and self.__calc_checksum(pair) != ( update['checksum'] & 0xFFFFFFFF): raise BadChecksum await self.book_callback( self.l2_book[pair], L2_BOOK, pair, True, None, timestamp_normalize(self.id, update['timestamp']), timestamp) else: # update for update in msg['data']: delta = {BID: [], ASK: []} pair = symbol_exchange_to_std(update['instrument_id']) for side in ('bids', 'asks'): s = BID if side == 'bids' else ASK for price, amount, *_ in update[side]: price = Decimal(price) amount = Decimal(amount) if amount == 0: if price in self.l2_book[pair][s]: delta[s].append((price, 0)) del self.l2_book[pair][s][price] else: delta[s].append((price, amount)) self.l2_book[pair][s][price] = amount if self.checksum_validation and self.__calc_checksum(pair) != ( update['checksum'] & 0xFFFFFFFF): raise BadChecksum await self.book_callback( self.l2_book[pair], L2_BOOK, pair, False, delta, timestamp_normalize(self.id, update['timestamp']), timestamp)
async def _trade(self, msg: dict, timestamp: float): """ { "params": { "data": [ { "trade_seq": 933, "trade_id": "9178", "timestamp": 1550736299753, "tick_direction": 3, "price": 3948.69, "instrument_name": "BTC-PERPETUAL", "index_price": 3930.73, "direction": "sell", "amount": 10 } ], "channel": "trades.BTC-PERPETUAL.raw" }, "method": "subscription", "jsonrpc": "2.0" } """ for trade in msg["params"]["data"]: await self.callback( TRADES, feed=self.id, symbol=symbol_exchange_to_std(trade["instrument_name"]), order_id=trade['trade_id'], side=BUY if trade['direction'] == 'buy' else SELL, amount=Decimal(trade['amount']), price=Decimal(trade['price']), timestamp=timestamp_normalize(self.id, trade['timestamp']), receipt_timestamp=timestamp, ) if 'liquidation' in trade: await self.callback( LIQUIDATIONS, feed=self.id, symbol=symbol_exchange_to_std(trade["instrument_name"]), side=BUY if trade['direction'] == 'buy' else SELL, leaves_qty=Decimal(trade['amount']), price=Decimal(trade['price']), order_id=trade['trade_id'], timestamp=timestamp_normalize(self.id, trade['timestamp']), receipt_timestamp=timestamp)
async def _trade(self, msg: dict, timestamp: float): """ Doc : https://docs.upbit.com/v1.0.7/reference#시세-체결-조회 { 'ty': 'trade' // Event type 'cd': 'KRW-BTC', // Symbol 'tp': 6759000.0, // Trade Price 'tv': 0.03243003, // Trade volume(amount) 'tms': 1584257228806, // Timestamp 'ttms': 1584257228000, // Trade Timestamp 'ab': 'BID', // 'BID' or 'ASK' 'cp': 64000.0, // Change of price 'pcp': 6823000.0, // Previous closing price 'sid': 1584257228000000, // Sequential ID 'st': 'SNAPSHOT', // 'SNAPSHOT' or 'REALTIME' 'td': '2020-03-15', // Trade date utc 'ttm': '07:27:08', // Trade time utc 'c': 'FALL', // Change - 'FALL' / 'RISE' / 'EVEN' } """ price = Decimal(msg['tp']) amount = Decimal(msg['tv']) await self.callback(TRADES, feed=self.id, order_id=msg['sid'], symbol=symbol_exchange_to_std(msg['cd']), side=BUY if msg['ab'] == 'BID' else SELL, amount=amount, price=price, timestamp=timestamp_normalize(self.id, msg['ttms']), receipt_timestamp=timestamp)
async def _snapshot(self, pairs: list): await asyncio.sleep(5) urls = [ f'https://www.bitstamp.net/api/v2/order_book/{sym}' for sym in pairs ] async def fetch(session, url): async with session.get(url) as response: response.raise_for_status() return await response.json() async with aiohttp.ClientSession() as session: results = await asyncio.gather( *[fetch(session, url) for url in urls]) for r, pair in zip(results, pairs): std_pair = symbol_exchange_to_std(pair) if pair else 'BTC-USD' self.last_update_id[std_pair] = r['timestamp'] self.l2_book[std_pair] = {BID: sd(), ASK: sd()} for s, side in (('bids', BID), ('asks', ASK)): for update in r[s]: price = Decimal(update[0]) amount = Decimal(update[1]) self.l2_book[std_pair][side][price] = amount
async def _ticker(self, msg: dict, timestamp: float): """ https://binance-docs.github.io/apidocs/delivery/cn/#symbol { "e":"bookTicker", // 事件类型 "u":17242169, // 更新ID "s":"BTCUSD_200626", // 交易对 "ps":"BTCUSD", // 标的交易对 "b":"9548.1", // 买单最优挂单价格 "B":"52", // 买单最优挂单数量 "a":"9548.5", // 卖单最优挂单价格 "A":"11", // 卖单最优挂单数量 "T":1591268628155, // 撮合时间 "E":1591268628166 // 事件时间 } """ pair = symbol_exchange_to_std(msg['s']) bid = Decimal(msg['b']) ask = Decimal(msg['a']) await self.callback(TICKER, feed=self.id, symbol=pair, bid=bid, ask=ask, bid_amount=Decimal(msg["B"]), ask_amount=Decimal(msg["A"]), timestamp=timestamp_normalize(self.id, msg['E']), receipt_timestamp=timestamp)
def _reset(self): self.partial_received = defaultdict(bool) self.order_id = {} for pair in self.normalized_symbols: pair = symbol_exchange_to_std(pair) self.l2_book[pair] = {BID: sd(), ASK: sd()} self.order_id[pair] = defaultdict(dict)
async def _ticker(self, msg: dict, timestamp: float): """ { 'u': 382569232, 's': 'FETUSDT', 'b': '0.36031000', 'B': '1500.00000000', 'a': '0.36092000', 'A': '176.40000000' } """ pair = symbol_exchange_to_std(msg['s']) bid = Decimal(msg['b']) ask = Decimal(msg['a']) # Binance does not have a timestamp in this update, but the two futures APIs do if 'E' in msg: ts = timestamp_normalize(self.id, msg['E']) else: ts = timestamp await self.callback(TICKER, feed=self.id, symbol=pair, bid=bid, ask=ask, timestamp=ts, receipt_timestamp=timestamp)
async def _book(self, msg: dict, timestamp: float): pair = symbol_exchange_to_std(msg['symbol']) # Gemini sends ALL data for the symbol, so if we don't actually want # the book data, bail before parsing if self.channels and L2_BOOK not in self.channels: return if self.subscription and ((L2_BOOK in self.subscription and msg['symbol'] not in self.subscription[L2_BOOK]) or L2_BOOK not in self.subscription): return data = msg['changes'] forced = not len(self.l2_book[pair][BID]) delta = {BID: [], ASK: []} for entry in data: side = ASK if entry[0] == 'sell' else BID price = Decimal(entry[1]) amount = Decimal(entry[2]) if amount == 0: if price in self.l2_book[pair][side]: del self.l2_book[pair][side][price] delta[side].append((price, 0)) else: self.l2_book[pair][side][price] = amount delta[side].append((price, amount)) await self.book_callback(self.l2_book[pair], L2_BOOK, pair, forced, delta, timestamp, timestamp)
async def _book_update(self, msg: dict, timestamp: float): ts = msg["params"]["data"]["timestamp"] pair = symbol_exchange_to_std(msg["params"]["data"]["instrument_name"]) if msg['params']['data']['prev_change_id'] != self.seq_no[pair]: LOG.warning("%s: Missing sequence number detected for %s", self.id, pair) LOG.warning("%s: Requesting book snapshot", self.id) raise MissingSequenceNumber self.seq_no[pair] = msg['params']['data']['change_id'] delta = {BID: [], ASK: []} for action, price, amount in msg["params"]["data"]["bids"]: bidask = self.l2_book[pair][BID] if action != "delete": bidask[price] = Decimal(amount) delta[BID].append((Decimal(price), Decimal(amount))) else: del bidask[price] delta[BID].append((Decimal(price), Decimal(amount))) for action, price, amount in msg["params"]["data"]["asks"]: bidask = self.l2_book[pair][ASK] if action != "delete": bidask[price] = amount delta[ASK].append((Decimal(price), Decimal(amount))) else: del bidask[price] delta[ASK].append((Decimal(price), Decimal(amount))) await self.book_callback(self.l2_book[pair], L2_BOOK, pair, False, delta, timestamp_normalize(self.id, ts), timestamp)
async def _trade(self, msg: dict, timestamp: float): """ { "jsonrpc":"2.0", "method":"channelMessage", "params":{ "channel":"lightning_executions_BTC_JPY", "message":[ { "id":2084881071, "side":"BUY", "price":2509125.0, "size":0.005, "exec_date":"2020-12-25T21:36:22.8840579Z", "buy_child_order_acceptance_id":"JRF20201225-213620-004123", "sell_child_order_acceptance_id":"JRF20201225-213620-133314" } ] } } """ pair = msg['params']['channel'][21:] pair = symbol_exchange_to_std(pair) for update in msg['params']['message']: await self.callback(TRADES, feed=self.id, order_id=update['id'], symbol=pair, side=BUY if update['side'] == 'BUY' else SELL, amount=update['size'], price=update['price'], timestamp=timestamp_normalize( self.id, update['exec_date']), receipt_timestamp=timestamp)
async def _pair_l3_update(self, msg: str, timestamp: float): delta = {BID: [], ASK: []} pair = symbol_exchange_to_std(msg['symbol']) if msg['event'] == 'snapshot': # Reset the book self.l3_book[pair] = {BID: sd(), ASK: sd()} book = self.l3_book[pair] for side in (BID, ASK): for update in msg[side + 's']: price = update['px'] qty = update['qty'] order_id = update['id'] p_orders = book[side].get(price, sd()) p_orders[order_id] = qty if qty <= 0: del p_orders[order_id] book[side][price] = p_orders if len(book[side][price]) == 0: del book[side][price] delta[side].append((order_id, price, qty)) self.l3_book[pair] = book await self.book_callback(self.l3_book[pair], L3_BOOK, pair, False, delta, timestamp, timestamp)
async def _trade(self, msg: dict, timestamp: float): """ { 'ch': 'market.adausdt.trade.detail', 'ts': 1597792835344, 'tick': { 'id': 101801945127, 'ts': 1597792835336, 'data': [ { 'id': Decimal('10180194512782291967181675'), <- per docs this is deprecated 'ts': 1597792835336, 'tradeId': 100341530602, 'amount': Decimal('0.1'), 'price': Decimal('0.137031'), 'direction': 'sell' } ] } } """ for trade in msg['tick']['data']: await self.callback( TRADES, feed=self.id, symbol=symbol_exchange_to_std(msg['ch'].split('.')[1]), order_id=trade['tradeId'], side=BUY if trade['direction'] == 'buy' else SELL, amount=Decimal(trade['amount']), price=Decimal(trade['price']), timestamp=timestamp_normalize(self.id, trade['ts']), receipt_timestamp=timestamp)
async def _ticker(self, msg: dict, timestamp: float): ''' https://huobiapi.github.io/docs/spot/v1/cn/#9d97b30872 { "ch": "market.btcusdt.bbo", "ts": 1489474082831, //system update time "tick": { "symbol": "btcusdt", "quoteTime": "1489474082811", "bid": "10008.31", "bidSize": "0.01", "ask": "10009.54", "askSize": "0.3", "seqId":"10242474683" } } ''' await self.callback(TICKER, feed=self.id, symbol=symbol_exchange_to_std( msg['ch'].split('.')[1]), bid=Decimal(msg['tick']['bid']), bid_amount=Decimal(msg['tick']['bidSize']), ask=Decimal(msg['tick']['ask']), ask_amount=Decimal(msg['tick']['askSize']), timestamp=timestamp_normalize(self.id, msg['ts']), receipt_timestamp=timestamp)
async def _book_snapshot(self, pairs: list): # Coinbase needs some time to send messages to us # before we request the snapshot. If we don't sleep # the snapshot seq no could be much earlier than # the subsequent messages, causing a seq no mismatch. await asyncio.sleep(2) url = 'https://api.pro.coinbase.com/products/{}/book?level=3' urls = [url.format(pair) for pair in pairs] results = [] for url in urls: ret = requests.get(url) results.append(ret) # rate limit - 3 per second await asyncio.sleep(0.3) timestamp = time.time() for res, pair in zip(results, pairs): orders = res.json() npair = symbol_exchange_to_std(pair) self.l3_book[npair] = {BID: sd(), ASK: sd()} self.seq_no[npair] = orders['sequence'] for side in (BID, ASK): for price, size, order_id in orders[side + 's']: price = Decimal(price) size = Decimal(size) if price in self.l3_book[npair][side]: self.l3_book[npair][side][price][order_id] = size else: self.l3_book[npair][side][price] = {order_id: size} self.order_map[order_id] = (price, size) await self.book_callback(self.l3_book[npair], L3_BOOK, npair, True, None, timestamp, timestamp)
async def _trade(self, msg: dict, timestamp: float): """ { "e": "aggTrade", // Event type "E": 123456789, // Event time "s": "BNBBTC", // Symbol "a": 12345, // Aggregate trade ID "p": "0.001", // Price "q": "100", // Quantity "f": 100, // First trade ID "l": 105, // Last trade ID "T": 123456785, // Trade time "m": true, // Is the buyer the market maker? "M": true // Ignore } """ price = Decimal(msg['p']) amount = Decimal(msg['q']) await self.callback(TRADES, feed=self.id, order_id=msg['a'], symbol=symbol_exchange_to_std(msg['s']), side=SELL if msg['m'] else BUY, amount=amount, price=price, timestamp=timestamp_normalize(self.id, msg['E']), receipt_timestamp=timestamp)
async def _done(self, msg: dict, timestamp: float): """ per Coinbase API Docs: A done message will be sent for received orders which are fully filled or canceled due to self-trade prevention. There will be no open message for such orders. Done messages for orders which are not on the book should be ignored when maintaining a real-time order book. """ if 'price' not in msg: return order_id = msg['order_id'] self.order_type_map.pop(order_id, None) if order_id not in self.order_map: return del self.order_map[order_id] if self.keep_l3_book: delta = {BID: [], ASK: []} price = Decimal(msg['price']) side = ASK if msg['side'] == 'sell' else BID pair = symbol_exchange_to_std(msg['product_id']) ts = timestamp_normalize(self.id, msg['time']) del self.l3_book[pair][side][price][order_id] if len(self.l3_book[pair][side][price]) == 0: del self.l3_book[pair][side][price] delta[side].append((order_id, price, 0)) await self.book_callback(self.l3_book[pair], L3_BOOK, pair, False, delta, ts, timestamp)
async def _liquidations(self, msg: dict, timestamp: float): """ { "e":"forceOrder", // Event Type "E":1568014460893, // Event Time "o":{ "s":"BTCUSDT", // Symbol "S":"SELL", // Side "o":"LIMIT", // Order Type "f":"IOC", // Time in Force "q":"0.014", // Original Quantity "p":"9910", // Price "ap":"9910", // Average Price "X":"FILLED", // Order Status "l":"0.014", // Order Last Filled Quantity "z":"0.014", // Order Filled Accumulated Quantity "T":1568014460893, // Order Trade Time } } """ pair = symbol_exchange_to_std(msg['o']['s']) await self.callback(LIQUIDATIONS, feed=self.id, symbol=pair, side=msg['o']['S'], leaves_qty=Decimal(msg['o']['q']), price=Decimal(msg['o']['p']), order_id=None, timestamp=timestamp_normalize(self.id, msg['E']), receipt_timestamp=timestamp)
async def _change(self, msg: dict, timestamp: float): """ Like done, these updates can be sent for orders that are not in the book. Per the docs: Not all done or change messages will result in changing the order book. These messages will be sent for received orders which are not yet on the order book. Do not alter the order book for such messages, otherwise your order book will be incorrect. """ if not self.keep_l3_book: return delta = {BID: [], ASK: []} if 'price' not in msg or not msg['price']: return order_id = msg['order_id'] if order_id not in self.order_map: return ts = timestamp_normalize(self.id, msg['time']) price = Decimal(msg['price']) side = ASK if msg['side'] == 'sell' else BID new_size = Decimal(msg['new_size']) pair = symbol_exchange_to_std(msg['product_id']) self.l3_book[pair][side][price][order_id] = new_size self.order_map[order_id] = (price, new_size) delta[side].append((order_id, price, new_size)) await self.book_callback(self.l3_book, L3_BOOK, pair, False, delta, ts, timestamp)
async def _trade(self, msg: dict, timestamp: float): """ trade msg example { 'timestamp': '2018-05-19T12:25:26.632Z', 'symbol': 'XBTUSD', 'side': 'Buy', 'size': 40, 'price': 8335, 'tickDirection': 'PlusTick', 'trdMatchID': '5f4ecd49-f87f-41c0-06e3-4a9405b9cdde', 'grossValue': 479920, 'homeNotional': Decimal('0.0047992'), 'foreignNotional': 40 } """ for data in msg['data']: ts = timestamp_normalize(self.id, data['timestamp']) await self.callback(TRADES, feed=self.id, symbol=symbol_exchange_to_std(data['symbol']), side=BUY if data['side'] == 'Buy' else SELL, amount=Decimal(data['size']), price=Decimal(data['price']), order_id=data['trdMatchID'], timestamp=ts, receipt_timestamp=timestamp)
async def message_handler(self, msg: str, conn, timestamp: float): msg = json.loads(msg, parse_float=Decimal) if 'event' in msg: if msg['event'] == 'info': return elif msg['event'] == 'subscribed': return else: LOG.warning("%s: Invalid message type %s", self.id, msg) else: pair = symbol_exchange_to_std(msg['product_id']) if msg['feed'] == 'trade': await self._trade(msg, pair, timestamp) elif msg['feed'] == 'trade_snapshot': return elif msg['feed'] == 'ticker_lite': await self._ticker(msg, pair, timestamp) elif msg['feed'] == 'ticker': await self._funding(msg, pair, timestamp) elif msg['feed'] == 'book_snapshot': await self._book_snapshot(msg, pair, timestamp) elif msg['feed'] == 'book': await self._book(msg, pair, timestamp) else: LOG.warning("%s: Invalid message type %s", self.id, msg)
async def _book(self, msg: dict, timestamp: float): pair = symbol_exchange_to_std(msg['ch'].split('.')[1]) data = msg['tick'] forced = pair not in self.l2_book update = { BID: sd({ Decimal(price): Decimal(amount) for price, amount in data['bids'] }), ASK: sd({ Decimal(price): Decimal(amount) for price, amount in data['asks'] }) } if not forced: self.previous_book[pair] = self.l2_book[pair] self.l2_book[pair] = update await self.book_callback(self.l2_book[pair], L2_BOOK, pair, forced, False, timestamp_normalize(self.id, msg['ts']), timestamp)
async def _open_interest(self, pairs: Iterable): """ { "openInterest": "10659.509", "symbol": "BTCUSDT", "time": 1589437530011 // Transaction time } """ rate_limiter = 2 # don't fetch too many pairs too fast async with aiohttp.ClientSession() as session: while True: for pair in pairs: end_point = f"{self.rest_endpoint}/openInterest?symbol={pair}" async with session.get(end_point) as response: data = await response.text() data = json.loads(data, parse_float=Decimal) oi = data['openInterest'] if oi != self.open_interest.get(pair, None): await self.callback( OPEN_INTEREST, feed=self.id, symbol=symbol_exchange_to_std(pair), open_interest=oi, timestamp=timestamp_normalize( self.id, data['time']), receipt_timestamp=time()) self.open_interest[pair] = oi await asyncio.sleep(rate_limiter) # Binance updates OI every 15 minutes, however not all pairs are ready exactly at :15 :30 :45 :00 wait_time = (17 - (datetime.now().minute % 15)) * 60 await asyncio.sleep(wait_time)
def register_channel_handler(self, msg: dict, conn: AsyncConnection): symbol = msg['symbol'] is_funding = (symbol[0] == 'f') pair = symbol_exchange_to_std(symbol) if msg['channel'] == 'ticker': if is_funding: LOG.warning('%s %s: Ticker funding not implemented - set _do_nothing() for %s', conn.uuid, pair, msg) handler = self._do_nothing else: handler = partial(self._ticker, pair) elif msg['channel'] == 'trades': if is_funding: handler = partial(self._funding, pair) else: handler = partial(self._trades, pair) elif msg['channel'] == 'book': if msg['prec'] == 'R0': handler = partial(self._raw_book, pair, self.l3_book[pair], self.order_map[pair]) elif is_funding: LOG.warning('%s %s: Book funding not implemented - set _do_nothing() for %s', conn.uuid, pair, msg) handler = self._do_nothing else: handler = partial(self._book, pair, self.l2_book[pair]) else: LOG.warning('%s %s: Unexpected message %s', conn.uuid, pair, msg) return LOG.debug('%s: Register channel=%s pair=%s funding=%s %s -> %s()', conn.uuid, msg['channel'], pair, is_funding, '='.join(list(msg.items())[-1]), handler.__name__ if hasattr(handler, '__name__') else handler.func.__name__) self.handlers[msg['chanId']] = handler
async def message_handler(self, msg: str, conn, timestamp: float): msg = json.loads(msg, parse_float=Decimal) if isinstance(msg, list): channel_id = msg[0] if channel_id not in self.channel_map: LOG.warning("%s: Invalid channel id received %d", self.id, channel_id) LOG.warning("%s: channel map: %s", self.id, self.channel_map) else: channel, pair = self.channel_map[channel_id] if channel == 'trade': await self._trade(msg, pair, timestamp) elif channel == 'ticker': await self._ticker(msg, pair, timestamp) elif channel == 'book': await self._book(msg, pair, timestamp) else: LOG.warning("%s: No mapping for message %s", self.id, msg) LOG.warning("%s: channel map: %s", self.id, self.channel_map) else: if msg['event'] == 'heartbeat': return elif msg['event'] == 'systemStatus': return elif msg['event'] == 'subscriptionStatus' and msg[ 'status'] == 'subscribed': self.channel_map[msg['channelID']] = ( msg['subscription']['name'], symbol_exchange_to_std(msg['pair'])) else: LOG.warning("%s: Invalid message type %s", self.id, msg)
async def _l2_book(self, msg: dict, timestamp: float): data = msg['data'] chan = msg['channel'] ts = int(data['microtimestamp']) pair = symbol_exchange_to_std(chan.split('_')[-1]) forced = False delta = {BID: [], ASK: []} if pair in self.last_update_id: if data['timestamp'] < self.last_update_id[pair]: return else: forced = True del self.last_update_id[pair] for side in (BID, ASK): for update in data[side + 's']: price = Decimal(update[0]) size = Decimal(update[1]) if size == 0: if price in self.l2_book[pair][side]: del self.l2_book[pair][side][price] delta[side].append((price, size)) else: self.l2_book[pair][side][price] = size delta[side].append((price, size)) await self.book_callback(self.l2_book[pair], L2_BOOK, pair, forced, delta, timestamp_normalize(self.id, ts), timestamp)
async def _book(self, msg: dict, timestamp: float): sequence_number = msg['data']['seqnum'] pair = symbol_exchange_to_std(msg['symbol']) delta = {BID: [], ASK: []} forced = False if msg['m'] == 'depth-snapshot': forced = True self.seq_no[pair] = sequence_number self.l2_book[pair] = {BID: sd(), ASK: sd()} else: # ignore messages while we wait for the snapshot if self.seq_no[pair] is None: return if self.seq_no[pair] + 1 != sequence_number: raise MissingSequenceNumber self.seq_no[pair] = sequence_number for side in ('bids', 'asks'): for price, amount in msg['data'][side]: s = BID if side == 'bids' else ASK price = Decimal(price) size = Decimal(amount) if size == 0: delta[s].append((price, 0)) if price in self.l2_book[pair][s]: del self.l2_book[pair][s][price] else: delta[s].append((price, size)) self.l2_book[pair][s][price] = size await self.book_callback( self.l2_book[pair], L2_BOOK, pair, forced, delta, timestamp_normalize(self.id, msg['data']['ts']), timestamp)