# -*- 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 from ccxt.base.precise import Precise 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_string(trade, 'maker_fee_rate') else: parsed['takerOrMaker'] = 'taker' feeRate = self.safe_string(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_string(parsed, 'cost') feeCost = Precise.string_mul(cost, feeRate) parsed['fee'] = { 'rate': self.parse_number(feeRate), 'cost': self.parse_number(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.safe_string(tradeEntry, 'cost', '0') totalAmount = self.safe_string(tradeEntry, 'amount', '0') if not Precise.string_eq(totalAmount, '0'): previousOrder['average'] = self.parse_number(Precise.string_div(totalCost, totalAmount)) previousOrder['cost'] = self.parse_number(totalCost) previousOrderFilled = self.safe_string(previousOrder, 'filled') if previousOrderFilled is not None: previousOrder['filled'] = self.parse_number(Precise.string_add(previousOrderFilled, self.safe_string(trade, 'amount'))) if previousOrder['amount'] is not None: previousOrder['remaining'] = self.parse_number(Precise.string_sub(self.safe_string(previousOrder, 'amount'), self.safe_string(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']) previousOrderFee = self.safe_dict(previousOrder, 'fee') tradeFee = self.safe_dict(trade, 'fee') previousOrder['fee']['cost'] = self.parse_number(Precise.string_add(self.safe_string(previousOrderFee, 'cost'), self.safe_string(tradeFee, '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_string_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_string(order, 'remaining_size') type = self.safe_string(order, 'type') filled = None if (amount is not None) and (remaining is not None): filled = Precise.string_sub(amount, remaining) elif type == 'received': filled = '0' if amount is not None: remaining = Precise.string_sub(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': self.parse_number(amount), 'cost': None, 'average': None, 'filled': self.parse_number(filled), 'remaining': self.parse_number(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)