ccxt 4.2.92__py2.py3-none-any.whl → 4.2.94__py2.py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of ccxt might be problematic. Click here for more details.

ccxt/okx.py CHANGED
@@ -6,7 +6,7 @@
6
6
  from ccxt.base.exchange import Exchange
7
7
  from ccxt.abstract.okx import ImplicitAPI
8
8
  import hashlib
9
- from ccxt.base.types import Account, Balances, Currencies, Currency, Greeks, Int, Leverage, MarginModification, Market, MarketInterface, Num, Option, OptionChain, Order, OrderBook, OrderRequest, OrderSide, OrderType, Str, Strings, Ticker, Tickers, Trade, TradingFeeInterface, Transaction, TransferEntry
9
+ from ccxt.base.types import Account, Balances, Conversion, Currencies, Currency, Greeks, Int, Leverage, MarginModification, Market, MarketInterface, Num, Option, OptionChain, Order, OrderBook, OrderRequest, OrderSide, OrderType, Str, Strings, Ticker, Tickers, Trade, TradingFeeInterface, Transaction, TransferEntry
10
10
  from typing import List
11
11
  from ccxt.base.errors import ExchangeError
12
12
  from ccxt.base.errors import PermissionDenied
@@ -82,6 +82,8 @@ class okx(Exchange, ImplicitAPI):
82
82
  'fetchCanceledOrders': True,
83
83
  'fetchClosedOrder': None,
84
84
  'fetchClosedOrders': True,
85
+ 'fetchConvertCurrencies': True,
86
+ 'fetchConvertQuote': True,
85
87
  'fetchCrossBorrowRate': True,
86
88
  'fetchCrossBorrowRates': True,
87
89
  'fetchCurrencies': True,
@@ -7097,6 +7099,152 @@ class okx(Exchange, ImplicitAPI):
7097
7099
  'quoteVolume': None,
7098
7100
  }
7099
7101
 
