ccxt 4.2.58__py2.py3-none-any.whl → 4.2.60__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.

Files changed (68) hide show
  1. ccxt/__init__.py +1 -1
  2. ccxt/abstract/blofin.py +1 -0
  3. ccxt/abstract/kraken.py +37 -37
  4. ccxt/abstract/wazirx.py +6 -1
  5. ccxt/ascendex.py +10 -12
  6. ccxt/async_support/__init__.py +1 -1
  7. ccxt/async_support/ascendex.py +10 -12
  8. ccxt/async_support/base/exchange.py +1 -1
  9. ccxt/async_support/binance.py +2 -2
  10. ccxt/async_support/bingx.py +40 -4
  11. ccxt/async_support/bitfinex2.py +20 -3
  12. ccxt/async_support/bitget.py +8 -3
  13. ccxt/async_support/bitmart.py +40 -23
  14. ccxt/async_support/bitmex.py +1 -1
  15. ccxt/async_support/blofin.py +53 -3
  16. ccxt/async_support/coinbase.py +21 -12
  17. ccxt/async_support/hitbtc.py +1 -1
  18. ccxt/async_support/htx.py +3 -1
  19. ccxt/async_support/kraken.py +42 -39
  20. ccxt/async_support/kucoinfutures.py +1 -0
  21. ccxt/async_support/lbank.py +1 -1
  22. ccxt/async_support/mexc.py +1 -1
  23. ccxt/async_support/okx.py +1 -1
  24. ccxt/async_support/phemex.py +1 -1
  25. ccxt/async_support/wazirx.py +6 -1
  26. ccxt/async_support/woo.py +148 -76
  27. ccxt/base/exchange.py +3 -1
  28. ccxt/binance.py +2 -2
  29. ccxt/bingx.py +40 -4
  30. ccxt/bitfinex2.py +20 -3
  31. ccxt/bitget.py +8 -3
  32. ccxt/bitmart.py +40 -23
  33. ccxt/bitmex.py +1 -1
  34. ccxt/blofin.py +53 -3
  35. ccxt/coinbase.py +21 -12
  36. ccxt/hitbtc.py +1 -1
  37. ccxt/htx.py +3 -1
  38. ccxt/kraken.py +42 -39
  39. ccxt/kucoinfutures.py +1 -0
  40. ccxt/lbank.py +1 -1
  41. ccxt/mexc.py +1 -1
  42. ccxt/okx.py +1 -1
  43. ccxt/phemex.py +1 -1
  44. ccxt/pro/__init__.py +1 -1
  45. ccxt/pro/binance.py +12 -3
  46. ccxt/pro/bitfinex2.py +1 -1
  47. ccxt/pro/bitget.py +1 -1
  48. ccxt/pro/bitmart.py +44 -78
  49. ccxt/pro/bitvavo.py +1 -1
  50. ccxt/pro/bybit.py +1 -1
  51. ccxt/pro/coinex.py +1 -1
  52. ccxt/pro/cryptocom.py +1 -1
  53. ccxt/pro/deribit.py +188 -84
  54. ccxt/pro/gate.py +1 -1
  55. ccxt/pro/independentreserve.py +1 -1
  56. ccxt/pro/kraken.py +1 -1
  57. ccxt/pro/kucoinfutures.py +1 -1
  58. ccxt/pro/mexc.py +5 -4
  59. ccxt/pro/okx.py +1 -1
  60. ccxt/pro/woo.py +1 -1
  61. ccxt/test/test_async.py +4 -4
  62. ccxt/test/test_sync.py +4 -4
  63. ccxt/wazirx.py +6 -1
  64. ccxt/woo.py +148 -76
  65. {ccxt-4.2.58.dist-info → ccxt-4.2.60.dist-info}/METADATA +4 -4
  66. {ccxt-4.2.58.dist-info → ccxt-4.2.60.dist-info}/RECORD +68 -68
  67. {ccxt-4.2.58.dist-info → ccxt-4.2.60.dist-info}/WHEEL +0 -0
  68. {ccxt-4.2.58.dist-info → ccxt-4.2.60.dist-info}/top_level.txt +0 -0
ccxt/pro/bitmart.py CHANGED
@@ -114,7 +114,8 @@ class bitmart(ccxt.async_support.bitmart):
114
114
  }
115
115
  return await self.watch(url, messageHash, self.deep_extend(request, params), messageHash)
116
116
 
117
- async def subscribe_multiple(self, channel: str, type: str, symbols: List[str], params={}):
117
+ async def subscribe_multiple(self, channel: str, type: str, symbols: Strings = None, params={}):
118
+ symbols = self.market_symbols(symbols, type, False, True)
118
119
  url = self.implode_hostname(self.urls['api']['ws'][type]['public'])
119
120
  channelType = 'spot' if (type == 'spot') else 'futures'
120
121
  actionType = 'op' if (type == 'spot') else 'action'
