# -*- 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, 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 AuthenticationError from ccxt.base.precise import Precise class phemex(ccxt.async_support.phemex): def describe(self) -> Any: return self.deep_extend(super(phemex, self).describe(), { 'has': { 'ws': True, 'watchTicker': True, 'watchTickers': True, 'watchTrades': True, 'watchMyTrades': True, 'watchOrders': True, 'watchOrderBook': True, 'watchOHLCV': True, 'watchPositions': None, # TODO # mutli-endpoints are not supported: https://github.com/ccxt/ccxt/pull/21490 'watchOrderBookForSymbols': False, 'watchTradesForSymbols': False, 'watchOHLCVForSymbols': False, 'watchBalance': True, }, 'urls': { 'test': { 'ws': 'wss://testnet-api.phemex.com/ws', }, 'api': { 'ws': 'wss://ws.phemex.com', }, }, 'options': { 'tradesLimit': 1000, 'OHLCVLimit': 1000, }, 'streaming': { 'keepAlive': 9000, }, }) def from_en(self, en, scale): if en is None: return None precise = Precise(en) precise.decimals = self.sum(precise.decimals, scale) precise.reduce() return str(precise) def from_ep(self, ep, market=None): if (ep is None) or (market is None): return ep return self.from_en(ep, self.safe_integer(market, 'priceScale')) def from_ev(self, ev, market=None): if (ev is None) or (market is None): return ev return self.from_en(ev, self.safe_integer(market, 'valueScale')) def from_er(self, er, market=None): if (er is None) or (market is None): return er return self.from_en(er, self.safe_integer(market, 'ratioScale')) def request_id(self): requestId = self.sum(self.safe_integer(self.options, 'requestId', 0), 1) self.options['requestId'] = requestId return requestId def parse_swap_ticker(self, ticker, market=None): # # { # "close": 442800, # "fundingRate": 10000, # "high": 445400, # "indexPrice": 442621, # "low": 428400, # "markPrice": 442659, # "open": 432200, # "openInterest": 744183, # "predFundingRate": 10000, # "symbol": "LTCUSD", # "turnover": 8133238294, # "volume": 934292 # } # marketId = self.safe_string(ticker, 'symbol') market = self.safe_market(marketId, market) symbol = market['symbol'] timestamp = self.safe_integer_product(ticker, 'timestamp', 0.000001) lastString = self.from_ep(self.safe_string(ticker, 'close'), market) last = self.parse_number(lastString) quoteVolume = self.parse_number(self.from_ev(self.safe_string(ticker, 'turnover'), market)) baseVolume = self.parse_number(self.from_ev(self.safe_string(ticker, 'volume'), market)) change = None percentage = None average = None openString = self.omit_zero(self.from_ep(self.safe_string(ticker, 'open'), market)) open = self.parse_number(openString) if (openString is not None) and (lastString is not None): change = self.parse_number(Precise.string_sub(lastString, openString)) average = self.parse_number(Precise.string_div(Precise.string_add(lastString, openString), '2')) percentage = self.parse_number(Precise.string_mul(Precise.string_sub(Precise.string_div(lastString, openString), '1'), '100')) return self.safe_ticker({ 'symbol': symbol, 'timestamp': timestamp, 'datetime': self.iso8601(timestamp), 'high': self.parse_number(self.from_ep(self.safe_string(ticker, 'high'), market)), 'low': self.parse_number(self.from_ep(self.safe_string(ticker, 'low'), market)), 'bid': None, 'bidVolume': None, 'ask': None, 'askVolume': None, 'vwap': None, 'open': open, 'close': last, 'last': last, 'previousClose': None, # previous day close 'change': change, 'percentage': percentage, 'average': average, 'baseVolume': baseVolume, 'quoteVolume': quoteVolume, 'markPrice': self.parse_number(self.from_ep(self.safe_string(ticker, 'markPrice'), market)), 'indexPrice': self.parse_number(self.from_ep(self.safe_string(ticker, 'indexPrice'), market)), 'info': ticker, }) def parse_perpetual_ticker(self, ticker, market=None): # # [ # "STXUSDT", # "0.64649", # "0.8628", # "0.61215", # "0.71737", # "4519387", # "3210827.98166", # "697635", # "0.71720205", # "0.71720205", # "0.0001", # "0.0001", # ] # marketId = self.safe_string(ticker, 0) market = self.safe_market(marketId, market) symbol = market['symbol'] lastString = self.from_ep(self.safe_string(ticker, 4), market) last = self.parse_number(lastString) quoteVolume = self.parse_number(self.from_ev(self.safe_string(ticker, 6), market)) baseVolume = self.parse_number(self.from_ev(self.safe_string(ticker, 5), market)) change = None percentage = None average = None openString = self.omit_zero(self.from_ep(self.safe_string(ticker, 1), market)) open = self.parse_number(openString) if (openString is not None) and (lastString is not None): change = self.parse_number(Precise.string_sub(lastString, openString)) average = self.parse_number(Precise.string_div(Precise.string_add(lastString, openString), '2')) percentage = self.parse_number(Precise.string_mul(Precise.string_sub(Precise.string_div(lastString, openString), '1'), '100')) return self.safe_ticker({ 'symbol': symbol, 'timestamp': None, 'datetime': None, 'high': self.parse_number(self.from_ep(self.safe_string(ticker, 2), market)), 'low': self.parse_number(self.from_ep(self.safe_string(ticker, 3), market)), 'bid': None, 'bidVolume': None, 'ask': None, 'askVolume': None, 'vwap': None, 'open': open, 'close': last, 'last': last, 'previousClose': None, # previous day close 'change': change, 'percentage': percentage, 'average': average, 'baseVolume': baseVolume, 'quoteVolume': quoteVolume, 'info': ticker, }) def handle_ticker(self, client: Client, message): # # { # "spot_market24h": { # "askEp": 958148000000, # "bidEp": 957884000000, # "highEp": 962000000000, # "lastEp": 958220000000, # "lowEp": 928049000000, # "openEp": 935597000000, # "symbol": "sBTCUSDT", # "turnoverEv": 146074214388978, # "volumeEv": 15492228900 # }, # "timestamp": 1592847265888272100 # } # # swap # # { # "market24h": { # "close": 442800, # "fundingRate": 10000, # "high": 445400, # "indexPrice": 442621, # "low": 428400, # "markPrice": 442659, # "open": 432200, # "openInterest": 744183, # "predFundingRate": 10000, # "symbol": "LTCUSD", # "turnover": 8133238294, # "volume": 934292 # }, # "timestamp": 1592845585373374500 # } # # perpetual # # { # "data": [ # [ # "STXUSDT", # "0.64649", # "0.8628", # "0.61215", # "0.71737", # "4519387", # "3210827.98166", # "697635", # "0.71720205", # "0.71720205", # "0.0001", # "0.0001", # ], # ... # ], # "fields": [ # "symbol", # "openRp", # "highRp", # "lowRp", # "lastRp", # "volumeRq", # "turnoverRv", # "openInterestRv", # "indexRp", # "markRp", # "fundingRateRr", # "predFundingRateRr", # ], # "method": "perp_market24h_pack_p.update", # "timestamp": "1677094918686806209", # "type": "snapshot", # } # tickers = [] if 'market24h' in message: ticker = self.safe_value(message, 'market24h') tickers.append(self.parse_swap_ticker(ticker)) elif 'spot_market24h' in message: ticker = self.safe_value(message, 'spot_market24h') tickers.append(self.parse_ticker(ticker)) elif 'data' in message: data = self.safe_value(message, 'data', []) for i in range(0, len(data)): tickers.append(self.parse_perpetual_ticker(data[i])) for i in range(0, len(tickers)): ticker = tickers[i] symbol = ticker['symbol'] messageHash = 'ticker:' + symbol timestamp = self.safe_integer_product(message, 'timestamp', 0.000001) ticker['timestamp'] = timestamp ticker['datetime'] = self.iso8601(timestamp) self.tickers[symbol] = ticker client.resolve(ticker, messageHash) async def watch_balance(self, params={}) -> Balances: """ https://github.com/phemex/phemex-api-docs/blob/master/Public-Hedged-Perpetual-API.md#subscribe-account-order-position-aop https://github.com/phemex/phemex-api-docs/blob/master/Public-Contract-API-en.md#subscribe-account-order-position-aop https://github.com/phemex/phemex-api-docs/blob/master/Public-Spot-API-en.md#subscribe-wallet-order-messages watch balance and get the amount of funds available for trading or funds locked in orders :param dict [params]: extra parameters specific to the exchange API endpoint :param str [params.settle]: set to USDT to use hedged perpetual api :returns dict: a `balance structure ` """ await self.load_markets() type = None type, params = self.handle_market_type_and_params('watchBalance', None, params) usePerpetualApi = self.safe_string(params, 'settle') == 'USDT' messageHash = ':balance' messageHash = 'perpetual' + messageHash if usePerpetualApi else type + messageHash return await self.subscribe_private(type, messageHash, params) def handle_balance(self, type, client, message): # spot # [ # { # "balanceEv": 0, # "currency": "BTC", # "lastUpdateTimeNs": "1650442638722099092", # "lockedTradingBalanceEv": 0, # "lockedWithdrawEv": 0, # "userID": 2647224 # }, # { # "balanceEv": 1154232337, # "currency": "USDT", # "lastUpdateTimeNs": "1650442617610017597", # "lockedTradingBalanceEv": 0, # "lockedWithdrawEv": 0, # "userID": 2647224 # } # ] # swap # [ # { # "accountBalanceEv": 0, # "accountID": 26472240001, # "bonusBalanceEv": 0, # "currency": "BTC", # "totalUsedBalanceEv": 0, # "userID": 2647224 # } # ] # perpetual # [ # { # "accountBalanceRv": "1508.452588802237", # "accountID": 9328670003, # "bonusBalanceRv": "0", # "currency": "USDT", # "totalUsedBalanceRv": "343.132599666883", # "userID": 932867 # } # ] # self.balance['info'] = message for i in range(0, len(message)): balance = message[i] currencyId = self.safe_string(balance, 'currency') code = self.safe_currency_code(currencyId) currency = self.safe_value(self.currencies, code, {}) scale = self.safe_integer(currency, 'valueScale', 8) account = self.account() used = self.safe_string(balance, 'totalUsedBalanceRv') if used is None: usedEv = self.safe_string(balance, 'totalUsedBalanceEv') if usedEv is None: lockedTradingBalanceEv = self.safe_string(balance, 'lockedTradingBalanceEv') lockedWithdrawEv = self.safe_string_2(balance, 'lockedWithdrawEv', 'lockedWithdrawRv') usedEv = Precise.string_add(lockedTradingBalanceEv, lockedWithdrawEv) used = self.from_en(usedEv, scale) total = self.safe_string(balance, 'accountBalanceRv') if total is None: totalEv = self.safe_string_2(balance, 'accountBalanceEv', 'balanceEv') total = self.from_en(totalEv, scale) account['used'] = used account['total'] = total self.balance[code] = account self.balance = self.safe_balance(self.balance) messageHash = type + ':balance' client.resolve(self.balance, messageHash) def handle_trades(self, client: Client, message): # # { # "sequence": 1795484727, # "symbol": "sBTCUSDT", # "trades": [ # [1592891002064516600, "Buy", 964020000000, 1431000], # [1592890978987934500, "Sell", 963704000000, 1401800], # [1592890972918701800, "Buy", 963938000000, 2018600], # ], # "type": "snapshot" # } # perpetual # { # "sequence": 1230197759, # "symbol": "BTCUSDT", # "trades_p": [ # [ # 1677094244729433000, # "Buy", # "23800.4", # "2.455", # ], # ], # "type": "snapshot", # } # name = 'trade' marketId = self.safe_string(message, 'symbol') market = self.safe_market(marketId) symbol = market['symbol'] messageHash = name + ':' + symbol stored = self.safe_value(self.trades, symbol) if stored is None: limit = self.safe_integer(self.options, 'tradesLimit', 1000) stored = ArrayCache(limit) self.trades[symbol] = stored trades = self.safe_value_2(message, 'trades', 'trades_p', []) parsed = self.parse_trades(trades, market) for i in range(0, len(parsed)): stored.append(parsed[i]) client.resolve(stored, messageHash) def handle_ohlcv(self, client: Client, message): # # { # "kline": [ # [1592905200, 60, 960688000000, 960709000000, 960709000000, 960400000000, 960400000000, 848100, 8146756046], # [1592905140, 60, 960718000000, 960716000000, 960717000000, 960560000000, 960688000000, 4284900, 41163743512], # [1592905080, 60, 960513000000, 960684000000, 960718000000, 960684000000, 960718000000, 4880500, 46887494349], # ], # "sequence": 1804401474, # "symbol": "sBTCUSDT", # "type": "snapshot" # } # perpetual # { # "kline_p": [ # [ # 1677094560, # 60, # "23746.2", # "23746.1", # "23757.6", # "23736.9", # "23754.8", # "34.273", # "813910.208", # ], # ], # "sequence": 1230786017, # "symbol": "BTCUSDT", # "type": "incremental", # } # marketId = self.safe_string(message, 'symbol') market = self.safe_market(marketId) symbol = market['symbol'] candles = self.safe_value_2(message, 'kline', 'kline_p', []) first = self.safe_value(candles, 0, []) interval = self.safe_string(first, 1) timeframe = self.find_timeframe(interval) if timeframe is not None: messageHash = 'kline:' + timeframe + ':' + symbol ohlcvs = self.parse_ohlcvs(candles, market) self.ohlcvs[symbol] = self.safe_value(self.ohlcvs, symbol, {}) stored = self.safe_value(self.ohlcvs[symbol], timeframe) if stored is None: limit = self.safe_integer(self.options, 'OHLCVLimit', 1000) stored = ArrayCacheByTimestamp(limit) self.ohlcvs[symbol][timeframe] = stored for i in range(0, len(ohlcvs)): candle = ohlcvs[i] stored.append(candle) client.resolve(stored, messageHash) async def watch_ticker(self, symbol: str, params={}) -> Ticker: """ https://github.com/phemex/phemex-api-docs/blob/master/Public-Hedged-Perpetual-API.md#subscribe-24-hours-ticker https://github.com/phemex/phemex-api-docs/blob/master/Public-Contract-API-en.md#subscribe-24-hours-ticker https://github.com/phemex/phemex-api-docs/blob/master/Public-Spot-API-en.md#subscribe-24-hours-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 ` """ await self.load_markets() market = self.market(symbol) symbol = market['symbol'] isSwap = market['swap'] settleIsUSDT = market['settle'] == 'USDT' name = 'spot_market24h' if isSwap: name = 'perp_market24h_pack_p' if settleIsUSDT else 'market24h' url = self.urls['api']['ws'] requestId = self.request_id() subscriptionHash = name + '.subscribe' messageHash = 'ticker:' + symbol subscribe: dict = { 'method': subscriptionHash, 'id': requestId, 'params': [], } request = self.deep_extend(subscribe, params) return await self.watch(url, messageHash, request, subscriptionHash) async def watch_tickers(self, symbols: Strings = None, params={}) -> Tickers: """ https://github.com/phemex/phemex-api-docs/blob/master/Public-Hedged-Perpetual-API.md#subscribe-24-hours-ticker https://github.com/phemex/phemex-api-docs/blob/master/Public-Contract-API-en.md#subscribe-24-hours-ticker https://github.com/phemex/phemex-api-docs/blob/master/Public-Spot-API-en.md#subscribe-24-hours-ticker 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() symbols = self.market_symbols(symbols, None, False) first = symbols[0] market = self.market(first) isSwap = market['swap'] settleIsUSDT = market['settle'] == 'USDT' name = 'spot_market24h' if isSwap: name = 'perp_market24h_pack_p' if settleIsUSDT else 'market24h' url = self.urls['api']['ws'] requestId = self.request_id() subscriptionHash = name + '.subscribe' messageHashes = [] for i in range(0, len(symbols)): messageHashes.append('ticker:' + symbols[i]) subscribe: dict = { 'method': subscriptionHash, 'id': requestId, 'params': [], } request = self.deep_extend(subscribe, params) ticker = await self.watch_multiple(url, messageHashes, request, messageHashes) 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]: """ https://github.com/phemex/phemex-api-docs/blob/master/Public-Hedged-Perpetual-API.md#subscribe-trade https://github.com/phemex/phemex-api-docs/blob/master/Public-Contract-API-en.md#subscribe-trade https://github.com/phemex/phemex-api-docs/blob/master/Public-Spot-API-en.md#subscribe-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() market = self.market(symbol) symbol = market['symbol'] url = self.urls['api']['ws'] requestId = self.request_id() isSwap = market['swap'] settleIsUSDT = market['settle'] == 'USDT' name = 'trade_p' if (isSwap and settleIsUSDT) else 'trade' messageHash = 'trade:' + symbol method = name + '.subscribe' subscribe: dict = { 'method': method, 'id': requestId, 'params': [ market['id'], ], } request = self.deep_extend(subscribe, params) trades = await self.watch(url, messageHash, request, messageHash) if self.newUpdates: limit = trades.getLimit(symbol, 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: """ https://github.com/phemex/phemex-api-docs/blob/master/Public-Spot-API-en.md#subscribe-orderbook https://github.com/phemex/phemex-api-docs/blob/master/Public-Hedged-Perpetual-API.md#subscribe-orderbook-for-new-model https://github.com/phemex/phemex-api-docs/blob/master/Public-Contract-API-en.md#subscribe-30-levels-orderbook https://github.com/phemex/phemex-api-docs/blob/master/Public-Contract-API-en.md#subscribe-full-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 """ await self.load_markets() market = self.market(symbol) symbol = market['symbol'] url = self.urls['api']['ws'] requestId = self.request_id() isSwap = market['swap'] settleIsUSDT = market['settle'] == 'USDT' name = 'orderbook_p' if (isSwap and settleIsUSDT) else 'orderbook' messageHash = 'orderbook:' + symbol method = name + '.subscribe' subscribe: dict = { 'method': method, 'id': requestId, 'params': [ market['id'], ], } request = self.deep_extend(subscribe, params) orderbook = await self.watch(url, messageHash, request, messageHash) return orderbook.limit() async def watch_ohlcv(self, symbol: str, timeframe: str = '1m', since: Int = None, limit: Int = None, params={}) -> List[list]: """ https://github.com/phemex/phemex-api-docs/blob/master/Public-Hedged-Perpetual-API.md#subscribe-kline https://github.com/phemex/phemex-api-docs/blob/master/Public-Contract-API-en.md#subscribe-kline https://github.com/phemex/phemex-api-docs/blob/master/Public-Spot-API-en.md#subscribe-kline watches historical candlestick data containing the open, high, low, and close price, and the volume of a market :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() market = self.market(symbol) symbol = market['symbol'] url = self.urls['api']['ws'] requestId = self.request_id() isSwap = market['swap'] settleIsUSDT = market['settle'] == 'USDT' name = 'kline_p' if (isSwap and settleIsUSDT) else 'kline' messageHash = 'kline:' + timeframe + ':' + symbol method = name + '.subscribe' subscribe: dict = { 'method': method, 'id': requestId, 'params': [ market['id'], self.safe_integer(self.timeframes, timeframe), ], } request = self.deep_extend(subscribe, params) ohlcv = await self.watch(url, messageHash, request, messageHash) if self.newUpdates: limit = ohlcv.getLimit(symbol, limit) return self.filter_by_since_limit(ohlcv, since, limit, 0, True) def custom_handle_delta(self, bookside, delta, market=None): bidAsk = self.custom_parse_bid_ask(delta, 0, 1, market) bookside.storeArray(bidAsk) def custom_handle_deltas(self, bookside, deltas, market=None): for i in range(0, len(deltas)): self.custom_handle_delta(bookside, deltas[i], market) def handle_order_book(self, client: Client, message): # # { # "book": { # "asks": [ # [960316000000, 6993800], # [960318000000, 13183000], # [960319000000, 9170200], # ], # "bids": [ # [959941000000, 8385300], # [959939000000, 10296600], # [959930000000, 3672400], # ] # }, # "depth": 30, # "sequence": 1805784701, # "symbol": "sBTCUSDT", # "timestamp": 1592908460404461600, # "type": "snapshot" # } # perpetual # { # "depth": 30, # "orderbook_p": { # "asks": [ # [ # "23788.5", # "0.13", # ], # ], # "bids": [ # [ # "23787.8", # "1.836", # ], # ], # }, # "sequence": 1230347368, # "symbol": "BTCUSDT", # "timestamp": "1677093457306978852", # "type": "snapshot", # } # marketId = self.safe_string(message, 'symbol') market = self.safe_market(marketId) symbol = market['symbol'] type = self.safe_string(message, 'type') depth = self.safe_integer(message, 'depth') name = 'orderbook' messageHash = name + ':' + symbol nonce = self.safe_integer(message, 'sequence') timestamp = self.safe_integer_product(message, 'timestamp', 0.000001) if type == 'snapshot': book = self.safe_value_2(message, 'book', 'orderbook_p', {}) snapshot = self.custom_parse_order_book(book, symbol, timestamp, 'bids', 'asks', 0, 1, market) snapshot['nonce'] = nonce orderbook = self.order_book(snapshot, depth) self.orderbooks[symbol] = orderbook client.resolve(orderbook, messageHash) else: if symbol in self.orderbooks: orderbook = self.orderbooks[symbol] changes = self.safe_dict_2(message, 'book', 'orderbook_p', {}) asks = self.safe_list(changes, 'asks', []) bids = self.safe_list(changes, 'bids', []) self.custom_handle_deltas(orderbook['asks'], asks, market) self.custom_handle_deltas(orderbook['bids'], bids, market) orderbook['nonce'] = nonce orderbook['timestamp'] = timestamp orderbook['datetime'] = self.iso8601(timestamp) self.orderbooks[symbol] = orderbook client.resolve(orderbook, messageHash) 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 ` """ await self.load_markets() market = None type = None messageHash = 'trades:' if symbol is not None: market = self.market(symbol) symbol = market['symbol'] messageHash = messageHash + market['symbol'] if market['settle'] == 'USDT': params = self.extend(params) params['settle'] = 'USDT' type, params = self.handle_market_type_and_params('watchMyTrades', market, params) if symbol is None: settle = self.safe_string(params, 'settle') messageHash = (messageHash + 'perpetual') if (settle == 'USDT') else (messageHash + type) trades = await self.subscribe_private(type, messageHash, params) if self.newUpdates: limit = trades.getLimit(symbol, limit) return self.filter_by_symbol_since_limit(trades, symbol, since, limit, True) def handle_my_trades(self, client: Client, message): # # swap # [ # { # "avgPriceEp":4138763000000, # "baseCurrency":"BTC", # "baseQtyEv":0, # "clOrdID":"7956e0be-e8be-93a0-2887-ca504d85cda2", # "execBaseQtyEv":30100, # "execFeeEv":31, # "execID":"d3b10cfa-84e3-5752-828e-78a79617e598", # "execPriceEp":4138763000000, # "execQuoteQtyEv":1245767663, # "feeCurrency":"BTC", # "lastLiquidityInd":"RemovedLiquidity", # "ordType":"Market", # "orderID":"34a4b1a8-ac3a-4580-b3e6-a6d039f27195", # "priceEp":4549022000000, # "qtyType":"ByQuote", # "quoteCurrency":"USDT", # "quoteQtyEv":1248000000, # "side":"Buy", # "symbol":"sBTCUSDT", # "tradeType":"Trade", # "transactTimeNs":"1650442617609928764", # "userID":2647224 # } # ] # perpetual # [ # { # "accountID": 9328670003, # "action": "New", # "actionBy": "ByUser", # "actionTimeNs": 1666858780876924611, # "addedSeq": 77751555, # "apRp": "0", # "bonusChangedAmountRv": "0", # "bpRp": "0", # "clOrdID": "c0327a7d-9064-62a9-28f6-2db9aaaa04e0", # "closedPnlRv": "0", # "closedSize": "0", # "code": 0, # "cumFeeRv": "0", # "cumQty": "0", # "cumValueRv": "0", # "curAccBalanceRv": "1508.489893982237", # "curAssignedPosBalanceRv": "24.62786650928", # "curBonusBalanceRv": "0", # "curLeverageRr": "-10", # "curPosSide": "Buy", # "curPosSize": "0.043", # "curPosTerm": 1, # "curPosValueRv": "894.0689", # "curRiskLimitRv": "1000000", # "currency": "USDT", # "cxlRejReason": 0, # "displayQty": "0.003", # "execFeeRv": "0", # "execID": "00000000-0000-0000-0000-000000000000", # "execPriceRp": "20723.7", # "execQty": "0", # "execSeq": 77751555, # "execStatus": "New", # "execValueRv": "0", # "feeRateRr": "0", # "leavesQty": "0.003", # "leavesValueRv": "63.4503", # "message": "No error", # "ordStatus": "New", # "ordType": "Market", # "orderID": "fa64c6f2-47a4-4929-aab4-b7fa9bbc4323", # "orderQty": "0.003", # "pegOffsetValueRp": "0", # "posSide": "Long", # "priceRp": "21150.1", # "relatedPosTerm": 1, # "relatedReqNum": 11, # "side": "Buy", # "slTrigger": "ByMarkPrice", # "stopLossRp": "0", # "stopPxRp": "0", # "symbol": "BTCUSDT", # "takeProfitRp": "0", # "timeInForce": "ImmediateOrCancel", # "tpTrigger": "ByLastPrice", # "tradeType": "Amend", # "transactTimeNs": 1666858780881545305, # "userID": 932867 # }, # ... # ] # channel = 'trades' tradesLength = len(message) if tradesLength == 0: return cachedTrades = self.myTrades if cachedTrades is None: limit = self.safe_integer(self.options, 'tradesLimit', 1000) cachedTrades = ArrayCacheBySymbolById(limit) marketIds: dict = {} type = None for i in range(0, len(message)): rawTrade = message[i] marketId = self.safe_string(rawTrade, 'symbol') market = self.safe_market(marketId) parsed = self.parse_trade(rawTrade) cachedTrades.append(parsed) symbol = parsed['symbol'] if type is None: type = 'perpetual' if (market['settle'] == 'USDT') else market['type'] marketIds[symbol] = True keys = list(marketIds.keys()) for i in range(0, len(keys)): market = keys[i] hash = channel + ':' + market client.resolve(cachedTrades, hash) # generic subscription messageHash = channel + ':' + type client.resolve(cachedTrades, messageHash) 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 ` """ await self.load_markets() messageHash = 'orders:' market = None type = None if symbol is not None: market = self.market(symbol) symbol = market['symbol'] messageHash = messageHash + market['symbol'] if market['settle'] == 'USDT': params = self.extend(params) params['settle'] = 'USDT' type, params = self.handle_market_type_and_params('watchOrders', market, params) isUSDTSettled = self.safe_string(params, 'settle') == 'USDT' if symbol is None: messageHash = (messageHash + 'perpetual') if (isUSDTSettled) else (messageHash + type) orders = await self.subscribe_private(type, messageHash, params) if self.newUpdates: limit = orders.getLimit(symbol, limit) return self.filter_by_symbol_since_limit(orders, symbol, since, limit, True) def handle_orders(self, client: Client, message): # spot update # { # "closed":[ # { # "action":"New", # "avgPriceEp":4138763000000, # "baseCurrency":"BTC", # "baseQtyEv":0, # "bizError":0, # "clOrdID":"7956e0be-e8be-93a0-2887-ca504d85cda2", # "createTimeNs":"1650442617606017583", # "cumBaseQtyEv":30100, # "cumFeeEv":31, # "cumQuoteQtyEv":1245767663, # "cxlRejReason":0, # "feeCurrency":"BTC", # "leavesBaseQtyEv":0, # "leavesQuoteQtyEv":0, # "ordStatus":"Filled", # "ordType":"Market", # "orderID":"34a4b1a8-ac3a-4580-b3e6-a6d039f27195", # "pegOffsetValueEp":0, # "priceEp":4549022000000, # "qtyType":"ByQuote", # "quoteCurrency":"USDT", # "quoteQtyEv":1248000000, # "side":"Buy", # "stopPxEp":0, # "symbol":"sBTCUSDT", # "timeInForce":"ImmediateOrCancel", # "tradeType":"Trade", # "transactTimeNs":"1650442617609928764", # "triggerTimeNs":0, # "userID":2647224 # } # ], # "fills":[ # { # "avgPriceEp":4138763000000, # "baseCurrency":"BTC", # "baseQtyEv":0, # "clOrdID":"7956e0be-e8be-93a0-2887-ca504d85cda2", # "execBaseQtyEv":30100, # "execFeeEv":31, # "execID":"d3b10cfa-84e3-5752-828e-78a79617e598", # "execPriceEp":4138763000000, # "execQuoteQtyEv":1245767663, # "feeCurrency":"BTC", # "lastLiquidityInd":"RemovedLiquidity", # "ordType":"Market", # "orderID":"34a4b1a8-ac3a-4580-b3e6-a6d039f27195", # "priceEp":4549022000000, # "qtyType":"ByQuote", # "quoteCurrency":"USDT", # "quoteQtyEv":1248000000, # "side":"Buy", # "symbol":"sBTCUSDT", # "tradeType":"Trade", # "transactTimeNs":"1650442617609928764", # "userID":2647224 # } # ], # "open":[ # { # "action":"New", # "avgPriceEp":0, # "baseCurrency":"LTC", # "baseQtyEv":0, # "bizError":0, # "clOrdID":"2c0e5eb5-efb7-60d3-2e5f-df175df412ef", # "createTimeNs":"1650446670073853755", # "cumBaseQtyEv":0, # "cumFeeEv":0, # "cumQuoteQtyEv":0, # "cxlRejReason":0, # "feeCurrency":"LTC", # "leavesBaseQtyEv":0, # "leavesQuoteQtyEv":1000000000, # "ordStatus":"New", # "ordType":"Limit", # "orderID":"d2aad92f-50f5-441a-957b-8184b146e3fb", # "pegOffsetValueEp":0, # "priceEp":5000000000, # "qtyType":"ByQuote", # "quoteCurrency":"USDT", # "quoteQtyEv":1000000000, # "side":"Buy", # } # ] # }, # perpetual # [ # { # "accountID": 40183400003, # "action": "New", # "actionBy": "ByUser", # "actionTimeNs": "1674110665380190869", # "addedSeq": 678760103, # "apRp": "0", # "bonusChangedAmountRv": "0", # "bpRp": "0", # "clOrdID": '', # "cl_req_code": 0, # "closedPnlRv": "0", # "closedSize": "0", # "code": 0, # "cumFeeRv": "0", # "cumQty": "0.001", # "cumValueRv": "20.849", # "curAccBalanceRv": "19.9874906", # "curAssignedPosBalanceRv": "0", # "curBonusBalanceRv": "0", # "curLeverageRr": "-10", # "curPosSide": "Buy", # "curPosSize": "0.001", # "curPosTerm": 1, # "curPosValueRv": "20.849", # "curRiskLimitRv": "1000000", # "currency": "USDT", # "cxlRejReason": 0, # "displayQty": "0.001", # "execFeeRv": "0.0125094", # "execID": "b88d2950-04a2-52d8-8927-346059900242", # "execPriceRp": "20849", # "execQty": "0.001", # "execSeq": 678760103, # "execStatus": "TakerFill", # "execValueRv": "20.849", # "feeRateRr": "0.0006", # "lastLiquidityInd": "RemovedLiquidity", # "leavesQty": "0", # "leavesValueRv": "0", # "message": "No error", # "ordStatus": "Filled", # "ordType": "Market", # "orderID": "79620ed2-54c6-4645-a35c-7057e687c576", # "orderQty": "0.001", # "pegOffsetProportionRr": "0", # "pegOffsetValueRp": "0", # "posSide": "Long", # "priceRp": "21476.3", # "relatedPosTerm": 1, # "relatedReqNum": 4, # "side": "Buy", # "slTrigger": "ByMarkPrice", # "stopLossRp": "0", # "stopPxRp": "0", # "symbol": "BTCUSDT", # "takeProfitRp": "0", # "timeInForce": "ImmediateOrCancel", # "tpTrigger": "ByLastPrice", # "tradeType": "Trade", # "transactTimeNs": "1674110665387882268", # "userID": 4018340 # }, # ... # ] # trades = [] parsedOrders = [] if ('closed' in message) or ('fills' in message) or ('open' in message): closed = self.safe_value(message, 'closed', []) open = self.safe_value(message, 'open', []) orders = self.array_concat(open, closed) ordersLength = len(orders) if ordersLength == 0: return trades = self.safe_value(message, 'fills', []) for i in range(0, len(orders)): rawOrder = orders[i] parsedOrder = self.parse_order(rawOrder) parsedOrders.append(parsedOrder) else: messageLength = len(message) if messageLength == 0: return for i in range(0, len(message)): update = message[i] action = self.safe_string(update, 'action') if (action is not None) and (action != 'Cancel'): # order + trade info together trades.append(update) parsedOrder = self.parse_ws_swap_order(update) parsedOrders.append(parsedOrder) self.handle_my_trades(client, trades) limit = self.safe_integer(self.options, 'ordersLimit', 1000) marketIds: dict = {} if self.orders is None: self.orders = ArrayCacheBySymbolById(limit) type = None stored = self.orders for i in range(0, len(parsedOrders)): parsed = parsedOrders[i] stored.append(parsed) symbol = parsed['symbol'] market = self.market(symbol) if type is None: isUsdt = market['settle'] == 'USDT' type = 'perpetual' if isUsdt else market['type'] marketIds[symbol] = True keys = list(marketIds.keys()) for i in range(0, len(keys)): currentMessageHash = 'orders' + ':' + keys[i] client.resolve(self.orders, currentMessageHash) # resolve generic subscription(spot or swap) messageHash = 'orders:' + type client.resolve(self.orders, messageHash) def parse_ws_swap_order(self, order, market=None): # # swap # { # "accountID":26472240002, # "action":"Cancel", # "actionBy":"ByUser", # "actionTimeNs":"1650450096104760797", # "addedSeq":26975849309, # "bonusChangedAmountEv":0, # "clOrdID":"d9675963-5e4e-6fc8-898a-ec8b934c1c61", # "closedPnlEv":0, # "closedSize":0, # "code":0, # "cumQty":0, # "cumValueEv":0, # "curAccBalanceEv":400079, # "curAssignedPosBalanceEv":0, # "curBonusBalanceEv":0, # "curLeverageEr":0, # "curPosSide":"None", # "curPosSize":0, # "curPosTerm":1, # "curPosValueEv":0, # "curRiskLimitEv":5000000000, # "currency":"USD", # "cxlRejReason":0, # "displayQty":0, # "execFeeEv":0, # "execID":"00000000-0000-0000-0000-000000000000", # "execPriceEp":0, # "execQty":1, # "execSeq":26975862338, # "execStatus":"Canceled", # "execValueEv":0, # "feeRateEr":0, # "leavesQty":0, # "leavesValueEv":0, # "message":"No error", # "ordStatus":"Canceled", # "ordType":"Limit", # "orderID":"8141deb9-8f94-48f6-9421-a4e3a791537b", # "orderQty":1, # "pegOffsetValueEp":0, # "priceEp":9521, # "relatedPosTerm":1, # "relatedReqNum":4, # "side":"Buy", # "slTrigger":"ByMarkPrice", # "stopLossEp":0, # "stopPxEp":0, # "symbol":"ADAUSD", # "takeProfitEp":0, # "timeInForce":"GoodTillCancel", # "tpTrigger":"ByLastPrice", # "transactTimeNs":"1650450096108143014", # "userID":2647224 # } # perpetual # { # "accountID": 40183400003, # "action": "New", # "actionBy": "ByUser", # "actionTimeNs": "1674110665380190869", # "addedSeq": 678760103, # "apRp": "0", # "bonusChangedAmountRv": "0", # "bpRp": "0", # "clOrdID": '', # "cl_req_code": 0, # "closedPnlRv": "0", # "closedSize": "0", # "code": 0, # "cumFeeRv": "0", # "cumQty": "0.001", # "cumValueRv": "20.849", # "curAccBalanceRv": "19.9874906", # "curAssignedPosBalanceRv": "0", # "curBonusBalanceRv": "0", # "curLeverageRr": "-10", # "curPosSide": "Buy", # "curPosSize": "0.001", # "curPosTerm": 1, # "curPosValueRv": "20.849", # "curRiskLimitRv": "1000000", # "currency": "USDT", # "cxlRejReason": 0, # "displayQty": "0.001", # "execFeeRv": "0.0125094", # "execID": "b88d2950-04a2-52d8-8927-346059900242", # "execPriceRp": "20849", # "execQty": "0.001", # "execSeq": 678760103, # "execStatus": "TakerFill", # "execValueRv": "20.849", # "feeRateRr": "0.0006", # "lastLiquidityInd": "RemovedLiquidity", # "leavesQty": "0", # "leavesValueRv": "0", # "message": "No error", # "ordStatus": "Filled", # "ordType": "Market", # "orderID": "79620ed2-54c6-4645-a35c-7057e687c576", # "orderQty": "0.001", # "pegOffsetProportionRr": "0", # "pegOffsetValueRp": "0", # "posSide": "Long", # "priceRp": "21476.3", # "relatedPosTerm": 1, # "relatedReqNum": 4, # "side": "Buy", # "slTrigger": "ByMarkPrice", # "stopLossRp": "0", # "stopPxRp": "0", # "symbol": "BTCUSDT", # "takeProfitRp": "0", # "timeInForce": "ImmediateOrCancel", # "tpTrigger": "ByLastPrice", # "tradeType": "Trade", # "transactTimeNs": "1674110665387882268", # "userID": 4018340 # } # id = self.safe_string(order, 'orderID') clientOrderId = self.safe_string(order, 'clOrdID') if (clientOrderId is not None) and (len(clientOrderId) < 1): clientOrderId = None marketId = self.safe_string(order, 'symbol') market = self.safe_market(marketId, market) symbol = market['symbol'] status = self.parse_order_status(self.safe_string(order, 'ordStatus')) side = self.safe_string_lower(order, 'side') type = self.parseOrderType(self.safe_string(order, 'ordType')) price = self.safe_string(order, 'priceRp', self.from_ep(self.safe_string(order, 'priceEp'), market)) amount = self.safe_string(order, 'orderQty') filled = self.safe_string(order, 'cumQty') remaining = self.safe_string(order, 'leavesQty') timestamp = self.safe_integer_product(order, 'actionTimeNs', 0.000001) cost = self.safe_string(order, 'cumValueRv', self.from_ev(self.safe_string(order, 'cumValueEv'), market)) lastTradeTimestamp = self.safe_integer_product(order, 'transactTimeNs', 0.000001) if lastTradeTimestamp == 0: lastTradeTimestamp = None timeInForce = self.parse_time_in_force(self.safe_string(order, 'timeInForce')) stopPrice = self.safe_string(order, 'stopPx') postOnly = (timeInForce == 'PO') return self.safe_order({ 'info': order, 'id': id, 'clientOrderId': clientOrderId, 'datetime': self.iso8601(timestamp), 'timestamp': timestamp, 'lastTradeTimestamp': lastTradeTimestamp, 'symbol': symbol, 'type': type, 'timeInForce': timeInForce, 'postOnly': postOnly, 'side': side, 'price': price, 'stopPrice': stopPrice, 'triggerPrice': stopPrice, 'amount': amount, 'filled': filled, 'remaining': remaining, 'cost': cost, 'average': None, 'status': status, 'fee': None, 'trades': None, }, market) def handle_message(self, client: Client, message): # private spot update # { # "orders": {closed: [], fills: [], open: []}, # "sequence": 40435835, # "timestamp": "1650443245600839241", # "type": "snapshot", # "wallets": [ # { # "balanceEv": 0, # "currency": "BTC", # "lastUpdateTimeNs": "1650442638722099092", # "lockedTradingBalanceEv": 0, # "lockedWithdrawEv": 0, # "userID": 2647224 # }, # { # "balanceEv": 1154232337, # "currency": "USDT", # "lastUpdateTimeNs": "1650442617610017597", # "lockedTradingBalanceEv": 0, # "lockedWithdrawEv": 0, # "userID": 2647224 # } # ] # } # private swap update # { # "sequence": 83839628, # "timestamp": "1650382581827447829", # "type": "snapshot", # "accounts": [ # { # "accountBalanceEv": 0, # "accountID": 26472240001, # "bonusBalanceEv": 0, # "currency": "BTC", # "totalUsedBalanceEv": 0, # "userID": 2647224 # } # ], # "orders": [], # "positions": [ # { # "accountID": 26472240001, # "assignedPosBalanceEv": 0, # "avgEntryPriceEp": 0, # "bankruptCommEv": 0, # "bankruptPriceEp": 0, # "buyLeavesQty": 0, # "buyLeavesValueEv": 0, # "buyValueToCostEr": 1150750, # "createdAtNs": 0, # "crossSharedBalanceEv": 0, # "cumClosedPnlEv": 0, # "cumFundingFeeEv": 0, # "cumTransactFeeEv": 0, # "curTermRealisedPnlEv": 0, # "currency": "BTC", # "dataVer": 2, # "deleveragePercentileEr": 0, # "displayLeverageEr": 10000000000, # "estimatedOrdLossEv": 0, # "execSeq": 0, # "freeCostEv": 0, # "freeQty": 0, # "initMarginReqEr": 1000000, # "lastFundingTime": "1640601827712091793", # "lastTermEndTime": 0, # "leverageEr": 0, # "liquidationPriceEp": 0, # "maintMarginReqEr": 500000, # "makerFeeRateEr": 0, # "markPriceEp": 507806777, # "orderCostEv": 0, # "posCostEv": 0, # "positionMarginEv": 0, # "positionStatus": "Normal", # "riskLimitEv": 10000000000, # "sellLeavesQty": 0, # "sellLeavesValueEv": 0, # "sellValueToCostEr": 1149250, # "side": "None", # "size": 0, # "symbol": "BTCUSD", # "takerFeeRateEr": 0, # "term": 1, # "transactTimeNs": 0, # "unrealisedPnlEv": 0, # "updatedAtNs": 0, # "usedBalanceEv": 0, # "userID": 2647224, # "valueEv": 0 # } # ] # } id = self.safe_string(message, 'id') if id in client.subscriptions: method = client.subscriptions[id] del client.subscriptions[id] if method is not True: method(client, message) return methodName = self.safe_string(message, 'method', '') if ('market24h' in message) or ('spot_market24h' in message) or (methodName.find('perp_market24h_pack_p') >= 0): self.handle_ticker(client, message) return elif ('trades' in message) or ('trades_p' in message): self.handle_trades(client, message) return elif ('kline' in message) or ('kline_p' in message): self.handle_ohlcv(client, message) return elif ('book' in message) or ('orderbook_p' in message): self.handle_order_book(client, message) return if ('orders' in message) or ('orders_p' in message): orders = self.safe_value_2(message, 'orders', 'orders_p', {}) self.handle_orders(client, orders) if ('accounts' in message) or ('accounts_p' in message) or ('wallets' in message): type = 'swap' if ('accounts' in message) else 'spot' if 'accounts_p' in message: type = 'perpetual' accounts = self.safe_value_n(message, ['accounts', 'accounts_p', 'wallets'], []) self.handle_balance(type, client, accounts) def handle_authenticate(self, client: Client, message): # # { # "error": null, # "id": 1234, # "result": { # "status": "success" # } # } # result = self.safe_value(message, 'result') status = self.safe_string(result, 'status') messageHash = 'authenticated' if status == '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] async def subscribe_private(self, type, messageHash, params={}): await self.load_markets() await self.authenticate() url = self.urls['api']['ws'] requestId = self.seconds() settleIsUSDT = (self.safe_value(params, 'settle', '') == 'USDT') params = self.omit(params, 'settle') channel = 'aop.subscribe' if type == 'spot': channel = 'wo.subscribe' if settleIsUSDT: channel = 'aop_p.subscribe' request = { 'id': requestId, 'method': channel, 'params': [], } request = self.extend(request, params) return await self.watch(url, messageHash, request, channel) async def authenticate(self, params={}): self.check_required_credentials() url = self.urls['api']['ws'] client = self.client(url) requestId = self.request_id() messageHash = 'authenticated' future = self.safe_value(client.subscriptions, messageHash) if future is None: expiryDelta = self.safe_integer(self.options, 'expires', 120) expiration = self.seconds() + expiryDelta payload = self.apiKey + str(expiration) signature = self.hmac(self.encode(payload), self.encode(self.secret), hashlib.sha256) method = 'user.auth' request: dict = { 'method': method, 'params': ['API', self.apiKey, signature, expiration], 'id': requestId, } subscriptionHash = str(requestId) message = self.extend(request, params) if not (messageHash in client.subscriptions): client.subscriptions[subscriptionHash] = self.handle_authenticate future = await self.watch(url, messageHash, message, messageHash) client.subscriptions[messageHash] = future return future