# -*- coding: utf-8 -*- # PLEASE DO NOT EDIT THIS FILE, IT IS GENERATED AND WILL BE OVERWRITTEN: # https://github.com/ccxt/ccxt/blob/master/CONTRIBUTING.md#how-to-contribute-code import ccxt.async_support from ccxt.async_support.base.ws.cache import ArrayCache, ArrayCacheBySymbolById import hashlib from ccxt.base.types import Any, Bool, Int, Order, OrderBook, Str, Strings, Ticker, Tickers, Trade from ccxt.async_support.base.ws.client import Client from typing import List from ccxt.base.errors import ExchangeError from ccxt.base.errors import AuthenticationError from ccxt.base.errors import ArgumentsRequired from ccxt.base.errors import BadRequest from ccxt.base.errors import BadSymbol class coinbaseexchange(ccxt.async_support.coinbaseexchange): def describe(self) -> Any: return self.deep_extend(super(coinbaseexchange, self).describe(), { 'has': { 'ws': True, 'watchOHLCV': False, # missing on the exchange side 'watchOrderBook': True, 'watchOrderBookForSymbols': True, 'watchTicker': True, 'watchTickers': True, 'watchTrades': True, 'watchTradesForSymbols': True, 'watchMyTradesForSymbols': True, 'watchBalance': False, 'watchStatus': False, # for now 'watchOrders': True, 'watchOrdersForSymbols': True, 'watchMyTrades': True, }, 'urls': { 'api': { 'ws': 'wss://ws-feed.exchange.coinbase.com', }, 'test': { 'ws': 'wss://ws-feed-public.sandbox.exchange.coinbase.com', }, }, 'options': { 'tradesLimit': 1000, 'ordersLimit': 1000, 'myTradesLimit': 1000, }, }) def authenticate(self): self.check_required_credentials() path = '/users/self/verify' nonce = self.nonce() payload = str(nonce) + 'GET' + path signature = self.hmac(self.encode(payload), self.base64_to_binary(self.secret), hashlib.sha256, 'base64') return { 'timestamp': nonce, 'key': self.apiKey, 'signature': signature, 'passphrase': self.password, } async def subscribe(self, name, symbol=None, messageHashStart=None, params={}): await self.load_markets() market = None messageHash = messageHashStart productIds = [] if symbol is not None: market = self.market(symbol) messageHash += ':' + market['id'] productIds.append(market['id']) url = self.urls['api']['ws'] if 'signature' in params: # need to distinguish between public trades and user trades url = url + '?' subscribe: dict = { 'type': 'subscribe', 'product_ids': productIds, 'channels': [ name, ], } request = self.extend(subscribe, params) return await self.watch(url, messageHash, request, messageHash) async def subscribe_multiple(self, name, symbols=[], messageHashStart=None, params={}): await self.load_markets() market = None symbols = self.market_symbols(symbols) messageHashes = [] productIds = [] for i in range(0, len(symbols)): symbol = symbols[i] market = self.market(symbol) productIds.append(market['id']) messageHashes.append(messageHashStart + ':' + market['symbol']) url = self.urls['api']['ws'] if 'signature' in params: # need to distinguish between public trades and user trades url = url + '?' subscribe: dict = { 'type': 'subscribe', 'product_ids': productIds, 'channels': [ name, ], } request = self.extend(subscribe, params) return await self.watch_multiple(url, messageHashes, request, messageHashes) async def watch_ticker(self, symbol: str, params={}) -> Ticker: """ watches a price ticker, a statistical calculation with the information calculated over the past 24 hours for a specific market :param str symbol: unified symbol of the market to fetch the ticker for :param dict [params]: extra parameters specific to the exchange API endpoint :returns dict: a `ticker structure ` """ name = 'ticker' return await self.subscribe(name, symbol, name, params) async def watch_tickers(self, symbols: Strings = None, params={}) -> Tickers: """ watches a price ticker, a statistical calculation with the information calculated over the past 24 hours for all markets of a specific list :param str[] [symbols]: unified symbol of the market to fetch the ticker for :param dict [params]: extra parameters specific to the exchange API endpoint :param str [params.channel]: the channel to subscribe to, tickers by default. Can be tickers, sprd-tickers, index-tickers, block-tickers :returns dict: a `ticker structure ` """ await self.load_markets() symbolsLength = len(symbols) if symbolsLength == 0: raise BadSymbol(self.id + ' watchTickers requires a non-empty symbols array') channel = 'ticker' messageHash = 'ticker' ticker = await self.subscribe_multiple(channel, symbols, messageHash, params) if self.newUpdates: result: dict = {} result[ticker['symbol']] = ticker return result return self.filter_by_array(self.tickers, 'symbol', symbols) async def watch_trades(self, symbol: str, since: Int = None, limit: Int = None, params={}) -> List[Trade]: """ get the list of most recent trades for a particular symbol :param str symbol: unified symbol of the market to fetch trades for :param int [since]: timestamp in ms of the earliest trade to fetch :param int [limit]: the maximum amount of trades to fetch :param dict [params]: extra parameters specific to the exchange API endpoint :returns dict[]: a list of `trade structures ` """ await self.load_markets() symbol = self.symbol(symbol) name = 'matches' trades = await self.subscribe(name, symbol, name, params) if self.newUpdates: limit = trades.getLimit(symbol, limit) return self.filter_by_since_limit(trades, since, limit, 'timestamp', True) async def watch_trades_for_symbols(self, symbols: List[str], since: Int = None, limit: Int = None, params={}) -> List[Trade]: """ get the list of most recent trades for a particular symbol :param str[] symbols: unified symbol of the market to fetch trades for :param int [since]: timestamp in ms of the earliest trade to fetch :param int [limit]: the maximum amount of trades to fetch :param dict [params]: extra parameters specific to the exchange API endpoint :returns dict[]: a list of `trade structures ` """ symbolsLength = len(symbols) if symbolsLength == 0: raise BadRequest(self.id + ' watchTradesForSymbols() requires a non-empty array of symbols') await self.load_markets() symbols = self.market_symbols(symbols) name = 'matches' trades = await self.subscribe_multiple(name, symbols, name, params) if self.newUpdates: first = self.safe_value(trades, 0) tradeSymbol = self.safe_string(first, 'symbol') limit = trades.getLimit(tradeSymbol, limit) return self.filter_by_since_limit(trades, since, limit, 'timestamp', True) async def watch_my_trades(self, symbol: Str = None, since: Int = None, limit: Int = None, params={}) -> List[Trade]: """ watches information on multiple trades made by the user :param str symbol: unified market symbol of the market trades were made in :param int [since]: the earliest time in ms to fetch trades for :param int [limit]: the maximum number of trade structures to retrieve :param dict [params]: extra parameters specific to the exchange API endpoint :returns dict[]: a list of `trade structures ` """ if symbol is None: raise ArgumentsRequired(self.id + ' watchMyTrades() requires a symbol argument') await self.load_markets() symbol = self.symbol(symbol) name = 'user' messageHash = 'myTrades' authentication = self.authenticate() trades = await self.subscribe(name, symbol, messageHash, self.extend(params, authentication)) if self.newUpdates: limit = trades.getLimit(symbol, limit) return self.filter_by_since_limit(trades, since, limit, 'timestamp', True) async def watch_my_trades_for_symbols(self, symbols: List[str], since: Int = None, limit: Int = None, params={}) -> List[Trade]: """ watches information on multiple trades made by the user :param str[] symbols: unified symbol of the market to fetch trades for :param int [since]: the earliest time in ms to fetch trades for :param int [limit]: the maximum number of trade structures to retrieve :param dict [params]: extra parameters specific to the exchange API endpoint :returns dict[]: a list of `trade structures ` """ symbols = self.market_symbols(symbols, None, False) await self.load_markets() name = 'user' messageHash = 'myTrades' authentication = self.authenticate() trades = await self.subscribe_multiple(name, symbols, messageHash, self.extend(params, authentication)) if self.newUpdates: first = self.safe_value(trades, 0) tradeSymbol = self.safe_string(first, 'symbol') limit = trades.getLimit(tradeSymbol, limit) return self.filter_by_since_limit(trades, since, limit, 'timestamp', True) async def watch_orders_for_symbols(self, symbols: List[str], since: Int = None, limit: Int = None, params={}) -> List[Order]: """ watches information on multiple orders made by the user :param str[] symbols: unified symbol of the market to fetch orders for :param int [since]: the earliest time in ms to fetch orders for :param int [limit]: the maximum number of trade structures to retrieve :param dict [params]: extra parameters specific to the exchange API endpoint :returns dict[]: a list of `order structures ` """ await self.load_markets() symbols = self.market_symbols(symbols, None, False) name = 'user' messageHash = 'orders' authentication = self.authenticate() orders = await self.subscribe_multiple(name, symbols, messageHash, self.extend(params, authentication)) if self.newUpdates: first = self.safe_value(orders, 0) tradeSymbol = self.safe_string(first, 'symbol') limit = orders.getLimit(tradeSymbol, limit) return self.filter_by_since_limit(orders, since, limit, 'timestamp', True) async def watch_orders(self, symbol: Str = None, since: Int = None, limit: Int = None, params={}) -> List[Order]: """ watches information on multiple orders made by the user :param str symbol: unified market symbol of the market orders were made in :param int [since]: the earliest time in ms to fetch orders for :param int [limit]: the maximum number of order structures to retrieve :param dict [params]: extra parameters specific to the exchange API endpoint :returns dict[]: a list of `order structures ` """ if symbol is None: raise BadSymbol(self.id + ' watchMyTrades requires a symbol') await self.load_markets() symbol = self.symbol(symbol) name = 'user' messageHash = 'orders' authentication = self.authenticate() orders = await self.subscribe(name, symbol, messageHash, self.extend(params, authentication)) if self.newUpdates: limit = orders.getLimit(symbol, limit) return self.filter_by_since_limit(orders, since, limit, 'timestamp', True) async def watch_order_book_for_symbols(self, symbols: List[str], limit: Int = None, params={}) -> OrderBook: """ watches information on open orders with bid(buy) and ask(sell) prices, volumes and other data :param str[] symbols: unified array of symbols :param int [limit]: the maximum amount of order book entries to return :param dict [params]: extra parameters specific to the exchange API endpoint :returns dict: A dictionary of `order book structures ` indexed by market symbols """ symbolsLength = len(symbols) if symbolsLength == 0: raise BadRequest(self.id + ' watchOrderBookForSymbols() requires a non-empty array of symbols') name = 'level2' await self.load_markets() symbols = self.market_symbols(symbols) marketIds = self.market_ids(symbols) messageHashes = [] for i in range(0, symbolsLength): marketId = marketIds[i] messageHashes.append(name + ':' + marketId) url = self.urls['api']['ws'] subscribe: dict = { 'type': 'subscribe', 'product_ids': marketIds, 'channels': [ name, ], } request = self.extend(subscribe, params) subscription: dict = { 'messageHash': name, 'symbols': symbols, 'marketIds': marketIds, 'limit': limit, } authentication = self.authenticate() orderbook = await self.watch_multiple(url, messageHashes, self.extend(request, authentication), messageHashes, subscription) return orderbook.limit() async def watch_order_book(self, symbol: str, limit: Int = None, params={}) -> OrderBook: """ watches information on open orders with bid(buy) and ask(sell) prices, volumes and other data :param str symbol: unified symbol of the market to fetch the order book for :param int [limit]: the maximum amount of order book entries to return :param dict [params]: extra parameters specific to the exchange API endpoint :returns dict: A dictionary of `order book structures ` indexed by market symbols """ name = 'level2' await self.load_markets() market = self.market(symbol) symbol = market['symbol'] messageHash = name + ':' + market['id'] url = self.urls['api']['ws'] subscribe: dict = { 'type': 'subscribe', 'product_ids': [ market['id'], ], 'channels': [ name, ], } request = self.extend(subscribe, params) subscription: dict = { 'messageHash': messageHash, 'symbol': symbol, 'marketId': market['id'], 'limit': limit, } authentication = self.authenticate() orderbook = await self.watch(url, messageHash, self.extend(request, authentication), messageHash, subscription) return orderbook.limit() def handle_trade(self, client: Client, message): # # { # "type": "match", # "trade_id": 82047307, # "maker_order_id": "0f358725-2134-435e-be11-753912a326e0", # "taker_order_id": "252b7002-87a3-425c-ac73-f5b9e23f3caf", # "side": "sell", # "size": "0.00513192", # "price": "9314.78", # "product_id": "BTC-USD", # "sequence": 12038915443, # "time": "2020-01-31T20:03:41.158814Z" # } # marketId = self.safe_string(message, 'product_id') if marketId is not None: trade = self.parse_ws_trade(message) symbol = trade['symbol'] # the exchange sends type = 'match' # but requires 'matches' upon subscribing # therefore we resolve 'matches' here instead of 'match' type = 'matches' messageHash = type + ':' + marketId tradesArray = self.safe_value(self.trades, symbol) if tradesArray is None: tradesLimit = self.safe_integer(self.options, 'tradesLimit', 1000) tradesArray = ArrayCache(tradesLimit) self.trades[symbol] = tradesArray tradesArray.append(trade) client.resolve(tradesArray, messageHash) return message def handle_my_trade(self, client: Client, message): marketId = self.safe_string(message, 'product_id') if marketId is not None: trade = self.parse_ws_trade(message) type = 'myTrades' messageHash = type + ':' + marketId tradesArray = self.myTrades if tradesArray is None: limit = self.safe_integer(self.options, 'myTradesLimit', 1000) tradesArray = ArrayCacheBySymbolById(limit) self.myTrades = tradesArray tradesArray.append(trade) client.resolve(tradesArray, messageHash) return message def parse_ws_trade(self, trade, market=None): # # private trades # { # "type": "match", # "trade_id": 10, # "sequence": 50, # "maker_order_id": "ac928c66-ca53-498f-9c13-a110027a60e8", # "taker_order_id": "132fb6ae-456b-4654-b4e0-d681ac05cea1", # "time": "2014-11-07T08:19:27.028459Z", # "product_id": "BTC-USD", # "size": "5.23512", # "price": "400.23", # "side": "sell", # "taker_user_id: "5844eceecf7e803e259d0365", # "user_id": "5844eceecf7e803e259d0365", # "taker_profile_id": "765d1549-9660-4be2-97d4-fa2d65fa3352", # "profile_id": "765d1549-9660-4be2-97d4-fa2d65fa3352", # "taker_fee_rate": "0.005" # } # # { # "type": "match", # "trade_id": 10, # "sequence": 50, # "maker_order_id": "ac928c66-ca53-498f-9c13-a110027a60e8", # "taker_order_id": "132fb6ae-456b-4654-b4e0-d681ac05cea1", # "time": "2014-11-07T08:19:27.028459Z", # "product_id": "BTC-USD", # "size": "5.23512", # "price": "400.23", # "side": "sell", # "maker_user_id: "5844eceecf7e803e259d0365", # "maker_id": "5844eceecf7e803e259d0365", # "maker_profile_id": "765d1549-9660-4be2-97d4-fa2d65fa3352", # "profile_id": "765d1549-9660-4be2-97d4-fa2d65fa3352", # "maker_fee_rate": "0.001" # } # # public trades # { # "type": "received", # "time": "2014-11-07T08:19:27.028459Z", # "product_id": "BTC-USD", # "sequence": 10, # "order_id": "d50ec984-77a8-460a-b958-66f114b0de9b", # "size": "1.34", # "price": "502.1", # "side": "buy", # "order_type": "limit" # } parsed = super(coinbaseexchange, self).parse_trade(trade) feeRate = None isMaker = False if 'maker_fee_rate' in trade: isMaker = True parsed['takerOrMaker'] = 'maker' feeRate = self.safe_number(trade, 'maker_fee_rate') else: parsed['takerOrMaker'] = 'taker' feeRate = self.safe_number(trade, 'taker_fee_rate') # side always represents the maker side of the trade # so if we're taker, we invert it currentSide = parsed['side'] parsed['side'] = self.safe_string({ 'buy': 'sell', 'sell': 'buy', }, currentSide, currentSide) idKey = 'maker_order_id' if isMaker else 'taker_order_id' parsed['order'] = self.safe_string(trade, idKey) market = self.market(parsed['symbol']) feeCurrency = market['quote'] feeCost = None if (parsed['cost'] is not None) and (feeRate is not None): cost = self.safe_number(parsed, 'cost') feeCost = cost * feeRate parsed['fee'] = { 'rate': feeRate, 'cost': feeCost, 'currency': feeCurrency, } return parsed def parse_ws_order_status(self, status): statuses: dict = { 'filled': 'closed', 'canceled': 'canceled', } return self.safe_string(statuses, status, 'open') def handle_order(self, client: Client, message): # # Order is created # # { # "type": "received", # "side": "sell", # "product_id": "BTC-USDC", # "time": "2021-03-05T16:42:21.878177Z", # "sequence": 5641953814, # "profile_id": "774ee0ce-fdda-405f-aa8d-47189a14ba0a", # "user_id": "54fc141576dcf32596000133", # "order_id": "11838707-bf9c-4d65-8cec-b57c9a7cab42", # "order_type": "limit", # "size": "0.0001", # "price": "50000", # "client_oid": "a317abb9-2b30-4370-ebfe-0deecb300180" # } # # { # "type": "received", # "time": "2014-11-09T08:19:27.028459Z", # "product_id": "BTC-USD", # "sequence": 12, # "order_id": "dddec984-77a8-460a-b958-66f114b0de9b", # "funds": "3000.234", # "side": "buy", # "order_type": "market" # } # # Order is on the order book # # { # "type": "open", # "side": "sell", # "product_id": "BTC-USDC", # "time": "2021-03-05T16:42:21.878177Z", # "sequence": 5641953815, # "profile_id": "774ee0ce-fdda-405f-aa8d-47189a14ba0a", # "user_id": "54fc141576dcf32596000133", # "price": "50000", # "order_id": "11838707-bf9c-4d65-8cec-b57c9a7cab42", # "remaining_size": "0.0001" # } # # Order is partially or completely filled # # { # "type": "match", # "side": "sell", # "product_id": "BTC-USDC", # "time": "2021-03-05T16:37:13.396107Z", # "sequence": 5641897876, # "profile_id": "774ee0ce-fdda-405f-aa8d-47189a14ba0a", # "user_id": "54fc141576dcf32596000133", # "trade_id": 5455505, # "maker_order_id": "e5f5754d-70a3-4346-95a6-209bcb503629", # "taker_order_id": "88bf7086-7b15-40ff-8b19-ab4e08516d69", # "size": "0.00021019", # "price": "47338.46", # "taker_profile_id": "774ee0ce-fdda-405f-aa8d-47189a14ba0a", # "taker_user_id": "54fc141576dcf32596000133", # "taker_fee_rate": "0.005" # } # # Order is canceled / closed # # { # "type": "done", # "side": "buy", # "product_id": "BTC-USDC", # "time": "2021-03-05T16:37:13.396107Z", # "sequence": 5641897877, # "profile_id": "774ee0ce-fdda-405f-aa8d-47189a14ba0a", # "user_id": "54fc141576dcf32596000133", # "order_id": "88bf7086-7b15-40ff-8b19-ab4e08516d69", # "reason": "filled" # } # currentOrders = self.orders if currentOrders is None: limit = self.safe_integer(self.options, 'ordersLimit', 1000) currentOrders = ArrayCacheBySymbolById(limit) self.orders = currentOrders type = self.safe_string(message, 'type') marketId = self.safe_string(message, 'product_id') if marketId is not None: messageHash = 'orders:' + marketId symbol = self.safe_symbol(marketId) orderId = self.safe_string(message, 'order_id') makerOrderId = self.safe_string(message, 'maker_order_id') takerOrderId = self.safe_string(message, 'taker_order_id') orders = self.orders previousOrders = self.safe_value(orders.hashmap, symbol, {}) previousOrder = self.safe_value(previousOrders, orderId) if previousOrder is None: previousOrder = self.safe_value_2(previousOrders, makerOrderId, takerOrderId) if previousOrder is None: parsed = self.parse_ws_order(message) orders.append(parsed) client.resolve(orders, messageHash) else: sequence = self.safe_integer(message, 'sequence') previousInfo = self.safe_value(previousOrder, 'info', {}) previousSequence = self.safe_integer(previousInfo, 'sequence') if (previousSequence is None) or (sequence > previousSequence): if type == 'match': trade = self.parse_ws_trade(message) if previousOrder['trades'] is None: previousOrder['trades'] = [] previousOrder['trades'].append(trade) previousOrder['lastTradeTimestamp'] = trade['timestamp'] totalCost = 0 totalAmount = 0 trades = previousOrder['trades'] for i in range(0, len(trades)): tradeEntry = trades[i] totalCost = self.sum(totalCost, tradeEntry['cost']) totalAmount = self.sum(totalAmount, tradeEntry['amount']) if totalAmount > 0: previousOrder['average'] = totalCost / totalAmount previousOrder['cost'] = totalCost if previousOrder['filled'] is not None: previousOrder['filled'] += trade['amount'] if previousOrder['amount'] is not None: previousOrder['remaining'] = previousOrder['amount'] - previousOrder['filled'] if previousOrder['fee'] is None: previousOrder['fee'] = { 'cost': 0, 'currency': trade['fee']['currency'], } if (previousOrder['fee']['cost'] is not None) and (trade['fee']['cost'] is not None): previousOrder['fee']['cost'] = self.sum(previousOrder['fee']['cost'], trade['fee']['cost']) # update the newUpdates count orders.append(previousOrder) client.resolve(orders, messageHash) elif (type == 'received') or (type == 'done'): info = self.extend(previousOrder['info'], message) order = self.parse_ws_order(info) keys = list(order.keys()) # update the reference for i in range(0, len(keys)): key = keys[i] if order[key] is not None: previousOrder[key] = order[key] # update the newUpdates count orders.append(previousOrder) client.resolve(orders, messageHash) def parse_ws_order(self, order, market=None): id = self.safe_string(order, 'order_id') clientOrderId = self.safe_string(order, 'client_oid') marketId = self.safe_string(order, 'product_id') symbol = self.safe_symbol(marketId) side = self.safe_string(order, 'side') price = self.safe_number(order, 'price') amount = self.safe_number_2(order, 'size', 'funds') time = self.safe_string(order, 'time') timestamp = self.parse8601(time) reason = self.safe_string(order, 'reason') status = self.parse_ws_order_status(reason) orderType = self.safe_string(order, 'order_type') remaining = self.safe_number(order, 'remaining_size') type = self.safe_string(order, 'type') filled = None if (amount is not None) and (remaining is not None): filled = amount - remaining elif type == 'received': filled = 0 if amount is not None: remaining = amount - filled return self.safe_order({ 'info': order, 'symbol': symbol, 'id': id, 'clientOrderId': clientOrderId, 'timestamp': timestamp, 'datetime': self.iso8601(timestamp), 'lastTradeTimestamp': None, 'type': orderType, 'timeInForce': None, 'postOnly': None, 'side': side, 'price': price, 'stopPrice': None, 'triggerPrice': None, 'amount': amount, 'cost': None, 'average': None, 'filled': filled, 'remaining': remaining, 'status': status, 'fee': None, 'trades': None, }) def handle_ticker(self, client: Client, message): # # { # "type": "ticker", # "sequence": 12042642428, # "product_id": "BTC-USD", # "price": "9380.55", # "open_24h": "9450.81000000", # "volume_24h": "9611.79166047", # "low_24h": "9195.49000000", # "high_24h": "9475.19000000", # "volume_30d": "327812.00311873", # "best_bid": "9380.54", # "best_ask": "9380.55", # "side": "buy", # "time": "2020-02-01T01:40:16.253563Z", # "trade_id": 82062566, # "last_size": "0.41969131" # } # marketId = self.safe_string(message, 'product_id') if marketId is not None: ticker = self.parse_ticker(message) symbol = ticker['symbol'] self.tickers[symbol] = ticker messageHash = 'ticker:' + symbol idMessageHash = 'ticker:' + marketId client.resolve(ticker, messageHash) client.resolve(ticker, idMessageHash) return message def parse_ticker(self, ticker, market=None) -> Ticker: # # { # "type": "ticker", # "sequence": 7388547310, # "product_id": "BTC-USDT", # "price": "22345.67", # "open_24h": "22308.13", # "volume_24h": "470.21123644", # "low_24h": "22150", # "high_24h": "22495.15", # "volume_30d": "25713.98401605", # "best_bid": "22345.67", # "best_bid_size": "0.10647825", # "best_ask": "22349.68", # "best_ask_size": "0.03131702", # "side": "sell", # "time": "2023-03-04T03:37:20.799258Z", # "trade_id": 11586478, # "last_size": "0.00352175" # } # type = self.safe_string(ticker, 'type') if type is None: return super(coinbaseexchange, self).parse_ticker(ticker, market) marketId = self.safe_string(ticker, 'product_id') symbol = self.safe_symbol(marketId, market, '-') timestamp = self.parse8601(self.safe_string(ticker, 'time')) last = self.safe_string(ticker, 'price') return self.safe_ticker({ 'symbol': symbol, 'timestamp': timestamp, 'datetime': self.iso8601(timestamp), 'high': self.safe_string(ticker, 'high_24h'), 'low': self.safe_string(ticker, 'low_24h'), 'bid': self.safe_string(ticker, 'best_bid'), 'bidVolume': self.safe_string(ticker, 'best_bid_size'), 'ask': self.safe_string(ticker, 'best_ask'), 'askVolume': self.safe_string(ticker, 'best_ask_size'), 'vwap': None, 'open': self.safe_string(ticker, 'open_24h'), 'close': last, 'last': last, 'previousClose': None, 'change': None, 'percentage': None, 'average': None, 'baseVolume': self.safe_string(ticker, 'volume_24h'), 'quoteVolume': None, 'info': ticker, }) def handle_delta(self, bookside, delta): price = self.safe_number(delta, 0) amount = self.safe_number(delta, 1) bookside.store(price, amount) def handle_deltas(self, bookside, deltas): for i in range(0, len(deltas)): self.handle_delta(bookside, deltas[i]) def handle_order_book(self, client: Client, message): # # first message(snapshot) # # { # "type": "snapshot", # "product_id": "BTC-USD", # "bids": [ # ["10101.10", "0.45054140"] # ], # "asks": [ # ["10102.55", "0.57753524"] # ] # } # # subsequent updates # # { # "type": "l2update", # "product_id": "BTC-USD", # "time": "2019-08-14T20:42:27.265Z", # "changes": [ # ["buy", "10101.80000000", "0.162567"] # ] # } # type = self.safe_string(message, 'type') marketId = self.safe_string(message, 'product_id') market = self.safe_market(marketId, None, '-') symbol = market['symbol'] name = 'level2' messageHash = name + ':' + marketId subscription = self.safe_value(client.subscriptions, messageHash, {}) limit = self.safe_integer(subscription, 'limit') if type == 'snapshot': self.orderbooks[symbol] = self.order_book({}, limit) orderbook = self.orderbooks[symbol] self.handle_deltas(orderbook['asks'], self.safe_value(message, 'asks', [])) self.handle_deltas(orderbook['bids'], self.safe_value(message, 'bids', [])) orderbook['timestamp'] = None orderbook['datetime'] = None orderbook['symbol'] = symbol client.resolve(orderbook, messageHash) elif type == 'l2update': orderbook = self.orderbooks[symbol] timestamp = self.parse8601(self.safe_string(message, 'time')) changes = self.safe_value(message, 'changes', []) sides: dict = { 'sell': 'asks', 'buy': 'bids', } for i in range(0, len(changes)): change = changes[i] key = self.safe_string(change, 0) side = self.safe_string(sides, key) price = self.safe_number(change, 1) amount = self.safe_number(change, 2) bookside = orderbook[side] bookside.store(price, amount) orderbook['timestamp'] = timestamp orderbook['datetime'] = self.iso8601(timestamp) client.resolve(orderbook, messageHash) def handle_subscription_status(self, client: Client, message): # # { # "type": "subscriptions", # "channels": [ # { # "name": "level2", # "product_ids": ["ETH-BTC"] # } # ] # } # return message def handle_error_message(self, client: Client, message) -> Bool: # # { # "type": "error", # "message": "error message", # /* ...""" # } # # auth error # # { # "type": "error", # "message": "Authentication Failed", # "reason": "{"message":"Invalid API Key"}" # } # errMsg = self.safe_string(message, 'message') reason = self.safe_string(message, 'reason') try: if errMsg == 'Authentication Failed': raise AuthenticationError('Authentication failed: ' + reason) else: raise ExchangeError(self.id + ' ' + reason) except Exception as error: client.reject(error) return True def handle_message(self, client: Client, message): type = self.safe_string(message, 'type') methods: dict = { 'snapshot': self.handle_order_book, 'l2update': self.handle_order_book, 'subscribe': self.handle_subscription_status, 'ticker': self.handle_ticker, 'received': self.handle_order, 'open': self.handle_order, 'change': self.handle_order, 'done': self.handle_order, 'error': self.handle_error_message, } length = len(client.url) - 0 authenticated = client.url[length - 1] == '?' method = self.safe_value(methods, type) if method is None: if type == 'match': if authenticated: self.handle_my_trade(client, message) self.handle_order(client, message) else: self.handle_trade(client, message) else: method(client, message)