@@ -125,6 +126,9 @@ class bitmart(ccxt.async_support.bitmart):
125
126
  message = channelType + '/' + channel + ':' + market['id']
126
127
  rawSubscriptions.append(message)
127
128
  messageHashes.append(channel + ':' + market['symbol'])
129
+ # exclusion, futures "tickers" need one generic request for all symbols
130
+ if (type != 'spot') and (channel == 'ticker'):
131
+ rawSubscriptions = [channelType + '/' + channel]
128
132
  request = {
129
133
  'args': rawSubscriptions,
130
134
  }
@@ -305,12 +309,8 @@ class bitmart(ccxt.async_support.bitmart):
305
309
  """
306
310
  await self.load_markets()
307
311
  symbol = self.symbol(symbol)
308
- market = self.market(symbol)
309
- type = 'spot'
310
- type, params = self.handle_market_type_and_params('watchTicker', market, params)
311
- if type == 'swap':
312
- raise NotSupported(self.id + ' watchTicker() does not support ' + type + ' markets. Use watchTickers() instead')
313
- return await self.subscribe('ticker', symbol, type, params)
312
+ tickers = await self.watch_tickers([symbol], params)
313
+ return tickers[symbol]
314
314
 
315
315
  async def watch_tickers(self, symbols: Strings = None, params={}) -> Tickers:
316
316
  """
