# -*- 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, ArrayCacheByTimestamp import hashlib from ccxt.base.types import Any, Balances, Bool, Int, Num, Order, OrderBook, OrderSide, OrderType, 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 BadRequest from ccxt.base.errors import InvalidOrder from ccxt.base.precise import Precise class poloniex(ccxt.async_support.poloniex): def describe(self) -> Any: return self.deep_extend(super(poloniex, self).describe(), { 'has': { 'ws': True, 'watchOHLCV': True, 'watchOrderBook': True, 'watchTicker': True, 'watchTickers': True, 'watchTrades': True, 'watchTradesForSymbols': True, 'watchBalance': True, 'watchStatus': False, 'watchOrders': True, 'watchMyTrades': True, 'createOrderWs': True, 'editOrderWs': False, 'fetchOpenOrdersWs': False, 'fetchOrderWs': False, 'cancelOrderWs': True, 'cancelOrdersWs': True, 'cancelAllOrdersWs': True, 'fetchTradesWs': False, 'fetchBalanceWs': False, }, 'urls': { 'api': { 'ws': { 'public': 'wss://ws.poloniex.com/ws/public', 'private': 'wss://ws.poloniex.com/ws/private', }, }, }, 'options': { 'createMarketBuyOrderRequiresPrice': True, 'tradesLimit': 1000, 'ordersLimit': 1000, 'OHLCVLimit': 1000, 'watchOrderBook': { 'name': 'book_lv2', # can also be 'book' }, 'connectionsLimit': 2000, # 2000 public, 2000 private, 4000 total, only for subscribe events, unsubscribe not restricted 'requestsLimit': 500, # per second, only for subscribe events, unsubscribe not restricted 'timeframes': { '1m': 'candles_minute_1', '5m': 'candles_minute_5', '10m': 'candles_minute_10', '15m': 'candles_minute_15', '30m': 'candles_minute_30', '1h': 'candles_hour_1', '2h': 'candles_hour_2', '4h': 'candles_hour_4', '6h': 'candles_hour_6', '12h': 'candles_hour_12', '1d': 'candles_day_1', '3d': 'candles_day_3', '1w': 'candles_week_1', '1M': 'candles_month_1', }, }, 'streaming': { 'keepAlive': 15000, 'ping': self.ping, }, }) async def authenticate(self, params={}): """ @ignore authenticates the user to access private web socket channels https://api-docs.poloniex.com/spot/websocket/authentication :returns dict: response from exchange """ self.check_required_credentials() timestamp = self.number_to_string(self.milliseconds()) url = self.urls['api']['ws']['private'] messageHash = 'authenticated' client = self.client(url) future = self.safe_value(client.subscriptions, messageHash) if future is None: accessPath = '/ws' requestString = 'GET\n' + accessPath + '\nsignTimestamp=' + timestamp signature = self.hmac(self.encode(requestString), self.encode(self.secret), hashlib.sha256, 'base64') request: dict = { 'event': 'subscribe', 'channel': ['auth'], 'params': { 'key': self.apiKey, 'signTimestamp': timestamp, 'signature': signature, 'signatureMethod': 'HmacSHA256', # optional 'signatureVersion': '2', # optional }, } message = self.extend(request, params) future = await self.watch(url, messageHash, message, messageHash) # # { # "data": { # "success": True, # "ts": 1645597033915 # }, # "channel": "auth" # } # # # Failure to return results # # { # "data": { # "success": False, # "message": "Authentication failed!", # "ts": 1646276295075 # }, # "channel": "auth" # } # client.subscriptions[messageHash] = future return future async def subscribe(self, name: str, messageHash: str, isPrivate: bool, symbols: Strings = None, params={}): """ @ignore Connects to a websocket channel :param str name: name of the channel :param str messageHash: unique identifier for the message :param boolean isPrivate: True for the authenticated url, False for the public url :param str[] [symbols]: CCXT market symbols :param dict [params]: extra parameters specific to the poloniex api :returns dict: data from the websocket stream """ publicOrPrivate = 'private' if isPrivate else 'public' url = self.urls['api']['ws'][publicOrPrivate] subscribe: dict = { 'event': 'subscribe', 'channel': [ name, ], } marketIds = [] if self.is_empty(symbols): marketIds.append('all') else: messageHash = messageHash + '::' + ','.join(symbols) marketIds = self.market_ids(symbols) if name != 'balances': subscribe['symbols'] = marketIds request = self.extend(subscribe, params) return await self.watch(url, messageHash, request, messageHash) async def trade_request(self, name: str, params={}): """ @ignore Connects to a websocket channel :param str name: name of the channel :param dict [params]: extra parameters specific to the poloniex api :returns dict: data from the websocket stream """ url = self.urls['api']['ws']['private'] messageHash = str(self.nonce()) subscribe: dict = { 'id': messageHash, 'event': name, 'params': params, } return await self.watch(url, messageHash, subscribe, messageHash) async def create_order_ws(self, symbol: str, type: OrderType, side: OrderSide, amount: float, price: Num = None, params={}) -> Order: """ https://api-docs.poloniex.com/spot/websocket/trade-request#create-order create a trade order :param str symbol: unified symbol of the market to create an order in :param str type: 'market' or 'limit' :param str side: 'buy' or 'sell' :param float amount: how much of currency you want to trade in units of base currency :param float [price]: the price at which the order is to be fulfilled, in units of the quote currency, ignored in market orders :param dict [params]: extra parameters specific to the poloniex api endpoint :param str [params.timeInForce]: GTC(default), IOC, FOK :param str [params.clientOrderId]: Maximum 64-character length.* :param float [params.cost]: *spot market buy only* the quote quantity that can be used alternative for the amount EXCHANGE SPECIFIC PARAMETERS :param str [params.amount]: quote units for the order :param boolean [params.allowBorrow]: allow order to be placed by borrowing funds(Default: False) :param str [params.stpMode]: self-trade prevention, defaults to expire_taker, none: enable self-trade; expire_taker: taker order will be canceled when self-trade happens :param str [params.slippageTolerance]: used to control the maximum slippage ratio, the value range is greater than 0 and less than 1 :returns dict: an `order structure ` """ await self.load_markets() await self.authenticate() market = self.market(symbol) uppercaseType = type.upper() uppercaseSide = side.upper() isPostOnly = self.is_post_only(uppercaseType == 'MARKET', uppercaseType == 'LIMIT_MAKER', params) if isPostOnly: uppercaseType = 'LIMIT_MAKER' request: dict = { 'symbol': market['id'], 'side': side.upper(), 'type': type.upper(), } if (uppercaseType == 'MARKET') and (uppercaseSide == 'BUY'): quoteAmount = None createMarketBuyOrderRequiresPrice = True createMarketBuyOrderRequiresPrice, params = self.handle_option_and_params(params, 'createOrder', 'createMarketBuyOrderRequiresPrice', True) cost = self.safe_number(params, 'cost') params = self.omit(params, 'cost') if cost is not None: quoteAmount = self.cost_to_precision(symbol, cost) elif createMarketBuyOrderRequiresPrice: if price is None: raise InvalidOrder(self.id + ' createOrder() requires the price argument for market buy orders to calculate the total cost to spend(amount * price), alternatively set the createMarketBuyOrderRequiresPrice option or param to False and pass the cost to spend(quote quantity) in the amount argument') else: amountString = self.number_to_string(amount) priceString = self.number_to_string(price) costRequest = Precise.string_mul(amountString, priceString) quoteAmount = self.cost_to_precision(symbol, costRequest) else: quoteAmount = self.cost_to_precision(symbol, amount) request['amount'] = quoteAmount else: request['quantity'] = self.amount_to_precision(market['symbol'], amount) if price is not None: request['price'] = self.price_to_precision(symbol, price) orders = await self.trade_request('createOrder', self.extend(request, params)) order = self.safe_dict(orders, 0) return order async def cancel_order_ws(self, id: str, symbol: Str = None, params={}): """ https://api-docs.poloniex.com/spot/websocket/trade-request#cancel-multiple-orders cancel multiple orders :param str id: order id :param str [symbol]: unified market symbol :param dict [params]: extra parameters specific to the poloniex api endpoint :param str [params.clientOrderId]: client order id :returns dict: an list of `order structures ` """ clientOrderId = self.safe_string(params, 'clientOrderId') if clientOrderId is not None: clientOrderIds = self.safe_value(params, 'clientOrderId', []) params['clientOrderIds'] = self.array_concat(clientOrderIds, [clientOrderId]) orders = await self.cancel_orders_ws([id], symbol, params) order = self.safe_dict(orders, 0) return order async def cancel_orders_ws(self, ids: List[str], symbol: Str = None, params={}): """ https://api-docs.poloniex.com/spot/websocket/trade-request#cancel-multiple-orders cancel multiple orders :param str[] ids: order ids :param str symbol: unified market symbol, default is None :param dict [params]: extra parameters specific to the poloniex api endpoint :param str[] [params.clientOrderIds]: client order ids :returns dict: an list of `order structures ` """ await self.load_markets() await self.authenticate() request: dict = { 'orderIds': ids, } return await self.trade_request('cancelOrders', self.extend(request, params)) async def cancel_all_orders_ws(self, symbol: Str = None, params={}) -> List[Order]: """ https://api-docs.poloniex.com/spot/websocket/trade-request#cancel-all-orders cancel all open orders of a type. Only applicable to Option in Portfolio Margin mode, and MMP privilege is required. :param str symbol: unified market symbol, only orders in the market of self symbol are cancelled when symbol is not None :param dict [params]: extra parameters specific to the poloniex api endpoint :returns dict[]: a list of `order structures ` """ await self.load_markets() await self.authenticate() return await self.trade_request('cancelAllOrders', params) def handle_order_request(self, client: Client, message): # # { # "id": "1234567", # "data": [{ # "orderId": 205343650954092544, # "clientOrderId": "", # "message": "", # "code": 200 # }] # } # messageHash = self.safe_string(message, 'id') data = self.safe_value(message, 'data', []) orders = [] for i in range(0, len(data)): order = data[i] parsedOrder = self.parse_ws_order(order) orders.append(parsedOrder) client.resolve(orders, messageHash) async def watch_ohlcv(self, symbol: str, timeframe: str = '1m', since: Int = None, limit: Int = None, params={}) -> List[list]: """ watches historical candlestick data containing the open, high, low, and close price, and the volume of a market https://api-docs.poloniex.com/spot/websocket/market-data#candlesticks :param str symbol: unified symbol of the market to fetch OHLCV data for :param str timeframe: the length of time each candle represents :param int [since]: timestamp in ms of the earliest candle to fetch :param int [limit]: the maximum amount of candles to fetch :param dict [params]: extra parameters specific to the exchange API endpoint :returns int[][]: A list of candles ordered, open, high, low, close, volume """ await self.load_markets() timeframes = self.safe_value(self.options, 'timeframes', {}) channel = self.safe_string(timeframes, timeframe, timeframe) if channel is None: raise BadRequest(self.id + ' watchOHLCV cannot take a timeframe of ' + timeframe) ohlcv = await self.subscribe(channel, channel, False, [symbol], params) if self.newUpdates: limit = ohlcv.getLimit(symbol, limit) return self.filter_by_since_limit(ohlcv, since, limit, 0, True) 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 https://api-docs.poloniex.com/spot/websocket/market-data#ticker :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 ` """ await self.load_markets() symbol = self.symbol(symbol) tickers = await self.watch_tickers([symbol], params) return self.safe_value(tickers, symbol) 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 a specific market https://api-docs.poloniex.com/spot/websocket/market-data#ticker :param str[] symbols: :param dict [params]: extra parameters specific to the exchange API endpoint :returns dict: a `ticker structure ` """ await self.load_markets() name = 'ticker' symbols = self.market_symbols(symbols) newTickers = await self.subscribe(name, name, False, symbols, params) if self.newUpdates: return newTickers 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 https://api-docs.poloniex.com/spot/websocket/market-data#trades :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 ` """ return await self.watch_trades_for_symbols([symbol], since, limit, params) 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 list of symbols https://api-docs.poloniex.com/spot/websocket/market-data#trades :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 ` """ await self.load_markets() symbols = self.market_symbols(symbols, None, False, True, True) name = 'trades' url = self.urls['api']['ws']['public'] marketIds = self.market_ids(symbols) subscribe: dict = { 'event': 'subscribe', 'channel': [ name, ], 'symbols': marketIds, } request = self.extend(subscribe, params) messageHashes = [] if symbols is not None: for i in range(0, len(symbols)): messageHashes.append(name + '::' + symbols[i]) trades = await self.watch_multiple(url, messageHashes, request, messageHashes) 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_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 https://api-docs.poloniex.com/spot/websocket/market-data#book-level-2 :param str symbol: unified symbol of the market to fetch the order book for :param int [limit]: not used by poloniex watchOrderBook :param dict [params]: extra parameters specific to the exchange API endpoint :returns dict: A dictionary of `order book structures ` indexed by market symbols """ await self.load_markets() watchOrderBookOptions = self.safe_value(self.options, 'watchOrderBook') name = self.safe_string(watchOrderBookOptions, 'name', 'book_lv2') name, params = self.handle_option_and_params(params, 'method', 'name', name) orderbook = await self.subscribe(name, name, False, [symbol], params) return orderbook.limit() 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 https://api-docs.poloniex.com/spot/websocket/order :param str symbol: unified market symbol of the market orders were made in :param int [since]: not used by poloniex watchOrders :param int [limit]: not used by poloniex watchOrders :param dict [params]: extra parameters specific to the exchange API endpoint :returns dict[]: a list of `order structures ` """ await self.load_markets() name = 'orders' await self.authenticate() if symbol is not None: symbol = self.symbol(symbol) symbols = None if (symbol is None) else [symbol] orders = await self.subscribe(name, name, True, symbols, params) if self.newUpdates: limit = orders.getLimit(symbol, limit) return self.filter_by_since_limit(orders, 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 using orders stream https://api-docs.poloniex.com/spot/websocket/order :param str symbol: unified market symbol of the market orders were made in :param int [since]: not used by poloniex watchMyTrades :param int [limit]: not used by poloniex watchMyTrades :param dict [params]: extra parameters specific to the poloniex strean :returns dict[]: a list of `trade structures ` """ await self.load_markets() name = 'orders' messageHash = 'myTrades' await self.authenticate() if symbol is not None: symbol = self.symbol(symbol) symbols = None if (symbol is None) else [symbol] trades = await self.subscribe(name, messageHash, True, symbols, params) if self.newUpdates: limit = trades.getLimit(symbol, limit) return self.filter_by_since_limit(trades, since, limit, 'timestamp', True) async def watch_balance(self, params={}) -> Balances: """ watch balance and get the amount of funds available for trading or funds locked in orders https://api-docs.poloniex.com/spot/websocket/balance :param dict [params]: extra parameters specific to the exchange API endpoint :returns dict: a `balance structure ` """ await self.load_markets() name = 'balances' await self.authenticate() return await self.subscribe(name, name, True, None, params) def parse_ws_ohlcv(self, ohlcv, market=None) -> list: # # { # "symbol": "BTC_USDT", # "amount": "840.7240416", # "high": "24832.35", # "quantity": "0.033856", # "tradeCount": 1, # "low": "24832.35", # "closeTime": 1676942519999, # "startTime": 1676942460000, # "close": "24832.35", # "open": "24832.35", # "ts": 1676942492072 # } # return [ self.safe_integer(ohlcv, 'startTime'), self.safe_number(ohlcv, 'open'), self.safe_number(ohlcv, 'high'), self.safe_number(ohlcv, 'low'), self.safe_number(ohlcv, 'close'), self.safe_number(ohlcv, 'quantity'), ] def handle_ohlcv(self, client: Client, message): # # { # "channel": "candles_minute_1", # "data": [ # { # "symbol": "BTC_USDT", # "amount": "840.7240416", # "high": "24832.35", # "quantity": "0.033856", # "tradeCount": 1, # "low": "24832.35", # "closeTime": 1676942519999, # "startTime": 1676942460000, # "close": "24832.35", # "open": "24832.35", # "ts": 1676942492072 # } # ] # } # data = self.safe_value(message, 'data') data = self.safe_value(data, 0) channel = self.safe_string(message, 'channel') marketId = self.safe_string(data, 'symbol') symbol = self.safe_symbol(marketId) market = self.safe_market(symbol) timeframes = self.safe_value(self.options, 'timeframes', {}) timeframe = self.find_timeframe(channel, timeframes) messageHash = channel + '::' + symbol parsed = self.parse_ws_ohlcv(data, market) self.ohlcvs[symbol] = self.safe_value(self.ohlcvs, symbol, {}) stored = self.safe_value(self.ohlcvs[symbol], timeframe) if symbol is not None: if stored is None: limit = self.safe_integer(self.options, 'OHLCVLimit', 1000) stored = ArrayCacheByTimestamp(limit) self.ohlcvs[symbol][timeframe] = stored stored.append(parsed) client.resolve(stored, messageHash) return message def handle_trade(self, client: Client, message): # # { # "channel": "trades", # "data": [ # { # "symbol": "BTC_USDT", # "amount": "13.41634893", # "quantity": "0.000537", # "takerSide": "buy", # "createTime": 1676950548834, # "price": "24983.89", # "id": "62486976", # "ts": 1676950548839 # } # ] # } # data = self.safe_value(message, 'data', []) for i in range(0, len(data)): item = data[i] marketId = self.safe_string(item, 'symbol') if marketId is not None: trade = self.parse_ws_trade(item) symbol = trade['symbol'] type = 'trades' messageHash = type + '::' + symbol 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 parse_ws_trade(self, trade, market=None): # # handleTrade # # { # "symbol": "BTC_USDT", # "amount": "13.41634893", # "quantity": "0.000537", # "takerSide": "buy", # "createTime": 1676950548834, # "price": "24983.89", # "id": "62486976", # "ts": 1676950548839 # } # # private trade # { # "orderId":"186250258089635840", # "tradeId":"62036513", # "clientOrderId":"", # "accountType":"SPOT", # "eventType":"trade", # "symbol":"ADA_USDT", # "side":"SELL", # "type":"MARKET", # "price":"0", # "quantity":"3", # "state":"FILLED", # "createTime":1685371921891, # "tradeTime":1685371921908, # "tradePrice":"0.37694", # "tradeQty":"3", # "feeCurrency":"USDT", # "tradeFee":"0.00226164", # "tradeAmount":"1.13082", # "filledQuantity":"3", # "filledAmount":"1.13082", # "ts":1685371921945, # "source":"WEB", # "orderAmount":"0", # "matchRole":"TAKER" # } # marketId = self.safe_string(trade, 'symbol') market = self.safe_market(marketId, market) timestamp = self.safe_integer(trade, 'createTime') takerMaker = self.safe_string_lower_2(trade, 'matchRole', 'taker') return self.safe_trade({ 'info': trade, 'id': self.safe_string_2(trade, 'id', 'tradeId'), 'symbol': self.safe_string(market, 'symbol'), 'timestamp': timestamp, 'datetime': self.iso8601(timestamp), 'order': self.safe_string(trade, 'orderId'), 'type': self.safe_string_lower(trade, 'type'), 'side': self.safe_string_lower_2(trade, 'takerSide', 'side'), 'takerOrMaker': takerMaker, 'price': self.omit_zero(self.safe_string_2(trade, 'tradePrice', 'price')), 'amount': self.omit_zero(self.safe_string_2(trade, 'filledQuantity', 'quantity')), 'cost': self.safe_string_2(trade, 'amount', 'filledAmount'), 'fee': { 'rate': None, 'cost': self.safe_string(trade, 'tradeFee'), 'currency': self.safe_string(trade, 'feeCurrency'), }, }, market) def parse_status(self, status): statuses: dict = { 'NEW': 'open', 'PARTIALLY_FILLED': 'open', 'FILLED': 'closed', 'PENDING_CANCEL': 'open', 'PARTIALLY_CANCELED': 'open', 'CANCELED': 'canceled', # FAILED } return self.safe_string(statuses, status, status) def parse_ws_order_trade(self, trade, market=None): # # { # "symbol": "BTC_USDT", # "type": "LIMIT", # "quantity": "1", # "orderId": "32471407854219264", # "tradeFee": "0", # "clientOrderId": "", # "accountType": "SPOT", # "feeCurrency": "", # "eventType": "place", # "source": "API", # "side": "BUY", # "filledQuantity": "0", # "filledAmount": "0", # "matchRole": "MAKER", # "state": "NEW", # "tradeTime": 0, # "tradeAmount": "0", # "orderAmount": "0", # "createTime": 1648708186922, # "price": "47112.1", # "tradeQty": "0", # "tradePrice": "0", # "tradeId": "0", # "ts": 1648708187469 # } # timestamp = self.safe_integer(trade, 'tradeTime') marketId = self.safe_string(trade, 'symbol') return self.safe_trade({ 'info': trade, 'id': self.safe_string(trade, 'tradeId'), 'symbol': self.safe_symbol(marketId, market), 'timestamp': timestamp, 'datetime': self.iso8601(timestamp), 'order': self.safe_string(trade, 'orderId'), 'type': self.safe_string_lower(trade, 'type'), 'side': self.safe_string(trade, 'side'), 'takerOrMaker': self.safe_string_lower(trade, 'matchRole'), 'price': self.safe_string(trade, 'price'), 'amount': self.safe_string(trade, 'tradeAmount'), 'cost': None, 'fee': { 'rate': None, 'cost': self.safe_string(trade, 'tradeFee'), 'currency': self.safe_string(trade, 'feeCurrency'), }, }, market) def handle_order(self, client: Client, message): # # Order is created # # { # "channel": "orders", # "data": [ # { # "symbol": "BTC_USDT", # "type": "LIMIT", # "quantity": "1", # "orderId": "32471407854219264", # "tradeFee": "0", # "clientOrderId": "", # "accountType": "SPOT", # "feeCurrency": "", # "eventType": "place", # "source": "API", # "side": "BUY", # "filledQuantity": "0", # "filledAmount": "0", # "matchRole": "MAKER", # "state": "NEW", # "tradeTime": 0, # "tradeAmount": "0", # "orderAmount": "0", # "createTime": 1648708186922, # "price": "47112.1", # "tradeQty": "0", # "tradePrice": "0", # "tradeId": "0", # "ts": 1648708187469 # } # ] # } # data = self.safe_value(message, 'data', []) orders = self.orders if orders is None: limit = self.safe_integer(self.options, 'ordersLimit') orders = ArrayCacheBySymbolById(limit) self.orders = orders marketIds = [] for i in range(0, len(data)): order = self.safe_value(data, i) marketId = self.safe_string(order, 'symbol') eventType = self.safe_string(order, 'eventType') if marketId is not None: symbol = self.safe_symbol(marketId) orderId = self.safe_string(order, 'orderId') clientOrderId = self.safe_string(order, 'clientOrderId') if eventType == 'place' or eventType == 'canceled': parsed = self.parse_ws_order(order) orders.append(parsed) else: previousOrders = self.safe_value(orders.hashmap, symbol, {}) previousOrder = self.safe_value_2(previousOrders, orderId, clientOrderId) trade = self.parse_ws_trade(order) self.handle_my_trades(client, trade) if previousOrder['trades'] is None: previousOrder['trades'] = [] previousOrder['trades'].append(trade) previousOrder['lastTradeTimestamp'] = trade['timestamp'] totalCost = '0' totalAmount = '0' previousOrderTrades = previousOrder['trades'] for j in range(0, len(previousOrderTrades)): previousOrderTrade = previousOrderTrades[j] cost = self.number_to_string(previousOrderTrade['cost']) amount = self.number_to_string(previousOrderTrade['amount']) totalCost = Precise.string_add(totalCost, cost) totalAmount = Precise.string_add(totalAmount, amount) if Precise.string_gt(totalAmount, '0'): previousOrder['average'] = self.parse_number(Precise.string_div(totalCost, totalAmount)) previousOrder['cost'] = self.parse_number(totalCost) if previousOrder['filled'] is not None: tradeAmount = self.number_to_string(trade['amount']) previousOrderFilled = self.number_to_string(previousOrder['filled']) previousOrderFilled = Precise.string_add(previousOrderFilled, tradeAmount) previousOrder['filled'] = previousOrderFilled if previousOrder['amount'] is not None: previousOrderAmount = self.number_to_string(previousOrder['amount']) previousOrder['remaining'] = self.parse_number(Precise.string_sub(previousOrderAmount, previousOrderFilled)) if previousOrder['fee'] is None: previousOrder['fee'] = { 'rate': None, 'cost': 0, 'currency': trade['fee']['currency'], } if (previousOrder['fee']['cost'] is not None) and (trade['fee']['cost'] is not None): stringOrderCost = self.number_to_string(previousOrder['fee']['cost']) stringTradeCost = self.number_to_string(trade['fee']['cost']) previousOrder['fee']['cost'] = Precise.string_add(stringOrderCost, stringTradeCost) rawState = self.safe_string(order, 'state') state = self.parse_status(rawState) previousOrder['status'] = state # update the newUpdates count orders.append(previousOrder) marketIds.append(marketId) for i in range(0, len(marketIds)): marketId = marketIds[i] market = self.market(marketId) symbol = market['symbol'] messageHash = 'orders::' + symbol client.resolve(orders, messageHash) client.resolve(orders, 'orders') return message def parse_ws_order(self, order, market=None): # # { # "symbol": "BTC_USDT", # "type": "LIMIT", # "quantity": "1", # "orderId": "32471407854219264", # "tradeFee": "0", # "clientOrderId": "", # "accountType": "SPOT", # "feeCurrency": "", # "eventType": "place", # "source": "API", # "side": "BUY", # "filledQuantity": "0", # "filledAmount": "0", # "matchRole": "MAKER", # "state": "NEW", # "tradeTime": 0, # "tradeAmount": "0", # "orderAmount": "0", # "createTime": 1648708186922, # "price": "47112.1", # "tradeQty": "0", # "tradePrice": "0", # "tradeId": "0", # "ts": 1648708187469 # } # id = self.safe_string(order, 'orderId') clientOrderId = self.safe_string(order, 'clientOrderId') marketId = self.safe_string(order, 'symbol') timestamp = self.safe_string(order, 'ts') filledAmount = self.safe_string(order, 'filledAmount') status = self.safe_string(order, 'state') trades = None if not Precise.string_eq(filledAmount, '0'): trades = [] trade = self.parse_ws_order_trade(order) trades.append(trade) return self.safe_order({ 'info': order, 'symbol': self.safe_symbol(marketId, market), 'id': id, 'clientOrderId': clientOrderId, 'timestamp': timestamp, 'datetime': self.iso8601(timestamp), 'lastTradeTimestamp': None, 'type': self.safe_string(order, 'type'), 'timeInForce': None, 'postOnly': None, 'side': self.safe_string(order, 'side'), 'price': self.safe_string(order, 'price'), 'stopPrice': None, 'triggerPrice': None, 'amount': self.safe_string(order, 'quantity'), 'cost': None, 'average': None, 'filled': filledAmount, 'remaining': self.safe_string(order, 'remaining_size'), 'status': self.parse_status(status), 'fee': { 'rate': None, 'cost': self.safe_string(order, 'tradeFee'), 'currency': self.safe_string(order, 'feeCurrency'), }, 'trades': trades, }) def handle_ticker(self, client: Client, message): # # { # "channel": "ticker", # "data": [ # { # "symbol": "BTC_USDT", # "startTime": 1677280800000, # "open": "23154.32", # "high": "23212.21", # "low": "22761.01", # "close": "23148.86", # "quantity": "105.179566", # "amount": "2423161.17436702", # "tradeCount": 17582, # "dailyChange": "-0.0002", # "markPrice": "23151.09", # "closeTime": 1677367197924, # "ts": 1677367251090 # } # ] # } # data = self.safe_value(message, 'data', []) newTickers: dict = {} for i in range(0, len(data)): item = data[i] marketId = self.safe_string(item, 'symbol') if marketId is not None: ticker = self.parse_ticker(item) symbol = ticker['symbol'] self.tickers[symbol] = ticker newTickers[symbol] = ticker messageHashes = self.find_message_hashes(client, 'ticker::') for i in range(0, len(messageHashes)): messageHash = messageHashes[i] parts = messageHash.split('::') symbolsString = parts[1] symbols = symbolsString.split(',') tickers = self.filter_by_array(newTickers, 'symbol', symbols) if not self.is_empty(tickers): client.resolve(tickers, messageHash) client.resolve(newTickers, 'ticker') return message def handle_order_book(self, client: Client, message): # # snapshot # # { # "channel": "book_lv2", # "data": [ # { # "symbol": "BTC_USDT", # "createTime": 1677368876253, # "asks": [ # ["5.65", "0.02"], # ... # ], # "bids": [ # ["6.16", "0.6"], # ... # ], # "lastId": 164148724, # "id": 164148725, # "ts": 1677368876316 # } # ], # "action": "snapshot" # } # # update # # { # "channel": "book_lv2", # "data": [ # { # "symbol": "BTC_USDT", # "createTime": 1677368876882, # "asks": [ # ["6.35", "3"] # ], # "bids": [ # ["5.65", "0.02"] # ], # "lastId": 164148725, # "id": 164148726, # "ts": 1677368876890 # } # ], # "action": "update" # } # data = self.safe_value(message, 'data', []) type = self.safe_string(message, 'action') snapshot = type == 'snapshot' update = type == 'update' for i in range(0, len(data)): item = data[i] marketId = self.safe_string(item, 'symbol') market = self.safe_market(marketId) symbol = market['symbol'] name = 'book_lv2' messageHash = name + '::' + symbol subscription = self.safe_value(client.subscriptions, messageHash, {}) limit = self.safe_integer(subscription, 'limit') timestamp = self.safe_integer(item, 'ts') asks = self.safe_value(item, 'asks') bids = self.safe_value(item, 'bids') if snapshot or update: if snapshot: self.orderbooks[symbol] = self.order_book({}, limit) orderbook = self.orderbooks[symbol] if bids is not None: for j in range(0, len(bids)): bid = self.safe_value(bids, j) price = self.safe_number(bid, 0) amount = self.safe_number(bid, 1) bidsSide = orderbook['bids'] bidsSide.store(price, amount) if asks is not None: for j in range(0, len(asks)): ask = self.safe_value(asks, j) price = self.safe_number(ask, 0) amount = self.safe_number(ask, 1) asksSide = orderbook['asks'] asksSide.store(price, amount) orderbook['symbol'] = symbol orderbook['timestamp'] = timestamp orderbook['datetime'] = self.iso8601(timestamp) client.resolve(orderbook, messageHash) def handle_balance(self, client: Client, message): # # { # "channel": "balances", # "data": [ # { # "changeTime": 1657312008411, # "accountId": "1234", # "accountType": "SPOT", # "eventType": "place_order", # "available": "9999999983.668", # "currency": "BTC", # "id": 60018450912695040, # "userId": 12345, # "hold": "16.332", # "ts": 1657312008443 # } # ] # } # data = self.safe_value(message, 'data', []) messageHash = 'balances' self.balance = self.parse_ws_balance(data) client.resolve(self.balance, messageHash) def parse_ws_balance(self, response): # # [ # { # "changeTime": 1657312008411, # "accountId": "1234", # "accountType": "SPOT", # "eventType": "place_order", # "available": "9999999983.668", # "currency": "BTC", # "id": 60018450912695040, # "userId": 12345, # "hold": "16.332", # "ts": 1657312008443 # } # ] # firstBalance = self.safe_value(response, 0, {}) timestamp = self.safe_integer(firstBalance, 'ts') result: dict = { 'info': response, 'timestamp': timestamp, 'datetime': self.iso8601(timestamp), } for i in range(0, len(response)): balance = self.safe_value(response, i) currencyId = self.safe_string(balance, 'currency') code = self.safe_currency_code(currencyId) newAccount = self.account() newAccount['free'] = self.safe_string(balance, 'available') newAccount['used'] = self.safe_string(balance, 'hold') result[code] = newAccount return self.safe_balance(result) def handle_my_trades(self, client: Client, parsedTrade): # emulated using the orders' stream messageHash = 'myTrades' symbol = parsedTrade['symbol'] if self.myTrades is None: limit = self.safe_integer(self.options, 'tradesLimit', 1000) self.myTrades = ArrayCacheBySymbolById(limit) trades = self.myTrades trades.append(parsedTrade) client.resolve(trades, messageHash) symbolMessageHash = messageHash + ':' + symbol client.resolve(trades, symbolMessageHash) def handle_pong(self, client: Client): client.lastPong = self.milliseconds() def handle_message(self, client: Client, message): if self.handle_error_message(client, message): return type = self.safe_string(message, 'channel') event = self.safe_string(message, 'event') if event == 'pong': client.lastPong = self.milliseconds() methods: dict = { 'candles_minute_1': self.handle_ohlcv, 'candles_minute_5': self.handle_ohlcv, 'candles_minute_10': self.handle_ohlcv, 'candles_minute_15': self.handle_ohlcv, 'candles_minute_30': self.handle_ohlcv, 'candles_hour_1': self.handle_ohlcv, 'candles_hour_2': self.handle_ohlcv, 'candles_hour_4': self.handle_ohlcv, 'candles_hour_6': self.handle_ohlcv, 'candles_hour_12': self.handle_ohlcv, 'candles_day_1': self.handle_ohlcv, 'candles_day_3': self.handle_ohlcv, 'candles_week_1': self.handle_ohlcv, 'candles_month_1': self.handle_ohlcv, 'book': self.handle_order_book, 'book_lv2': self.handle_order_book, 'ticker': self.handle_ticker, 'trades': self.handle_trade, 'orders': self.handle_order, 'balances': self.handle_balance, 'createOrder': self.handle_order_request, 'cancelOrder': self.handle_order_request, 'cancelAllOrders': self.handle_order_request, 'auth': self.handle_authenticate, } method = self.safe_value(methods, type) if type == 'auth': self.handle_authenticate(client, message) elif type is None: self.handle_order_request(client, message) else: data = self.safe_value(message, 'data', []) dataLength = len(data) if dataLength > 0: method(client, message) def handle_error_message(self, client: Client, message) -> Bool: # # { # message: 'Invalid channel value ["ordersss"]', # event: 'error' # } # # { # "orderId": 0, # "clientOrderId": null, # "message": "Currency trade disabled", # "code": 21352 # } # # { # "event": "error", # "message": "Platform in maintenance mode" # } # { # "id":"1722386782048", # "data":[ # { # "orderId":0, # "clientOrderId":null, # "message":"available insufficient", # "code":21721 # } # ] # } # id = self.safe_string(message, 'id') event = self.safe_string(message, 'event') data = self.safe_list(message, 'data') first = self.safe_dict(data, 0) orderId = self.safe_string(first, 'orderId') if (event == 'error') or (orderId == '0'): try: error = self.safe_string(first, 'message') code = self.safe_string(first, 'code') feedback = self.id + ' ' + self.json(message) self.throw_exactly_matched_exception(self.exceptions['exact'], code, feedback) self.throw_broadly_matched_exception(self.exceptions['broad'], error, feedback) raise ExchangeError(feedback) except Exception as e: if isinstance(e, AuthenticationError): messageHash = 'authenticated' client.reject(e, messageHash) if messageHash in client.subscriptions: del client.subscriptions[messageHash] else: client.reject(e, id) return True return False def handle_authenticate(self, client: Client, message): # # { # "success": True, # "ret_msg": '', # "op": "auth", # "conn_id": "ce3dpomvha7dha97tvp0-2xh" # } # data = self.safe_value(message, 'data') success = self.safe_value(data, 'success') messageHash = 'authenticated' if success: client.resolve(message, messageHash) else: error = AuthenticationError(self.id + ' ' + self.json(message)) client.reject(error, messageHash) if messageHash in client.subscriptions: del client.subscriptions[messageHash] return message def ping(self, client: Client): return { 'event': 'ping', }