7102
+ def fetch_convert_quote(self, fromCode: str, toCode: str, amount: Num = None, params={}) -> Conversion:
7103
+ """
7104
+ fetch a quote for converting from one currency to another
7105
+ :see: https://www.okx.com/docs-v5/en/#funding-account-rest-api-estimate-quote
7106
+ :param str fromCode: the currency that you want to sell and convert from
7107
+ :param str toCode: the currency that you want to buy and convert into
7108
+ :param float [amount]: how much you want to trade in units of the from currency
7109
+ :param dict [params]: extra parameters specific to the exchange API endpoint
7110
+ :returns dict: a `conversion structure <https://docs.ccxt.com/#/?id=conversion-structure>`
7111
+ """
7112
+ self.load_markets()
7113
+ request = {
7114
+ 'baseCcy': fromCode.upper(),
7115
+ 'quoteCcy': toCode.upper(),
7116
+ 'rfqSzCcy': fromCode.upper(),
7117
+ 'rfqSz': self.number_to_string(amount),
7118
+ 'side': 'sell',
7119
+ }
7120
+ response = self.privatePostAssetConvertEstimateQuote(self.extend(request, params))
7121
+ #
7122
+ # {
7123
+ # "code": "0",
7124
+ # "data": [
7125
+ # {
7126
+ # "baseCcy": "ETH",
7127
+ # "baseSz": "0.01023052",
7128
+ # "clQReqId": "",
7129
+ # "cnvtPx": "2932.40104429",
7130
+ # "origRfqSz": "30",
7131
+ # "quoteCcy": "USDT",
7132
+ # "quoteId": "quoterETH-USDT16461885104612381",
7133
+ # "quoteSz": "30",
7134
+ # "quoteTime": "1646188510461",
7135
+ # "rfqSz": "30",
7136
+ # "rfqSzCcy": "USDT",
7137
+ # "side": "buy",
7138
+ # "ttlMs": "10000"
7139
+ # }
7140
+ # ],
7141
+ # "msg": ""
7142
+ # }
7143
+ #
7144
+ data = self.safe_list(response, 'data', [])
7145
+ result = self.safe_dict(data, 0, {})
7146
+ fromCurrencyId = self.safe_string(result, 'baseCcy', fromCode)
7147
+ fromCurrency = self.currency(fromCurrencyId)
7148
+ toCurrencyId = self.safe_string(result, 'quoteCcy', toCode)
7149
+ toCurrency = self.currency(toCurrencyId)
7150
+ return self.parse_conversion(result, fromCurrency, toCurrency)
7151
+
7152
+ def parse_conversion(self, conversion, fromCurrency: Currency = None, toCurrency: Currency = None) -> Conversion:
7153
+ #
7154
+ # fetchConvertQuote
7155
+ #
7156
+ # {
7157
+ # "baseCcy": "ETH",
7158
+ # "baseSz": "0.01023052",
7159
+ # "clQReqId": "",
7160
+ # "cnvtPx": "2932.40104429",
7161
+ # "origRfqSz": "30",
7162
+ # "quoteCcy": "USDT",
7163
+ # "quoteId": "quoterETH-USDT16461885104612381",
7164
+ # "quoteSz": "30",
7165
+ # "quoteTime": "1646188510461",
7166
+ # "rfqSz": "30",
7167
+ # "rfqSzCcy": "USDT",
7168
+ # "side": "buy",
7169
+ # "ttlMs": "10000"
7170
+ # }
7171
+ #
7172
+ timestamp = self.safe_integer(conversion, 'quoteTime')
7173
+ fromCoin = self.safe_string(conversion, 'baseCcy')
7174
+ fromCode = self.safe_currency_code(fromCoin, fromCurrency)
7175
+ to = self.safe_string(conversion, 'quoteCcy')
7176
+ toCode = self.safe_currency_code(to, toCurrency)
7177
+ return {
7178
+ 'info': conversion,
7179
+ 'timestamp': timestamp,
7180
+ 'datetime': self.iso8601(timestamp),
7181
+ 'id': self.safe_string(conversion, 'clQReqId'),
7182
+ 'fromCurrency': fromCode,
7183
+ 'fromAmount': self.safe_number(conversion, 'baseSz'),
7184
+ 'toCurrency': toCode,
7185
+ 'toAmount': self.safe_number(conversion, 'quoteSz'),
7186
+ 'price': self.safe_number(conversion, 'cnvtPx'),
7187
+ 'fee': None,
7188
+ }
7189
+
7190
+ def fetch_convert_currencies(self, params={}) -> Currencies:
7191
+ """
7192
+ fetches all available currencies that can be converted
7193
+ :see: https://www.okx.com/docs-v5/en/#funding-account-rest-api-get-convert-currencies
7194
+ :param dict [params]: extra parameters specific to the exchange API endpoint
7195
+ :returns dict: an associative dictionary of currencies
7196
+ """
7197
+ self.load_markets()
7198
+ response = self.privateGetAssetConvertCurrencies(params)
7199
+ #
7200
+ # {
7201
+ # "code": "0",
7202
+ # "data": [
7203
+ # {
7204
+ # "ccy": "BTC",
7205
+ # "max": "",
7206
+ # "min": ""
7207
+ # },
7208
+ # ],
7209
+ # "msg": ""
7210
+ # }
7211
+ #
7212
+ result = {}
7213
+ data = self.safe_list(response, 'data', [])
7214
+ for i in range(0, len(data)):
7215
+ entry = data[i]
7216
+ id = self.safe_string(entry, 'ccy')
7217
+ code = self.safe_currency_code(id)
7218
+ result[code] = {
7219
+ 'info': entry,
7220
+ 'id': id,
7221
+ 'code': code,
7222
+ 'networks': None,
7223
+ 'type': None,
7224
+ 'name': None,
7225
+ 'active': None,
7226
+ 'deposit': None,
7227
+ 'withdraw': None,
7228
+ 'fee': None,
7229
+ 'precision': None,
7230
+ 'limits': {
7231
+ 'amount': {
7232
+ 'min': self.safe_number(entry, 'min'),
7233
+ 'max': self.safe_number(entry, 'max'),
7234
+ },
7235
+ 'withdraw': {
7236
+ 'min': None,
7237
+ 'max': None,
7238
+ },
7239
+ 'deposit': {
7240
+ 'min': None,
7241
+ 'max': None,
7242
+ },
7243
+ },
7244
+ 'created': None,
7245
+ }
7246
+ return result
7247
+
7100
7248
  def handle_errors(self, httpCode, reason, url, method, headers, body, response, requestHeaders, requestBody):
7101
7249
  if not response:
7102
7250
  return None # fallback to default error handler
ccxt/pro/__init__.py CHANGED
@@ -4,7 +4,7 @@
4
4
 
5
5
  # ----------------------------------------------------------------------------
6
6
 
7
- __version__ = '4.2.92'
7
+ __version__ = '4.2.94'
8
8
 
9
9
  # ----------------------------------------------------------------------------
10
10
 
ccxt/pro/kraken.py CHANGED
@@ -5,7 +5,7 @@
5
5
 
6
6
  import ccxt.async_support