@@ -322,35 +322,12 @@ class bitmart(ccxt.async_support.bitmart):
322
322
  """
323
323
  await self.load_markets()
324
324
  market = self.get_market_from_symbols(symbols)
325
- type = 'spot'
326
- type, params = self.handle_market_type_and_params('watchTickers', market, params)
327
- url = self.implode_hostname(self.urls['api']['ws'][type]['public'])
328
- symbols = self.market_symbols(symbols)
329
- messageHash = 'tickers::' + type
330
- if symbols is not None:
331
- messageHash += '::' + ','.join(symbols)
332
- request = None
333
- tickers = None
334
- isSpot = (type == 'spot')
335
- if isSpot:
336
- if symbols is None:
337
- raise ArgumentsRequired(self.id + ' watchTickers() for ' + type + ' market type requires symbols argument to be provided')
338
- marketIds = self.market_ids(symbols)
339
- finalArray = []
340
- for i in range(0, len(marketIds)):
341
- finalArray.append('spot/ticker:' + marketIds[i])
342
- request = {
343
- 'op': 'subscribe',
344
- 'args': finalArray,
345
- }
346
- tickers = await self.watch(url, messageHash, self.deep_extend(request, params), messageHash)
347
- else:
348
- request = {
349
- 'action': 'subscribe',
350
- 'args': ['futures/ticker'],
351
- }
352
- tickers = await self.watch(url, messageHash, self.deep_extend(request, params), messageHash)
325
+ marketType = None
326
+ marketType, params = self.handle_market_type_and_params('watchTickers', market, params)
327
+ ticker = await self.subscribe_multiple('ticker', marketType, symbols, params)
353
328
  if self.newUpdates:
329
+ tickers = {}
330
+ tickers[ticker['symbol']] = ticker
354
331
  return tickers
355
332
  return self.filter_by_array(self.tickers, 'symbol', symbols)
356
333
 
@@ -799,19 +776,29 @@ class bitmart(ccxt.async_support.bitmart):
799
776
  data = self.safe_value(message, 'data')
800
777
  if data is None:
801
778
  return
802
- stored = None
803
779
  symbol = None
804
- for i in range(0, len(data)):
805
- trade = self.parse_ws_trade(data[i])
806
- symbol = trade['symbol']
807
- tradesLimit = self.safe_integer(self.options, 'tradesLimit', 1000)
808
- stored = self.safe_value(self.trades, symbol)
809
- if stored is None:
810
- stored = ArrayCache(tradesLimit)
811
- self.trades[symbol] = stored
812
- stored.append(trade)
813
- messageHash = 'trade:' + symbol
814
- client.resolve(stored, messageHash)
780
+ length = len(data)
781
+ isSwap = ('group' in message)
782
+ if isSwap:
783
+ # in swap, chronologically decreasing: 1709536849322, 1709536848954,
784
+ maxLen = max(length - 1, 0)
785
+ for i in range(maxLen, 0):
786
+ symbol = self.handle_trade_loop(data[i])
787
+ else:
788
+ # in spot, chronologically increasing: 1709536771200, 1709536771226,
789
+ for i in range(0, length):
790
+ symbol = self.handle_trade_loop(data[i])
791
+ client.resolve(self.trades[symbol], 'trade:' + symbol)
792
+
793
+ def handle_trade_loop(self, entry):
794
+ trade = self.parse_ws_trade(entry)
795
+ symbol = trade['symbol']
796
+ tradesLimit = self.safe_integer(self.options, 'tradesLimit', 1000)
797
+ if self.safe_value(self.trades, symbol) is None:
798
+ self.trades[symbol] = ArrayCache(tradesLimit)
799
+ stored = self.trades[symbol]
800
+ stored.append(trade)
801
+ return symbol
815
802
 
816
803
  def parse_ws_trade(self, trade, market: Market = None):
817
804
  # spot
@@ -890,40 +877,19 @@ class bitmart(ccxt.async_support.bitmart):
890
877
  #
891
878
  table = self.safe_string(message, 'table')
892
879
  isSpot = (table is not None)
893
- data = self.safe_value(message, 'data')
894
- if data is None:
895
- return
880
+ rawTickers = []
896
881
  if isSpot:
897
- for i in range(0, len(data)):
898
- ticker = self.parse_ticker(data[i])
899
- symbol = ticker['symbol']
900
- marketId = self.safe_string(ticker['info'], 'symbol')
901
- messageHash = table + ':' + marketId
902
- self.tickers[symbol] = ticker
903
- client.resolve(ticker, messageHash)
904
- self.resolve_message_hashes_for_symbol(client, symbol, ticker, 'tickers::')
882
+ rawTickers = self.safe_list(message, 'data', [])
905
883
  else:
906
- # on each update for contract markets, single ticker is provided
907
- ticker = self.parse_ws_swap_ticker(data)
908
- symbol = self.safe_string(ticker, 'symbol')
884
+ rawTickers = [self.safe_value(message, 'data', {})]
885
+ if not len(rawTickers):
886
+ return
887
+ for i in range(0, len(rawTickers)):
888
+ ticker = self.parse_ticker(rawTickers[i]) if isSpot else self.parse_ws_swap_ticker(rawTickers[i])
889
+ symbol = ticker['symbol']
909
890
  self.tickers[symbol] = ticker
910
- client.resolve(ticker, 'tickers::swap')
911
- self.resolve_message_hashes_for_symbol(client, symbol, ticker, 'tickers::')
912
-
913
- def resolve_message_hashes_for_symbol(self, client, symbol, result, prexif):
914
- prefixSeparator = '::'
915
- symbolsSeparator = ','
916
- messageHashes = self.find_message_hashes(client, prexif)
917
- for i in range(0, len(messageHashes)):
918
- messageHash = messageHashes[i]
919
- parts = messageHash.split(prefixSeparator)
920
- length = len(parts)
921
- symbolsString = parts[length - 1]
922
- symbols = symbolsString.split(symbolsSeparator)
923
- if self.in_array(symbol, symbols):
924
- response = {}
925
- response[symbol] = result
926
- client.resolve(response, messageHash)
891
+ messageHash = 'ticker:' + symbol
892
+ client.resolve(ticker, messageHash)
927
893
 
928
894
  def parse_ws_swap_ticker(self, ticker, market: Market = None):
929
895
  #
ccxt/pro/bitvavo.py CHANGED
@@ -1020,7 +1020,7 @@ class bitvavo(ccxt.async_support.bitvavo):
1020
1020
  return messageHash
1021
1021
 
1022
1022
  def check_message_hash_does_not_exist(self, messageHash):
1023
- supressMultipleWsRequestsError = self.safe_value(self.options, 'supressMultipleWsRequestsError', False)
1023
+ supressMultipleWsRequestsError = self.safe_bool(self.options, 'supressMultipleWsRequestsError', False)
1024
1024
  if not supressMultipleWsRequestsError:
1025
1025
  client = self.safe_value(self.clients, self.urls['api']['ws'])
1026
1026
  if client is not None:
ccxt/pro/bybit.py CHANGED
@@ -885,7 +885,7 @@ class bybit(ccxt.async_support.bybit):
885
885
  self.set_positions_cache(client, symbols)
886
886
  cache = self.positions
887
887
  fetchPositionsSnapshot = self.handle_option('watchPositions', 'fetchPositionsSnapshot', True)
888
- awaitPositionsSnapshot = self.safe_value('watchPositions', 'awaitPositionsSnapshot', True)
888
+ awaitPositionsSnapshot = self.safe_bool('watchPositions', 'awaitPositionsSnapshot', True)
889
889
  if fetchPositionsSnapshot and awaitPositionsSnapshot and cache is None:
890
890
  snapshot = await client.future('fetchPositionsSnapshot')
891
891
  return self.filter_by_symbols_since_limit(snapshot, symbols, since, limit, True)
ccxt/pro/coinex.py CHANGED
@@ -529,7 +529,7 @@ class coinex(ccxt.async_support.coinex):
529
529
  raise NotSupported(self.id + ' watchOHLCV() is only supported for swap markets. Try using fetchOHLCV() instead')
530
530
  url = self.urls['api']['ws'][type]
531
531
  messageHash = 'ohlcv'
532
- watchOHLCVWarning = self.safe_value(self.options, 'watchOHLCVWarning', True)
532
+ watchOHLCVWarning = self.safe_bool(self.options, 'watchOHLCVWarning', True)
533
533
  client = self.safe_value(self.clients, url, {})
534
534
  clientSub = self.safe_value(client, 'subscriptions', {})
535
535
  existingSubscription = self.safe_value(clientSub, messageHash)
ccxt/pro/cryptocom.py CHANGED
@@ -515,7 +515,7 @@ class cryptocom(ccxt.async_support.cryptocom):
515
515
  client = self.client(url)
516
516
  self.set_positions_cache(client, symbols)
517
517
  fetchPositionsSnapshot = self.handle_option('watchPositions', 'fetchPositionsSnapshot', True)
518
- awaitPositionsSnapshot = self.safe_value('watchPositions', 'awaitPositionsSnapshot', True)
518
+ awaitPositionsSnapshot = self.safe_bool('watchPositions', 'awaitPositionsSnapshot', True)
519
519
  if fetchPositionsSnapshot and awaitPositionsSnapshot and self.positions is None:
520
520
  snapshot = await client.future('fetchPositionsSnapshot')
521
521
  return self.filter_by_symbols_since_limit(snapshot, symbols, since, limit, True)
ccxt/pro/deribit.py CHANGED
@@ -10,6 +10,7 @@ from ccxt.base.types import Balances, Int, Order, OrderBook, Str, Ticker, Trade
10
10
  from ccxt.async_support.base.ws.client import Client
11
11
  from typing import List
12
12
  from ccxt.base.errors import ExchangeError
13
+ from ccxt.base.errors import ArgumentsRequired
13
14
  from ccxt.base.errors import NotSupported
14
15
 
15
16
 
@@ -23,10 +24,13 @@ class deribit(ccxt.async_support.deribit):
23
24
  'watchTicker': True,
24
25
  'watchTickers': False,
25
26
  'watchTrades': True,
27
+ 'watchTradesForSymbols': True,
26
28
  'watchMyTrades': True,
27
29
  'watchOrders': True,
28
30
  'watchOrderBook': True,
31
+ 'watchOrderBookForSymbols': True,
29
32
  'watchOHLCV': True,
33
+ 'watchOHLCVForSymbols': True,
30
34
  },
31
35
  'urls': {
32
36
  'test': {
@@ -37,18 +41,31 @@ class deribit(ccxt.async_support.deribit):
37
41
  },
38
42
  },
39
43
  'options': {
40
- 'timeframes': {
41
- '1m': 1,
42
- '3m': 3,
43
- '5m': 5,
44
- '15m': 15,
45
- '30m': 30,
46
- '1h': 60,
47
- '2h': 120,
48
- '4h': 180,
49
- '6h': 360,
50
- '12h': 720,
51
- '1d': '1D',
44
+ 'ws': {
45
+ 'timeframes': {
46
+ '1m': '1',
47
+ '3m': '3',
48
+ '5m': '5',
49
+ '15m': '15',
50
+ '30m': '30',
51
+ '1h': '60',
52
+ '2h': '120',
53
+ '4h': '180',
54
+ '6h': '360',
55
+ '12h': '720',
56
+ '1d': '1D',
57
+ },
58
+ # watchTrades replacement
59
+ 'watchTradesForSymbols': {
60
+ 'interval': '100ms', # 100ms, agg2, raw
61
+ },
62
+ # watchOrderBook replacement
63
+ 'watchOrderBookForSymbols': {
64
+ 'interval': '100ms', # 100ms, agg2, raw
65
+ 'useDepthEndpoint': False, # if True, it will use the {books.group.depth.interval} endpoint instead of the {books.interval} endpoint
66
+ 'depth': '20', # 1, 10, 20
67
+ 'group': 'none', # none, 1, 2, 5, 10, 25, 100, 250
68
+ },
52
69
  },
53
70
  'currencies': ['BTC', 'ETH', 'SOL', 'USDC'],
54
71
  },
@@ -222,26 +239,28 @@ class deribit(ccxt.async_support.deribit):
222
239
  :param str [params.interval]: specify aggregation and frequency of notifications. Possible values: 100ms, raw
223
240
  :returns dict[]: a list of `trade structures <https://docs.ccxt.com/#/?id=public-trades>`
224
241
  """
