async def _ticker(self, msg: dict, timestamp: float): ''' { "params" : { "data" : { "timestamp" : 1550652954406, "stats" : { "volume" : null, "low" : null, "high" : null }, "state" : "open", "settlement_price" : 3960.14, "open_interest" : 0.12759952124659626, "min_price" : 3943.21, "max_price" : 3982.84, "mark_price" : 3940.06, "last_price" : 3906, "instrument_name" : "BTC-PERPETUAL", "index_price" : 3918.51, "funding_8h" : 0.01520525, "current_funding" : 0.00499954, "best_bid_price" : 3914.97, "best_bid_amount" : 40, "best_ask_price" : 3996.61, "best_ask_amount" : 50 }, "channel" : "ticker.BTC-PERPETUAL.raw" }, "method" : "subscription", "jsonrpc" : "2.0"} ''' pair = msg['params']['data']['instrument_name'] ts = timestamp_normalize(self.id, msg['params']['data']['timestamp']) await self.callback( TICKER, feed=self.id, symbol=pair, bid=Decimal(msg["params"]["data"]['best_bid_price']), ask=Decimal(msg["params"]["data"]['best_ask_price']), timestamp=ts, receipt_timestamp=timestamp) if "current_funding" in msg["params"]["data"] and "funding_8h" in msg[ "params"]["data"]: await self.callback(FUNDING, feed=self.id, symbol=pair, timestamp=ts, receipt_timestamp=timestamp, rate=msg["params"]["data"]["current_funding"], rate_8h=msg["params"]["data"]["funding_8h"]) oi = msg['params']['data']['open_interest'] if pair in self.open_interest and oi == self.open_interest[pair]: return self.open_interest[pair] = oi await self.callback(OPEN_INTEREST, feed=self.id, symbol=pair, open_interest=oi, timestamp=ts, receipt_timestamp=timestamp)
async def _instrument(self, msg): """ Example instrument data { 'table':'instrument', 'action':'partial', 'keys':[ 'symbol' ], 'types':{ 'symbol':'symbol', 'rootSymbol':'symbol', 'state':'symbol', 'typ':'symbol', 'listing':'timestamp', 'front':'timestamp', 'expiry':'timestamp', 'settle':'timestamp', 'relistInterval':'timespan', 'inverseLeg':'symbol', 'sellLeg':'symbol', 'buyLeg':'symbol', 'optionStrikePcnt':'float', 'optionStrikeRound':'float', 'optionStrikePrice':'float', 'optionMultiplier':'float', 'positionCurrency':'symbol', 'underlying':'symbol', 'quoteCurrency':'symbol', 'underlyingSymbol':'symbol', 'reference':'symbol', 'referenceSymbol':'symbol', 'calcInterval':'timespan', 'publishInterval':'timespan', 'publishTime':'timespan', 'maxOrderQty':'long', 'maxPrice':'float', 'lotSize':'long', 'tickSize':'float', 'multiplier':'long', 'settlCurrency':'symbol', 'underlyingToPositionMultiplier':'long', 'underlyingToSettleMultiplier':'long', 'quoteToSettleMultiplier':'long', 'isQuanto':'boolean', 'isInverse':'boolean', 'initMargin':'float', 'maintMargin':'float', 'riskLimit':'long', 'riskStep':'long', 'limit':'float', 'capped':'boolean', 'taxed':'boolean', 'deleverage':'boolean', 'makerFee':'float', 'takerFee':'float', 'settlementFee':'float', 'insuranceFee':'float', 'fundingBaseSymbol':'symbol', 'fundingQuoteSymbol':'symbol', 'fundingPremiumSymbol':'symbol', 'fundingTimestamp':'timestamp', 'fundingInterval':'timespan', 'fundingRate':'float', 'indicativeFundingRate':'float', 'rebalanceTimestamp':'timestamp', 'rebalanceInterval':'timespan', 'openingTimestamp':'timestamp', 'closingTimestamp':'timestamp', 'sessionInterval':'timespan', 'prevClosePrice':'float', 'limitDownPrice':'float', 'limitUpPrice':'float', 'bankruptLimitDownPrice':'float', 'bankruptLimitUpPrice':'float', 'prevTotalVolume':'long', 'totalVolume':'long', 'volume':'long', 'volume24h':'long', 'prevTotalTurnover':'long', 'totalTurnover':'long', 'turnover':'long', 'turnover24h':'long', 'homeNotional24h':'float', 'foreignNotional24h':'float', 'prevPrice24h':'float', 'vwap':'float', 'highPrice':'float', 'lowPrice':'float', 'lastPrice':'float', 'lastPriceProtected':'float', 'lastTickDirection':'symbol', 'lastChangePcnt':'float', 'bidPrice':'float', 'midPrice':'float', 'askPrice':'float', 'impactBidPrice':'float', 'impactMidPrice':'float', 'impactAskPrice':'float', 'hasLiquidity':'boolean', 'openInterest':'long', 'openValue':'long', 'fairMethod':'symbol', 'fairBasisRate':'float', 'fairBasis':'float', 'fairPrice':'float', 'markMethod':'symbol', 'markPrice':'float', 'indicativeTaxRate':'float', 'indicativeSettlePrice':'float', 'optionUnderlyingPrice':'float', 'settledPrice':'float', 'timestamp':'timestamp' }, 'foreignKeys':{ 'inverseLeg':'instrument', 'sellLeg':'instrument', 'buyLeg':'instrument' }, 'attributes':{ 'symbol':'unique' }, 'filter':{ 'symbol':'XBTUSD' }, 'data':[ { 'symbol':'XBTUSD', 'rootSymbol':'XBT', 'state':'Open', 'typ':'FFWCSX', 'listing':'2016-05-13T12:00:00.000Z', 'front':'2016-05-13T12:00:00.000Z', 'expiry':None, 'settle':None, 'relistInterval':None, 'inverseLeg':'', 'sellLeg':'', 'buyLeg':'', 'optionStrikePcnt':None, 'optionStrikeRound':None, 'optionStrikePrice':None, 'optionMultiplier':None, 'positionCurrency':'USD', 'underlying':'XBT', 'quoteCurrency':'USD', 'underlyingSymbol':'XBT=', 'reference':'BMEX', 'referenceSymbol':'.BXBT', 'calcInterval':None, 'publishInterval':None, 'publishTime':None, 'maxOrderQty':10000000, 'maxPrice':1000000, 'lotSize':1, 'tickSize':Decimal( '0.5' ), 'multiplier':-100000000, 'settlCurrency':'XBt', 'underlyingToPositionMultiplier':None, 'underlyingToSettleMultiplier':-100000000, 'quoteToSettleMultiplier':None, 'isQuanto':False, 'isInverse':True, 'initMargin':Decimal( '0.01' ), 'maintMargin':Decimal( '0.005' ), 'riskLimit':20000000000, 'riskStep':10000000000, 'limit':None, 'capped':False, 'taxed':True, 'deleverage':True, 'makerFee':Decimal( '-0.00025' ), 'takerFee':Decimal( '0.00075' ), 'settlementFee':0, 'insuranceFee':0, 'fundingBaseSymbol':'.XBTBON8H', 'fundingQuoteSymbol':'.USDBON8H', 'fundingPremiumSymbol':'.XBTUSDPI8H', 'fundingTimestamp':'2020-02-02T04:00:00.000Z', 'fundingInterval':'2000-01-01T08:00:00.000Z', 'fundingRate':Decimal( '0.000106' ), 'indicativeFundingRate':Decimal( '0.0001' ), 'rebalanceTimestamp':None, 'rebalanceInterval':None, 'openingTimestamp':'2020-02-02T00:00:00.000Z', 'closingTimestamp':'2020-02-02T01:00:00.000Z', 'sessionInterval':'2000-01-01T01:00:00.000Z', 'prevClosePrice':Decimal( '9340.63' ), 'limitDownPrice':None, 'limitUpPrice':None, 'bankruptLimitDownPrice':None, 'bankruptLimitUpPrice':None, 'prevTotalVolume':1999389257669, 'totalVolume':1999420432348, 'volume':31174679, 'volume24h':1605909209, 'prevTotalTurnover':27967114248663460, 'totalTurnover':27967447182062520, 'turnover':332933399058, 'turnover24h':17126993087717, 'homeNotional24h':Decimal( '171269.9308771703' ), 'foreignNotional24h':1605909209, 'prevPrice24h':9348, 'vwap':Decimal( '9377.3443' ), 'highPrice':9464, 'lowPrice':Decimal( '9287.5' ), 'lastPrice':9352, 'lastPriceProtected':9352, 'lastTickDirection':'ZeroMinusTick', 'lastChangePcnt':Decimal( '0.0004' ), 'bidPrice':9352, 'midPrice':Decimal( '9352.25' ), 'askPrice':Decimal( '9352.5' ), 'impactBidPrice':Decimal( '9351.9125' ), 'impactMidPrice':Decimal( '9352.25' ), 'impactAskPrice':Decimal( '9352.7871' ), 'hasLiquidity':True, 'openInterest':983043322, 'openValue':10518563545400, 'fairMethod':'FundingRate', 'fairBasisRate':Decimal( '0.11607' ), 'fairBasis':Decimal( '0.43' ), 'fairPrice':Decimal( '9345.36' ), 'markMethod':'FairPrice', 'markPrice':Decimal( '9345.36' ), 'indicativeTaxRate':0, 'indicativeSettlePrice':Decimal( '9344.93' ), 'optionUnderlyingPrice':None, 'settledPrice':None, 'timestamp':'2020-02-02T00:30:43.772Z' } ] } """ for data in msg['data']: if 'openInterest' in data: ts = timestamp_normalize(self.id, data['timestamp']) await self.callback(OPEN_INTEREST, feed=self.id, pair=data['symbol'], open_interest=data['openInterest'], timestamp=ts)
async def _ticker(self, msg): await self.callback(TICKER, feed=self.id, pair=pair_exchange_to_std(msg['symbol']), bid=Decimal(msg['bid']), ask=Decimal(msg['ask']), timestamp=timestamp_normalize(self.id, msg['timestamp']))
async def _book(self, msg: dict, timestamp: float): """ { 'ch':'market.BTC_CW.depth.step0', 'ts':1565857755564, 'tick':{ 'mrid':14848858327, 'id':1565857755, 'bids':[ [ Decimal('9829.99'), 1], ... ] 'asks':[ [ 9830, 625], ... ] }, 'ts':1565857755552, 'version':1565857755, 'ch':'market.BTC_CW.depth.step0' } """ pair = pair_std_to_exchange(msg['ch'].split('.')[1], self.id) data = msg['tick'] forced = pair not in self.l2_book if 'bids' in data: 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 ticker(self, msg: dict, timestamp: float): for t in msg['D']: if (not self.config and t['M'] in self.pairs) or ('SubscribeToSummaryDeltas' in self.config and t['M'] in self.config['SubscribeToSummaryDeltas']): await self.callback(TICKER, feed=self.id, pair=pair_exchange_to_std(t['M']), bid=Decimal(t['B']), ask=Decimal(t['A']), timestamp=timestamp_normalize(self.id, t['T']), receipt_timestamp=timestamp)
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 ticker(self, msg: dict, timestamp: float): for t in msg['D']: if 'SubscribeToSummaryDeltas' in self.subscription and t['M'] in self.subscription['SubscribeToSummaryDeltas']: await self.callback(TICKER, feed=self.id, symbol=self.exchange_symbol_to_std_symbol(t['M']), bid=Decimal(t['B']), ask=Decimal(t['A']), timestamp=timestamp_normalize(self.id, t['T']), receipt_timestamp=timestamp)
async def _market_info(self, session, pair): """ Data from /coins/{id}. """ quote_c, base_c = pair.split('_') async with session.get( f"{self.address}coins/{quote_c}?localization=false&tickers=false&market_data=true&community_data=true&developer_data=false&sparkline=false" ) as response: data = await response.read() try: data = json.loads(data) except JSONDecodeError as jde: raise Exception( 'Rate limit possibly exceeded\nReturned error: {!s}\nReturned response content from HTTP request: {!s}' .format(jde, data)) timestamp = timestamp_normalize(self.id, data['last_updated']) if (pair not in self.last_market_info_update) or ( self.last_market_info_update[pair] < timestamp): self.last_market_info_update[pair] = timestamp # `None` and null data is systematically replaced with '-1' for digits and '' for string (empty string), for compatibility with Redis stream. market_data = { k: (-1 if (not v or (isinstance(v, dict) and not (base_c in v and v[base_c])) ) else v if k in OTHER_MARKET_DATA_FILTER else v[base_c]) for k, v in data['market_data'].items() if k in ALL_MARKET_DATA } # 'last_updated' here is assumed to be specific for market data, so it is kept as well. market_data['last_updated'] = timestamp_normalize( self.id, data['market_data']['last_updated']) community_data = { k: (v if v else -1) for k, v in data['community_data'].items() } public_interest_stats = { k: (v if v else -1) for k, v in data['public_interest_stats'].items() } # Only retain selected data, and remove as well `market_data`, `community_data` and `public_interest_stats`. # These latter are added back in `data` to have it in the shape of a flatten dict. data_s = { k: (v if v else '') for k, v in data.items() if k in MARKET_INFO_FILTER_S } data_d = { k: (v if v else -1) for k, v in data.items() if k in MARKET_INFO_FILTER_D } status = str(data['status_updates']) data = { **data_s, **data_d, **market_data, **community_data, **public_interest_stats } # `list` data type is converted to string for compatibility with Redis stream. data['status_updates'] = status await self.callback(MARKET_INFO, feed=self.id, pair=pair_exchange_to_std(pair), timestamp=timestamp, **data) return
async def _book(self, msg: dict, timestamp: float): """ Doc : https://docs.upbit.com/v1.0.7/reference#시세-호가-정보orderbook-조회 Currently, Upbit orderbook api only provides 15 depth book state and does not support delta { 'ty': 'orderbook' // Event type 'cd': 'KRW-BTC', // Symbol 'obu': [{'ap': 6727000.0, 'as': 0.4744314, 'bp': 6721000.0, 'bs': 0.0014551}, // orderbook units {'ap': 6728000.0, 'as': 1.85862302, 'bp': 6719000.0, 'bs': 0.00926683}, {'ap': 6729000.0, 'as': 5.43556558, 'bp': 6718000.0, 'bs': 0.40908977}, {'ap': 6730000.0, 'as': 4.41993651, 'bp': 6717000.0, 'bs': 0.48052204}, {'ap': 6731000.0, 'as': 0.09207, 'bp': 6716000.0, 'bs': 6.52612927}, {'ap': 6732000.0, 'as': 1.42736812, 'bp': 6715000.0, 'bs': 610.45535023}, {'ap': 6734000.0, 'as': 0.173, 'bp': 6714000.0, 'bs': 1.09218395}, {'ap': 6735000.0, 'as': 1.08739294, 'bp': 6713000.0, 'bs': 0.46785444}, {'ap': 6737000.0, 'as': 3.34450006, 'bp': 6712000.0, 'bs': 0.01300915}, {'ap': 6738000.0, 'as': 0.26, 'bp': 6711000.0, 'bs': 0.24701799}, {'ap': 6739000.0, 'as': 0.086, 'bp': 6710000.0, 'bs': 1.97964014}, {'ap': 6740000.0, 'as': 0.00658782, 'bp': 6708000.0, 'bs': 0.0002}, {'ap': 6741000.0, 'as': 0.8004, 'bp': 6707000.0, 'bs': 0.02022364}, {'ap': 6742000.0, 'as': 0.11040396, 'bp': 6706000.0, 'bs': 0.29082183}, {'ap': 6743000.0, 'as': 1.1, 'bp': 6705000.0, 'bs': 0.94493254}], 'st': 'REALTIME', // Streaming type - 'REALTIME' or 'SNAPSHOT' 'tas': 20.67627941, // Total ask size for given 15 depth (not total ask order size) 'tbs': 622.93769692, // Total bid size for given 15 depth (not total bid order size) 'tms': 1584263923870, // Timestamp } """ pair = symbol_exchange_to_std(msg['cd']) orderbook_timestamp = timestamp_normalize(self.id, msg['tms']) forced = pair not in self.l2_book update = { BID: sd({ Decimal(unit['bp']): Decimal(unit['bs']) for unit in msg['obu'] if unit['bp'] > 0 }), ASK: sd({ Decimal(unit['ap']): Decimal(unit['as']) for unit in msg['obu'] if unit['ap'] > 0 }) } 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, orderbook_timestamp, timestamp) book = self.l2_book[pair] ask_price_0, ask_qty_0 = book[ASK].peekitem(index=0) bid_price_0, bid_qty_0 = book[BID].peekitem(index=-1) await self.callback(TICKER, feed=self.id, symbol=pair, bid=bid_price_0, ask=ask_price_0, bid_amount=bid_qty_0, ask_amount=ask_qty_0, timestamp=orderbook_timestamp, receipt_timestamp=timestamp)
async def _instrument_info(self, msg: dict, timestamp: float): """ ### Snapshot type update { "topic": "instrument_info.100ms.BTCUSD", "type": "snapshot", "data": { "id": 1, "symbol": "BTCUSD", //instrument name "last_price_e4": 81165000, //the latest price "last_tick_direction": "ZeroPlusTick", //the direction of last tick:PlusTick,ZeroPlusTick,MinusTick, //ZeroMinusTick "prev_price_24h_e4": 81585000, //the price of prev 24h "price_24h_pcnt_e6": -5148, //the current last price percentage change from prev 24h price "high_price_24h_e4": 82900000, //the highest price of prev 24h "low_price_24h_e4": 79655000, //the lowest price of prev 24h "prev_price_1h_e4": 81395000, //the price of prev 1h "price_1h_pcnt_e6": -2825, //the current last price percentage change from prev 1h price "mark_price_e4": 81178500, //mark price "index_price_e4": 81172800, //index price "open_interest": 154418471, //open interest quantity - Attention, the update is not //immediate - slowest update is 1 minute "open_value_e8": 1997561103030, //open value quantity - Attention, the update is not //immediate - the slowest update is 1 minute "total_turnover_e8": 2029370141961401, //total turnover "turnover_24h_e8": 9072939873591, //24h turnover "total_volume": 175654418740, //total volume "volume_24h": 735865248, //24h volume "funding_rate_e6": 100, //funding rate "predicted_funding_rate_e6": 100, //predicted funding rate "cross_seq": 1053192577, //sequence "created_at": "2018-11-14T16:33:26Z", "updated_at": "2020-01-12T18:25:16Z", "next_funding_time": "2020-01-13T00:00:00Z", //next funding time //the rest time to settle funding fee "countdown_hour": 6 //the remaining time to settle the funding fee }, "cross_seq": 1053192634, "timestamp_e6": 1578853524091081 //the timestamp when this information was produced } ### Delta type update { "topic": "instrument_info.100ms.BTCUSD", "type": "delta", "data": { "delete": [], "update": [ { "id": 1, "symbol": "BTCUSD", "prev_price_24h_e4": 81565000, "price_24h_pcnt_e6": -4904, "open_value_e8": 2000479681106, "total_turnover_e8": 2029370495672976, "turnover_24h_e8": 9066215468687, "volume_24h": 735316391, "cross_seq": 1053192657, "created_at": "2018-11-14T16:33:26Z", "updated_at": "2020-01-12T18:25:25Z" } ], "insert": [] }, "cross_seq": 1053192657, "timestamp_e6": 1578853525691123 } """ update_type = msg['type'] if update_type == 'snapshot': updates = [msg['data']] else: updates = msg['data']['update'] for info in updates: if 'updated_at_e9' in info: ts = info['updated_at_e9'] / 1e9 elif 'updated_at' in info: ts = timestamp_normalize(self.id, info['updated_at']) else: continue if 'open_interest' in info: await self.callback( OPEN_INTEREST, feed=self.id, symbol=self.exchange_symbol_to_std_symbol(info['symbol']), open_interest=Decimal(info['open_interest']), timestamp=ts, receipt_timestamp=timestamp) if 'index_price_e4' in info: await self.callback( FUTURES_INDEX, feed=self.id, symbol=self.exchange_symbol_to_std_symbol(info['symbol']), futures_index=Decimal(info['index_price_e4']) * Decimal(1e-4), timestamp=ts, receipt_timestamp=timestamp)
async def _book(self, msg): if msg['action'] == 'partial': # snapshot for update in msg['data']: pair = pair_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'] }) } await self.book_callback(self.l2_book[pair], L2_BOOK, pair, True, None, timestamp_normalize(self.id, update['timestamp'])) else: # update for update in msg['data']: delta = {BID: [], ASK: []} pair = pair_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: 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 await self.book_callback(self.l2_book[pair], L2_BOOK, pair, False, delta, timestamp_normalize(self.id, update['timestamp']))