7
7
  from ccxt.async_support.base.ws.cache import ArrayCache, ArrayCacheBySymbolById, ArrayCacheByTimestamp
8
- from ccxt.base.types import Int, Num, Order, OrderBook, OrderSide, OrderType, Str, Ticker, Trade
8
+ from ccxt.base.types import Int, Num, Order, OrderBook, OrderSide, OrderType, Str, Strings, Ticker, Tickers, Trade
9
9
  from ccxt.async_support.base.ws.client import Client
10
10
  from typing import List
11
11
  from ccxt.base.errors import ExchangeError
@@ -34,10 +34,12 @@ class kraken(ccxt.async_support.kraken):
34
34
  'watchMyTrades': True,
35
35
  'watchOHLCV': True,
36
36
  'watchOrderBook': True,
37
+ 'watchOrderBookForSymbols': True,
37
38
  'watchOrders': True,
38
39
  'watchTicker': True,
39
- 'watchTickers': False, # for now
40
+ 'watchTickers': True,
40
41
  'watchTrades': True,
42
+ 'watchTradesForSymbols': True,
41
43
  'createOrderWs': True,
42
44
  'editOrderWs': True,
43
45
  'cancelOrderWs': True,
@@ -317,10 +319,9 @@ class kraken(ccxt.async_support.kraken):
317
319
  # ]
318
320
  #
319
321
  wsName = message[3]
320
- name = 'ticker'
321
- messageHash = name + ':' + wsName
322
322
  market = self.safe_value(self.options['marketsByWsName'], wsName)
323
323
  symbol = market['symbol']
324
+ messageHash = self.get_message_hash('ticker', None, symbol)
324
325
  ticker = message[1]
325
326
  vwap = self.safe_string(ticker['p'], 0)
326
327
  quoteVolume = None
@@ -350,9 +351,6 @@ class kraken(ccxt.async_support.kraken):
350
351
  'quoteVolume': quoteVolume,
351
352
  'info': ticker,
352
353
  })
353
- # todo add support for multiple tickers(may be tricky)
354
- # kraken confirms multi-pair subscriptions separately one by one
355
- # trigger correct watchTickers calls upon receiving any of symbols
356
354
  self.tickers[symbol] = result
357
355
  client.resolve(result, messageHash)
358
356
 
@@ -370,9 +368,9 @@ class kraken(ccxt.async_support.kraken):
370
368
  #
371
369
  wsName = self.safe_string(message, 3)
372
370
  name = self.safe_string(message, 2)
373
- messageHash = name + ':' + wsName
374
371
  market = self.safe_value(self.options['marketsByWsName'], wsName)
375
372
  symbol = market['symbol']
373
+ messageHash = self.get_message_hash(name, None, symbol)
376
374
  stored = self.safe_value(self.trades, symbol)
377
375
  if stored is None:
378
376
  limit = self.safe_integer(self.options, 'tradesLimit', 1000)
@@ -467,43 +465,85 @@ class kraken(ccxt.async_support.kraken):
467
465
  :param dict [params]: extra parameters specific to the exchange API endpoint
468
466
  :returns dict: a `ticker structure <https://docs.ccxt.com/#/?id=ticker-structure>`
469
467
  """
470
- return await self.watch_public('ticker', symbol, params)
468
+ await self.load_markets()
469
+ symbol = self.symbol(symbol)
470
+ tickers = await self.watch_tickers([symbol], params)
471
+ return tickers[symbol]
472
+
473
+ async def watch_tickers(self, symbols: Strings = None, params={}) -> Tickers:
474
+ """
475
+ watches a price ticker, a statistical calculation with the information calculated over the past 24 hours for a specific market
476
+ :param str symbol: unified symbol of the market to fetch the ticker for
477
+ :param dict [params]: extra parameters specific to the exchange API endpoint
478
+ :returns dict: a `ticker structure <https://docs.ccxt.com/#/?id=ticker-structure>`
479
+ """
480
+ await self.load_markets()
481
+ symbols = self.market_symbols(symbols, None, False)
482
+ ticker = await self.watch_multi_helper('ticker', 'ticker', symbols, None, params)
483
+ if self.newUpdates:
484
+ result = {}
485
+ result[ticker['symbol']] = ticker
486
+ return result
487
+ return self.filter_by_array(self.tickers, 'symbol', symbols)
471
488
 
472
489
  async def watch_trades(self, symbol: str, since: Int = None, limit: Int = None, params={}) -> List[Trade]:
473
490
  """
474
491
  get the list of most recent trades for a particular symbol