225
- await self.load_markets()
226
- market = self.market(symbol)
227
- url = self.urls['api']['ws']
228
- interval = self.safe_string(params, 'interval', '100ms')
229
- params = self.omit(params, 'interval')
230
- channel = 'trades.' + market['id'] + '.' + interval
242
+ params['callerMethodName'] = 'watchTrades'
243
+ return await self.watch_trades_for_symbols([symbol], since, limit, params)
244
+
245
+ async def watch_trades_for_symbols(self, symbols: List[str], since: Int = None, limit: Int = None, params={}) -> List[Trade]:
246
+ """
247
+ get the list of most recent trades for a list of symbols
248
+ :see: https://docs.deribit.com/#trades-instrument_name-interval
249
+ :param str[] symbols: unified symbol of the market to fetch trades for
250
+ :param int [since]: timestamp in ms of the earliest trade to fetch
251
+ :param int [limit]: the maximum amount of trades to fetch
252
+ :param dict [params]: extra parameters specific to the exchange API endpoint
253
+ :returns dict[]: a list of `trade structures <https://docs.ccxt.com/#/?id=public-trades>`
254
+ """
255
+ interval = None
256
+ interval, params = self.handle_option_and_params(params, 'watchTradesForSymbols', 'interval', '100ms')
231
257
  if interval == 'raw':