492
+ :see: https://docs.kraken.com/websockets/#message-trade
475
493
  :param str symbol: unified symbol of the market to fetch trades for
476
494
  :param int [since]: timestamp in ms of the earliest trade to fetch
477
495
  :param int [limit]: the maximum amount of trades to fetch
478
496
  :param dict [params]: extra parameters specific to the exchange API endpoint
479
497
  :returns dict[]: a list of `trade structures <https://docs.ccxt.com/#/?id=public-trades>`
480
498
  """
481
- await self.load_markets()
482
- symbol = self.symbol(symbol)
483
- name = 'trade'
484
- trades = await self.watch_public(name, symbol, params)
499
+ return await self.watch_trades_for_symbols([symbol], since, limit, params)
500
+
501
+ async def watch_trades_for_symbols(self, symbols: List[str], since: Int = None, limit: Int = None, params={}) -> List[Trade]:
502
+ """
503
+ :see: https://docs.kraken.com/websockets/#message-trade
504
+ get the list of most recent trades for a list of symbols
505
+ :param str[] symbols: unified symbol of the market to fetch trades for
506
+ :param int [since]: timestamp in ms of the earliest trade to fetch
507
+ :param int [limit]: the maximum amount of trades to fetch
508
+ :param dict [params]: extra parameters specific to the exchange API endpoint
509
+ :returns dict[]: a list of `trade structures <https://docs.ccxt.com/#/?id=public-trades>`
510
+ """
511
+ trades = await self.watch_multi_helper('trade', 'trade', symbols, None, params)
485
512
  if self.newUpdates:
486
- limit = trades.getLimit(symbol, limit)
513
+ first = self.safe_list(trades, 0)
514
+ tradeSymbol = self.safe_string(first, 'symbol')
515
+ limit = trades.getLimit(tradeSymbol, limit)
487
516
  return self.filter_by_since_limit(trades, since, limit, 'timestamp', True)
488
517
 
489
518
  async def watch_order_book(self, symbol: str, limit: Int = None, params={}) -> OrderBook:
490
519
  """
491
520
  watches information on open orders with bid(buy) and ask(sell) prices, volumes and other data
521
+ :see: https://docs.kraken.com/websockets/#message-book
492
522
  :param str symbol: unified symbol of the market to fetch the order book for
493
523
  :param int [limit]: the maximum amount of order book entries to return
494
524
  :param dict [params]: extra parameters specific to the exchange API endpoint
495
525
  :returns dict: A dictionary of `order book structures <https://docs.ccxt.com/#/?id=order-book-structure>` indexed by market symbols
496
526
  """
497
- name = 'book'
527
+ return await self.watch_order_book_for_symbols([symbol], limit, params)
528
+
529
+ async def watch_order_book_for_symbols(self, symbols: List[str], limit: Int = None, params={}) -> OrderBook:
530
+ """
531
+ watches information on open orders with bid(buy) and ask(sell) prices, volumes and other data
532
+ :see: https://docs.kraken.com/websockets/#message-book
533
+ :param str[] symbols: unified array of symbols
534
+ :param int [limit]: the maximum amount of order book entries to return
535
+ :param dict [params]: extra parameters specific to the exchange API endpoint
536
+ :returns dict: A dictionary of `order book structures <https://docs.ccxt.com/#/?id=order-book-structure>` indexed by market symbols
537
+ """
498
538
  request = {}
499
539
  if limit is not None:
500
- if (limit == 10) or (limit == 25) or (limit == 100) or (limit == 500) or (limit == 1000):
540
+ if self.in_array(limit, [10, 25, 100, 500, 1000]):
501
541
  request['subscription'] = {
502
542
  'depth': limit, # default 10, valid options 10, 25, 100, 500, 1000
503
543
  }
504
544
  else:
505
545
  raise NotSupported(self.id + ' watchOrderBook accepts limit values of 10, 25, 100, 500 and 1000 only')
506
- orderbook = await self.watch_public(name, symbol, self.extend(request, params))
546
+ orderbook = await self.watch_multi_helper('orderbook', 'book', symbols, {'limit': limit}, self.extend(request, params))
507
547
  return orderbook.limit()
508
548
 
509
549
  async def watch_ohlcv(self, symbol: str, timeframe='1m', since: Int = None, limit: Int = None, params={}) -> List[list]:
@@ -625,7 +665,7 @@ class kraken(ccxt.async_support.kraken):
625
665
  market = self.safe_value(self.options['marketsByWsName'], wsName)
626
666
  symbol = market['symbol']
627
667
  timestamp = None
628
- messageHash = 'book:' + wsName
668
+ messageHash = self.get_message_hash('orderbook', None, symbol)
629
669
  # if self is a snapshot
630
670
  if 'as' in message[1]:
631
671
  # todo get depth from marketsByWsName
@@ -695,6 +735,7 @@ class kraken(ccxt.async_support.kraken):
695
735
  if localChecksum != c:
696
736
  error = InvalidNonce(self.id + ' invalid checksum')
697
737
  client.reject(error, messageHash)
738
+ return
698
739
  orderbook['symbol'] = symbol
699
740
  orderbook['timestamp'] = timestamp
700
741
  orderbook['datetime'] = self.iso8601(timestamp)
@@ -1179,6 +1220,43 @@ class kraken(ccxt.async_support.kraken):
1179
1220
  'trades': trades,
1180
1221
  })
1181
1222
 
1223
+ async def watch_multi_helper(self, unifiedName: str, channelName: str, symbols: Strings = None, subscriptionArgs=None, params={}):
1224
+ await self.load_markets()
1225
+ # symbols are required
1226
+ symbols = self.market_symbols(symbols, None, False, True, False)
1227
+ messageHashes = []
1228
+ for i in range(0, len(symbols)):
1229
+ messageHashes.append(self.get_message_hash(unifiedName, None, self.symbol(symbols[i])))
1230
+ # for WS subscriptions, we can't use .market_ids(symbols), instead a custom is field needed
1231
+ markets = self.markets_for_symbols(symbols)
1232
+ wsMarketIds = []
1233
+ for i in range(0, len(markets)):
1234
+ wsMarketId = self.safe_string(markets[i]['info'], 'wsname')
1235
+ wsMarketIds.append(wsMarketId)
1236
+ request = {
1237
+ 'event': 'subscribe',
1238
+ 'reqid': self.request_id(),
1239
+ 'pair': wsMarketIds,
1240
+ 'subscription': {
1241
+ 'name': channelName,
1242
+ },
1243
+ }
1244
+ url = self.urls['api']['ws']['public']
1245
+ return await self.watch_multiple(url, messageHashes, self.extend(request, params), messageHashes, subscriptionArgs)
1246
+
1247
+ def get_message_hash(self, unifiedElementName: str, subChannelName: Str = None, symbol: Str = None):
1248
+ # unifiedElementName can be : orderbook, trade, ticker, bidask ...
1249
+ # subChannelName only applies to channel that needs specific variation(i.e. depth_50, depth_100..) to be selected
1250
+ withSymbol = symbol is not None
1251
+ messageHash = unifiedElementName
1252
+ if not withSymbol:
1253
+ messageHash += 's'
1254
+ else:
1255
+ messageHash += '@' + symbol
1256
+ if subChannelName is not None:
1257
+ messageHash += '#' + subChannelName
1258
+ return messageHash
1259
+
1182
1260
  def handle_subscription_status(self, client: Client, message):
1183
1261
  #
1184
1262
  # public
ccxt/pro/krakenfutures.py CHANGED
@@ -32,9 +32,12 @@ class krakenfutures(ccxt.async_support.krakenfutures):
32
32
  'fetchTradesWs': False,
33
33
  'watchOHLCV': False,
34
34
  'watchOrderBook': True,
35
+ 'watchOrderBookForSymbols': True,
35
36
  'watchTicker': True,
36
37
  'watchTickers': True,
38
+ 'watchBidsAsks': True,
37
39
  'watchTrades': True,
40
+ 'watchTradesForSymbols': True,
38
41
  'watchBalance': True,
39
42
  # 'watchStatus': True, # https://docs.futures.kraken.com/#websocket-api-public-feeds-heartbeat
40
43
  'watchOrders': True,
@@ -55,12 +58,6 @@ class krakenfutures(ccxt.async_support.krakenfutures):
55
58
  'OHLCVLimit': 1000,
56
59
  'connectionLimit': 100, # https://docs.futures.kraken.com/#websocket-api-websocket-api-introduction-subscriptions-limits
57
60
  'requestLimit': 100, # per second
58
- 'watchTicker': {
59
- 'method': 'ticker', # or ticker_lite
60
- },
61
- 'watchTickers': {
62
- 'method': 'ticker', # or ticker_lite
63
- },
64
61
  'fetchBalance': {
65
62
  'type': None,
66
63
  },
@@ -96,6 +93,18 @@ class krakenfutures(ccxt.async_support.krakenfutures):
96
93
  client.subscriptions[messageHash] = future
97
94
  return future
98
95
 
96
+ async def watch_order_book_for_symbols(self, symbols: List[str], limit: Int = None, params={}) -> OrderBook:
97
+ """
98
+ watches information on open orders with bid(buy) and ask(sell) prices, volumes and other data
99
+ :see: https://docs.futures.kraken.com/#websocket-api-public-feeds-challenge
100
+ :param str[] symbols: unified array of symbols
101
+ :param int [limit]: the maximum amount of order book entries to return
102
+ :param dict [params]: extra parameters specific to the exchange API endpoint
103
+ :returns dict: A dictionary of `order book structures <https://docs.ccxt.com/#/?id=order-book-structure>` indexed by market symbols
104
+ """
105
+ orderbook = await self.watch_multi_helper('orderbook', 'book', symbols, {'limit': limit}, params)
106
+ return orderbook.limit()
107
+
99
108
  async def subscribe_public(self, name: str, symbols: List[str], params={}):
100
109
  """