232
258
  await self.authenticate()
233
- message = {
234
- 'jsonrpc': '2.0',
235
- 'method': 'public/subscribe',
236
- 'params': {
237
- 'channels': [channel],
238
- },
239
- 'id': self.request_id(),
240
- }
241
- request = self.deep_extend(message, params)
242
- trades = await self.watch(url, channel, request, channel, request)
259
+ trades = await self.watch_multiple_wrapper('trades', interval, symbols, params)
243
260
  if self.newUpdates:
244
- limit = trades.getLimit(symbol, limit)
261
+ first = self.safe_dict(trades, 0)
262
+ tradeSymbol = self.safe_string(first, 'symbol')
263
+ limit = trades.getLimit(tradeSymbol, limit)
245
264
  return self.filter_by_since_limit(trades, since, limit, 'timestamp', True)
246
265
 
247
266
  def handle_trades(self, client: Client, message):
@@ -266,24 +285,25 @@ class deribit(ccxt.async_support.deribit):
266
285
  # }
267
286
  # }
268
287
  #
269
- params = self.safe_value(message, 'params', {})
288
+ params = self.safe_dict(message, 'params', {})
270
289
  channel = self.safe_string(params, 'channel', '')
271
290
  parts = channel.split('.')
272
291
  marketId = self.safe_string(parts, 1)
292
+ interval = self.safe_string(parts, 2)
273
293
  symbol = self.safe_symbol(marketId)
274
294
  market = self.safe_market(marketId)
275
- trades = self.safe_value(params, 'data', [])
276
- stored = self.safe_value(self.trades, symbol)
277
- if stored is None:
295
+ trades = self.safe_list(params, 'data', [])
296
+ if self.safe_value(self.trades, symbol) is None:
278
297
  limit = self.safe_integer(self.options, 'tradesLimit', 1000)
279
- stored = ArrayCache(limit)
280
- self.trades[symbol] = stored
298
+ self.trades[symbol] = ArrayCache(limit)
299
+ stored = self.trades[symbol]
281
300
  for i in range(0, len(trades)):
282
301
  trade = trades[i]
283
302
  parsed = self.parse_trade(trade, market)
284
303
  stored.append(parsed)
285
304
  self.trades[symbol] = stored
286
- client.resolve(self.trades[symbol], channel)
305
+ messageHash = 'trades|' + symbol + '|' + interval
306
+ client.resolve(self.trades[symbol], messageHash)
287
307
 
288
308
  async def watch_my_trades(self, symbol: Str = None, since: Int = None, limit: Int = None, params={}) -> List[Trade]:
289
309
  """
@@ -367,7 +387,7 @@ class deribit(ccxt.async_support.deribit):
367
387
 
368
388
  async def watch_order_book(self, symbol: str, limit: Int = None, params={}) -> OrderBook:
369
389
  """
370
- :see: https://docs.deribit.com/#public-get_book_summary_by_instrument
390
+ :see: https://docs.deribit.com/#book-instrument_name-group-depth-interval
371
391
  watches information on open orders with bid(buy) and ask(sell) prices, volumes and other data
372
392
  :param str symbol: unified symbol of the market to fetch the order book for
373
393
  :param int [limit]: the maximum amount of order book entries to return
@@ -375,24 +395,34 @@ class deribit(ccxt.async_support.deribit):
375
395
  :param str [params.interval]: Frequency of notifications. Events will be aggregated over self interval. Possible values: 100ms, raw
376
396
  :returns dict: A dictionary of `order book structures <https://docs.ccxt.com/#/?id=order-book-structure>` indexed by market symbols
377
397
  """
378
- await self.load_markets()
379
- market = self.market(symbol)
380
- url = self.urls['api']['ws']
381
- interval = self.safe_string(params, 'interval', '100ms')
382
- params = self.omit(params, 'interval')
398
+ params['callerMethodName'] = 'watchOrderBook'
399
+ return await self.watch_order_book_for_symbols([symbol], limit, params)
400
+
401
+ async def watch_order_book_for_symbols(self, symbols: List[str], limit: Int = None, params={}) -> OrderBook:
402
+ """
403
+ watches information on open orders with bid(buy) and ask(sell) prices, volumes and other data
404
+ :see: https://docs.deribit.com/#book-instrument_name-group-depth-interval
405
+ :param str[] symbols: unified array of symbols
406
+ :param int [limit]: the maximum amount of order book entries to return
407
+ :param dict [params]: extra parameters specific to the exchange API endpoint
408
+ :returns dict: A dictionary of `order book structures <https://docs.ccxt.com/#/?id=order-book-structure>` indexed by market symbols
409
+ """
410
+ interval = None
411
+ interval, params = self.handle_option_and_params(params, 'watchOrderBookForSymbols', 'interval', '100ms')
383
412
  if interval == 'raw':
384
413
  await self.authenticate()
385
- channel = 'book.' + market['id'] + '.' + interval
386
- subscribe = {
387
- 'jsonrpc': '2.0',
388
- 'method': 'public/subscribe',
389
- 'params': {
390
- 'channels': [channel],
391
- },
392
- 'id': self.request_id(),
393
- }
394
- request = self.deep_extend(subscribe, params)
395
- orderbook = await self.watch(url, channel, request, channel)
414
+ descriptor = ''
415
+ useDepthEndpoint = None # for more info, see comment in .options
416
+ useDepthEndpoint, params = self.handle_option_and_params(params, 'watchOrderBookForSymbols', 'useDepthEndpoint', False)
417
+ if useDepthEndpoint:
418
+ depth = None
419
+ depth, params = self.handle_option_and_params(params, 'watchOrderBookForSymbols', 'depth', '20')
420
+ group = None
421
+ group, params = self.handle_option_and_params(params, 'watchOrderBookForSymbols', 'group', 'none')
422
+ descriptor = group + '.' + depth + '.' + interval
423
+ else:
424
+ descriptor = interval
425
+ orderbook = await self.watch_multiple_wrapper('book', descriptor, symbols, params)
396
426
  return orderbook.limit()
397
427
 
398
428
  def handle_order_book(self, client: Client, message):
@@ -444,6 +474,18 @@ class deribit(ccxt.async_support.deribit):
444
474
  params = self.safe_value(message, 'params', {})
445
475
  data = self.safe_value(params, 'data', {})
446
476
  channel = self.safe_string(params, 'channel')
477
+ parts = channel.split('.')
478
+ descriptor = ''
479
+ partsLength = len(parts)
480
+ isDetailed = partsLength == 5
481
+ if isDetailed:
482
+ group = self.safe_string(parts, 2)
483
+ depth = self.safe_string(parts, 3)
484
+ interval = self.safe_string(parts, 4)
485
+ descriptor = group + '.' + depth + '.' + interval
486
+ else:
487
+ interval = self.safe_string(parts, 2)
488
+ descriptor = interval
447
489
  marketId = self.safe_string(data, 'instrument_name')
448
490
  symbol = self.safe_symbol(marketId)
449
491
  timestamp = self.safe_integer(data, 'timestamp')
@@ -459,7 +501,8 @@ class deribit(ccxt.async_support.deribit):
459
501
  storedOrderBook['datetime'] = self.iso8601(timestamp)
460
502
  storedOrderBook['symbol'] = symbol
461
503
  self.orderbooks[symbol] = storedOrderBook
462
- client.resolve(storedOrderBook, channel)
504
+ messageHash = 'book|' + symbol + '|' + descriptor
505
+ client.resolve(storedOrderBook, messageHash)
463
506
 
464
507
  def clean_order_book(self, data):
465
508
  bids = self.safe_value(data, 'bids', [])
@@ -584,26 +627,28 @@ class deribit(ccxt.async_support.deribit):
584
627
  :returns int[][]: A list of candles ordered, open, high, low, close, volume
585
628
  """
586
629
  await self.load_markets()