101
110
  * @ignore
@@ -156,31 +165,43 @@ class krakenfutures(ccxt.async_support.krakenfutures):
156
165
  :param dict [params]: extra parameters specific to the exchange API endpoint
157
166
  :returns dict: a `ticker structure <https://docs.ccxt.com/#/?id=ticker-structure>`
158
167
  """
159
- options = self.safe_value(self.options, 'watchTicker')
160
- method = self.safe_string(options, 'method', 'ticker') # or ticker_lite
161
- name = self.safe_string(params, 'method', method)
162
- params = self.omit(params, ['method'])
163
- return await self.subscribe_public(name, [symbol], params)
168
+ await self.load_markets()
169
+ symbol = self.symbol(symbol)
170
+ tickers = await self.watch_tickers([symbol], params)
171
+ return tickers[symbol]
164
172
 
165
173
  async def watch_tickers(self, symbols: Strings = None, params={}) -> Tickers:
166
174
  """
167
175
  watches a price ticker, a statistical calculation with the information calculated over the past 24 hours for a specific market
168
- :see: https://docs.futures.kraken.com/#websocket-api-public-feeds-ticker-lite
176
+ :see: https://docs.futures.kraken.com/#websocket-api-public-feeds-ticker
169
177
  :param str symbol: unified symbol of the market to fetch the ticker for
170
178
  :param dict [params]: extra parameters specific to the exchange API endpoint
171
179
  :returns dict: a `ticker structure <https://docs.ccxt.com/#/?id=ticker-structure>`
172
180
  """
173
- method = self.safe_string(self.options, 'watchTickerMethod', 'ticker') # or ticker_lite
174
- name = self.safe_string_2(params, 'method', 'watchTickerMethod', method)
175
- params = self.omit(params, ['watchTickerMethod', 'method'])
181
+ await self.load_markets()
176
182
  symbols = self.market_symbols(symbols, None, False)
177
- ticker = await self.subscribe_public(name, symbols, params)
183
+ ticker = await self.watch_multi_helper('ticker', 'ticker', symbols, None, params)
178
184
  if self.newUpdates:
179
- tickers = {}
180
- tickers[ticker['symbol']] = ticker
181
- return tickers
185
+ result = {}
186
+ result[ticker['symbol']] = ticker
187
+ return result
182
188
  return self.filter_by_array(self.tickers, 'symbol', symbols)
183
189
 
190
+ async def watch_bids_asks(self, symbols: Strings = None, params={}) -> Tickers:
191
+ """
192
+ :see: https://docs.futures.kraken.com/#websocket-api-public-feeds-ticker-lite
193
+ watches best bid & ask for symbols
194
+ :param str[] symbols: unified symbol of the market to fetch the ticker for
195
+ :param dict [params]: extra parameters specific to the exchange API endpoint
196
+ :returns dict: a `ticker structure <https://docs.ccxt.com/#/?id=ticker-structure>`
197
+ """
198
+ ticker = await self.watch_multi_helper('bidask', 'ticker_lite', symbols, None, params)
199
+ if self.newUpdates:
200
+ result = {}
201
+ result[ticker['symbol']] = ticker
202
+ return result
203
+ return self.filter_by_array(self.bidsasks, 'symbol', symbols)
204
+
184
205
  async def watch_trades(self, symbol: Str = None, since: Int = None, limit: Int = None, params={}) -> List[Trade]:
185
206
  """
186
207
  get the list of most recent trades for a particular symbol
@@ -191,11 +212,23 @@ class krakenfutures(ccxt.async_support.krakenfutures):
191
212
  :param dict [params]: extra parameters specific to the exchange API endpoint
192
213
  :returns dict[]: a list of `trade structures <https://docs.ccxt.com/#/?id=public-trades>`