587
- market = self.market(symbol)
588
- url = self.urls['api']['ws']
589
- timeframes = self.safe_value(self.options, 'timeframes', {})
590
- interval = self.safe_string(timeframes, timeframe)
591
- if interval is None:
592
- raise NotSupported(self.id + ' self interval is not supported, please provide one of the supported timeframes')
593
- channel = 'chart.trades.' + market['id'] + '.' + interval
594
- message = {
595
- 'jsonrpc': '2.0',
596
- 'method': 'public/subscribe',
597
- 'params': {
598
- 'channels': [channel],
599
- },
600
- 'id': self.request_id(),
601
- }
602
- request = self.deep_extend(message, params)
603
- ohlcv = await self.watch(url, channel, request, channel, request)
630
+ symbol = self.symbol(symbol)
631
+ ohlcvs = await self.watch_ohlcv_for_symbols([[symbol, timeframe]], since, limit, params)
632
+ return ohlcvs[symbol][timeframe]
633
+
634
+ async def watch_ohlcv_for_symbols(self, symbolsAndTimeframes: List[List[str]], since: Int = None, limit: Int = None, params={}):
635
+ """
636
+ watches historical candlestick data containing the open, high, low, and close price, and the volume of a market
637
+ :see: https://docs.deribit.com/#chart-trades-instrument_name-resolution
638
+ :param str[][] symbolsAndTimeframes: array of arrays containing unified symbols and timeframes to fetch OHLCV data for, example [['BTC/USDT', '1m'], ['LTC/USDT', '5m']]
639
+ :param int [since]: timestamp in ms of the earliest candle to fetch
640
+ :param int [limit]: the maximum amount of candles to fetch
641
+ :param dict [params]: extra parameters specific to the exchange API endpoint
642
+ :returns int[][]: A list of candles ordered, open, high, low, close, volume
643
+ """
644
+ symbolsLength = len(symbolsAndTimeframes)
645
+ if symbolsLength == 0 or not isinstance(symbolsAndTimeframes[0], list):
646
+ raise ArgumentsRequired(self.id + " watchOHLCVForSymbols() requires a an array of symbols and timeframes, like [['BTC/USDT', '1m'], ['LTC/USDT', '5m']]")
647
+ symbol, timeframe, candles = await self.watch_multiple_wrapper('chart.trades', None, symbolsAndTimeframes, params)
604
648
  if self.newUpdates:
605
- limit = ohlcv.getLimit(market['symbol'], limit)
606
- return self.filter_by_since_limit(ohlcv, since, limit, 0, True)
649
+ limit = candles.getLimit(symbol, limit)
650
+ filtered = self.filter_by_since_limit(candles, since, limit, 0, True)
651
+ return self.create_ohlcv_object(symbol, timeframe, filtered)
607
652
 
608
653
  def handle_ohlcv(self, client: Client, message):
609
654
  #
@@ -624,13 +669,43 @@ class deribit(ccxt.async_support.deribit):
624
669
  # }
625
670
  # }
626
671
  #
627
- params = self.safe_value(message, 'params', {})
672
+ params = self.safe_dict(message, 'params', {})
628
673
  channel = self.safe_string(params, 'channel', '')
629
674
  parts = channel.split('.')
630
675
  marketId = self.safe_string(parts, 2)
631
- symbol = self.safe_symbol(marketId)
632
- ohlcv = self.safe_value(params, 'data', {})
633
- parsed = [
676
+ rawTimeframe = self.safe_string(parts, 3)
677
+ market = self.safe_market(marketId)
678
+ symbol = market['symbol']
679
+ wsOptions = self.safe_dict(self.options, 'ws', {})
680
+ timeframes = self.safe_dict(wsOptions, 'timeframes', {})
681
+ unifiedTimeframe = self.find_timeframe(rawTimeframe, timeframes)
682
+ self.ohlcvs[symbol] = self.safe_dict(self.ohlcvs, symbol, {})
683
+ if self.safe_value(self.ohlcvs[symbol], unifiedTimeframe) is None:
684
+ limit = self.safe_integer(self.options, 'OHLCVLimit', 1000)
685
+ self.ohlcvs[symbol][unifiedTimeframe] = ArrayCacheByTimestamp(limit)
686
+ stored = self.ohlcvs[symbol][unifiedTimeframe]
687
+ ohlcv = self.safe_dict(params, 'data', {})
688
+ # data contains a single OHLCV candle
689
+ parsed = self.parse_ws_ohlcv(ohlcv, market)
690
+ stored.append(parsed)
691
+ self.ohlcvs[symbol][unifiedTimeframe] = stored
692
+ resolveData = [symbol, unifiedTimeframe, stored]
693
+ messageHash = 'chart.trades|' + symbol + '|' + rawTimeframe
694
+ client.resolve(resolveData, messageHash)
695
+
696
+ def parse_ws_ohlcv(self, ohlcv, market=None) -> list:
697
+ #
698
+ # {
699
+ # "c": "28909.0",
700
+ # "o": "28915.4",
701
+ # "h": "28915.4",
702
+ # "l": "28896.1",
703
+ # "v": "27.6919",
704
+ # "T": 1696687499999,
705
+ # "t": 1696687440000
706
+ # }
707
+ #
708
+ return [
634
709
  self.safe_integer(ohlcv, 'tick'),
635
710
  self.safe_number(ohlcv, 'open'),
636
711
  self.safe_number(ohlcv, 'high'),
@@ -638,13 +713,42 @@ class deribit(ccxt.async_support.deribit):
638
713
  self.safe_number(ohlcv, 'close'),
639
714
  self.safe_number(ohlcv, 'volume'),
640
715
  ]
641
- stored = self.safe_value(self.ohlcvs, symbol)
642
- if stored is None:
643
- limit = self.safe_integer(self.options, 'OHLCVLimit', 1000)
644
- stored = ArrayCacheByTimestamp(limit)
645
- stored.append(parsed)
646
- self.ohlcvs[symbol] = stored
647
- client.resolve(stored, channel)
716
+
717
+ async def watch_multiple_wrapper(self, channelName: str, channelDescriptor: Str, symbolsArray=None, params={}):
718
+ await self.load_markets()
719
+ url = self.urls['api']['ws']
720
+ rawSubscriptions = []
721
+ messageHashes = []
722
+ isOHLCV = (channelName == 'chart.trades')
723
+ symbols = self.get_list_from_object_values(symbolsArray, 0) if isOHLCV else symbolsArray
724
+ self.market_symbols(symbols, None, False)
725
+ for i in range(0, len(symbolsArray)):
726
+ current = symbolsArray[i]
727
+ market = None
728
+ if isOHLCV:
729
+ market = self.market(current[0])
730
+ unifiedTf = current[1]
731
+ rawTf = self.safe_string(self.timeframes, unifiedTf, unifiedTf)
732
+ channelDescriptor = rawTf
733
+ else:
734
+ market = self.market(current)
735
+ message = channelName + '.' + market['id'] + '.' + channelDescriptor
736
+ rawSubscriptions.append(message)
737
+ messageHashes.append(channelName + '|' + market['symbol'] + '|' + channelDescriptor)
738
+ request = {
739
+ 'jsonrpc': '2.0',
740
+ 'method': 'public/subscribe',
741
+ 'params': {
742
+ 'channels': rawSubscriptions,
743
+ },
744
+ 'id': self.request_id(),
745
+ }
746
+ extendedRequest = self.deep_extend(request, params)
747
+ maxMessageByteLimit = 32768 - 1 # 'Message Too Big: limit 32768B'
748
+ jsonedText = self.json(extendedRequest)
749
+ if len(jsonedText) >= maxMessageByteLimit:
750
+ raise ExchangeError(self.id + ' requested subscription length over limit, try to reduce symbols amount')
751
+ return await self.watch_multiple(url, messageHashes, extendedRequest, rawSubscriptions)
648
752
 
649
753
  def handle_message(self, client: Client, message):
650
754
  #
ccxt/pro/gate.py CHANGED
@@ -769,7 +769,7 @@ class gate(ccxt.async_support.gate):
769
769
  client = self.client(url)
770
770
  self.set_positions_cache(client, type, symbols)
771
771
  fetchPositionsSnapshot = self.handle_option('watchPositions', 'fetchPositionsSnapshot', True)
772
- awaitPositionsSnapshot = self.safe_value('watchPositions', 'awaitPositionsSnapshot', True)
772
+ awaitPositionsSnapshot = self.safe_bool('watchPositions', 'awaitPositionsSnapshot', True)
773
773
  cache = self.safe_value(self.positions, type)
774
774
  if fetchPositionsSnapshot and awaitPositionsSnapshot and cache is None:
775
775
  return await client.future(type + ':fetchPositionsSnapshot')
@@ -196,7 +196,7 @@ class independentreserve(ccxt.async_support.independentreserve):
196
196
  self.handle_deltas(orderbook['bids'], bids)
197
197
  orderbook['timestamp'] = timestamp
198
198
  orderbook['datetime'] = self.iso8601(timestamp)
199
- checksum = self.safe_value(self.options, 'checksum', True)
199
+ checksum = self.safe_bool(self.options, 'checksum', True)
200
200
  if checksum and receivedSnapshot:
201
201
  storedAsks = orderbook['asks']
202
202
  storedBids = orderbook['bids']
ccxt/pro/kraken.py CHANGED
@@ -674,7 +674,7 @@ class kraken(ccxt.async_support.kraken):
674
674
  example = self.safe_value(b, 0)
675
675
  # don't remove self line or I will poop on your face
676
676
  orderbook.limit()
677
- checksum = self.safe_value(self.options, 'checksum', True)
677
+ checksum = self.safe_bool(self.options, 'checksum', True)
678
678
  if checksum:
679
679
  priceString = self.safe_string(example, 0)
680
680
  amountString = self.safe_string(example, 1)
ccxt/pro/kucoinfutures.py CHANGED
@@ -231,7 +231,7 @@ class kucoinfutures(ccxt.async_support.kucoinfutures):
231
231
  client = self.client(url)
232
232
  self.set_position_cache(client, symbol)
233
233
  fetchPositionSnapshot = self.handle_option('watchPosition', 'fetchPositionSnapshot', True)
234
- awaitPositionSnapshot = self.safe_value('watchPosition', 'awaitPositionSnapshot', True)
234
+ awaitPositionSnapshot = self.safe_bool('watchPosition', 'awaitPositionSnapshot', True)
235
235
  currentPosition = self.get_current_position(symbol)
236
236
  if fetchPositionSnapshot and awaitPositionSnapshot and currentPosition is None:
237
237
  snapshot = await client.future('fetchPositionSnapshot:' + symbol)