193
214
  """
194
- await self.load_markets()
195
- name = 'trade'
196
- trades = await self.subscribe_public(name, [symbol], params)
215
+ return await self.watch_trades_for_symbols([symbol], since, limit, params)
216
+
217
+ async def watch_trades_for_symbols(self, symbols: List[str], since: Int = None, limit: Int = None, params={}) -> List[Trade]:
218
+ """
219
+ :see: https://docs.futures.kraken.com/#websocket-api-public-feeds-trade
220
+ get the list of most recent trades for a list of symbols
221
+ :param str[] symbols: unified symbol of the market to fetch trades for
222
+ :param int [since]: timestamp in ms of the earliest trade to fetch
223
+ :param int [limit]: the maximum amount of trades to fetch
224
+ :param dict [params]: extra parameters specific to the exchange API endpoint
225
+ :returns dict[]: a list of `trade structures <https://docs.ccxt.com/#/?id=public-trades>`
226
+ """
227
+ trades = await self.watch_multi_helper('trade', 'trade', symbols, None, params)
197
228
  if self.newUpdates:
198
- limit = trades.getLimit(symbol, limit)
229
+ first = self.safe_list(trades, 0)
230
+ tradeSymbol = self.safe_string(first, 'symbol')
231
+ limit = trades.getLimit(tradeSymbol, limit)
199
232
  return self.filter_by_since_limit(trades, since, limit, 'timestamp', True)
200
233
 
201
234
  async def watch_order_book(self, symbol: str, limit: Int = None, params={}) -> OrderBook:
@@ -207,8 +240,7 @@ class krakenfutures(ccxt.async_support.krakenfutures):
207
240
  :param dict [params]: extra parameters specific to the exchange API endpoint
208
241
  :returns dict: A dictionary of `order book structures <https://docs.ccxt.com/#/?id=order-book-structure>` indexed by market symbols
209
242
  """
210
- orderbook = await self.subscribe_public('book', [symbol], params)
211
- return orderbook.limit()
243
+ return await self.watch_order_book_for_symbols([symbol], limit, params)
212
244
 
213
245
  async def watch_positions(self, symbols: Strings = None, since: Int = None, limit: Int = None, params={}) -> List[Position]:
214
246
  """
@@ -436,7 +468,7 @@ class krakenfutures(ccxt.async_support.krakenfutures):
436
468
  if marketId is not None:
437
469
  market = self.market(marketId)
438
470
  symbol = market['symbol']
439
- messageHash = 'trade:' + symbol
471
+ messageHash = self.get_message_hash('trade', None, symbol)
440
472
  if self.safe_list(self.trades, symbol) is None:
441
473
  tradesLimit = self.safe_integer(self.options, 'tradesLimit', 1000)
442
474
  self.trades[symbol] = ArrayCache(tradesLimit)
@@ -453,7 +485,6 @@ class krakenfutures(ccxt.async_support.krakenfutures):
453
485
  trade = self.parse_ws_trade(message)
454
486
  tradesArray.append(trade)
455
487
  client.resolve(tradesArray, messageHash)
456
- return message
457
488
 
458
489
  def parse_ws_trade(self, trade, market=None):
459
490
  #
@@ -859,7 +890,15 @@ class krakenfutures(ccxt.async_support.krakenfutures):
859
890
  # "volumeQuote": 19628180
860
891
  # }
861
892
  #
862
- # ticker_lite
893
+ marketId = self.safe_string(message, 'product_id')
894
+ if marketId is not None:
895
+ ticker = self.parse_ws_ticker(message)
896
+ symbol = ticker['symbol']
897
+ self.tickers[symbol] = ticker
898
+ messageHash = self.get_message_hash('ticker', None, symbol)
899
+ client.resolve(ticker, messageHash)
900
+
901
+ def handle_bid_ask(self, client: Client, message):
863
902
  #
864
903
  # {
865
904
  # "feed": "ticker_lite",
@@ -877,15 +916,12 @@ class krakenfutures(ccxt.async_support.krakenfutures):
877
916
  # }
878
917
  #
879
918
  marketId = self.safe_string(message, 'product_id')
880
- feed = self.safe_string(message, 'feed')
881
919
  if marketId is not None:
882
920
  ticker = self.parse_ws_ticker(message)
883
921
  symbol = ticker['symbol']
884
- self.tickers[symbol] = ticker
885
- messageHash = feed + ':' + symbol
922
+ self.bidsasks[symbol] = ticker
923
+ messageHash = self.get_message_hash('bidask', None, symbol)
886
924
  client.resolve(ticker, messageHash)
887
- client.resolve(self.tickers, feed)
888
- return message
889
925
 
890
926
  def parse_ws_ticker(self, ticker, market=None):
891
927
  #
@@ -997,14 +1033,14 @@ class krakenfutures(ccxt.async_support.krakenfutures):
997
1033
  marketId = self.safe_string(message, 'product_id')
998
1034
  market = self.safe_market(marketId)
999
1035
  symbol = market['symbol']
1000
- messageHash = 'book:' + symbol
1001
- subscription = self.safe_value(client.subscriptions, messageHash, {})
1036
+ messageHash = self.get_message_hash('orderbook', None, symbol)
1037
+ subscription = self.safe_dict(client.subscriptions, messageHash, {})
1002
1038
  limit = self.safe_integer(subscription, 'limit')
1003
1039
  timestamp = self.safe_integer(message, 'timestamp')
1004
1040
  self.orderbooks[symbol] = self.order_book({}, limit)
1005
1041
  orderbook = self.orderbooks[symbol]
1006
- bids = self.safe_value(message, 'bids')
1007
- asks = self.safe_value(message, 'asks')
1042
+ bids = self.safe_list(message, 'bids')
1043
+ asks = self.safe_list(message, 'asks')
1008
1044
  for i in range(0, len(bids)):
1009
1045
  bid = bids[i]
1010
1046
  price = self.safe_number(bid, 'price')
@@ -1037,7 +1073,7 @@ class krakenfutures(ccxt.async_support.krakenfutures):
1037
1073
  marketId = self.safe_string(message, 'product_id')
1038
1074
  market = self.safe_market(marketId)
1039
1075
  symbol = market['symbol']
1040
- messageHash = 'book:' + symbol
1076
+ messageHash = self.get_message_hash('orderbook', None, symbol)
1041
1077
  orderbook = self.orderbooks[symbol]
1042
1078
  side = self.safe_string(message, 'side')
1043
1079
  price = self.safe_number(message, 'price')
@@ -1353,6 +1389,35 @@ class krakenfutures(ccxt.async_support.krakenfutures):
1353
1389
  },
1354
1390
  })
1355
1391
 
1392
+ async def watch_multi_helper(self, unifiedName: str, channelName: str, symbols: Strings = None, subscriptionArgs=None, params={}):
1393
+ await self.load_markets()
1394
+ # symbols are required
1395
+ symbols = self.market_symbols(symbols, None, False, True, False)
1396
+ messageHashes = []
1397
+ for i in range(0, len(symbols)):
1398
+ messageHashes.append(self.get_message_hash(unifiedName, None, self.symbol(symbols[i])))
1399
+ marketIds = self.market_ids(symbols)
1400
+ request = {
1401
+ 'event': 'subscribe',
1402
+ 'feed': channelName,
1403
+ 'product_ids': marketIds,
1404
+ }
1405
+ url = self.urls['api']['ws']
1406
+ return await self.watch_multiple(url, messageHashes, self.extend(request, params), messageHashes, subscriptionArgs)
1407
+
1408
+ def get_message_hash(self, unifiedElementName: str, subChannelName: Str = None, symbol: Str = None):
1409
+ # unifiedElementName can be : orderbook, trade, ticker, bidask ...
1410
+ # subChannelName only applies to channel that needs specific variation(i.e. depth_50, depth_100..) to be selected
1411
+ withSymbol = symbol is not None
1412
+ messageHash = unifiedElementName
1413
+ if not withSymbol:
1414
+ messageHash += 's'
1415
+ else:
1416
+ messageHash += ':' + symbol
1417
+ if subChannelName is not None:
1418
+ messageHash += '#' + subChannelName
1419
+ return messageHash
1420
+
1356
1421
  def handle_error_message(self, client: Client, message):
1357
1422
  #
1358
1423
  # {
@@ -1378,10 +1443,10 @@ class krakenfutures(ccxt.async_support.krakenfutures):
1378
1443
  feed = self.safe_string(message, 'feed')
1379
1444
  methods = {
1380
1445
  'ticker': self.handle_ticker,
1446
+ 'ticker_lite': self.handle_bid_ask,
1381
1447
  'trade': self.handle_trade,
1382
1448
  'trade_snapshot': self.handle_trade,
1383
1449
  # 'heartbeat': self.handleStatus,
1384
- 'ticker_lite': self.handle_ticker,
1385
1450
  'book': self.handle_order_book,
1386
1451
  'book_snapshot': self.handle_order_book_snapshot,
1387
1452
  'open_orders_verbose': self.handle_order,