ccxt 4.3.94__py2.py3-none-any.whl → 4.3.96__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.
Files changed (53) hide show
  1. ccxt/__init__.py +1 -5
  2. ccxt/abstract/okx.py +2 -0
  3. ccxt/ascendex.py +8 -6
  4. ccxt/async_support/__init__.py +1 -5
  5. ccxt/async_support/ascendex.py +9 -6
  6. ccxt/async_support/base/exchange.py +1 -4
  7. ccxt/async_support/bingx.py +1 -0
  8. ccxt/async_support/bitfinex.py +4 -2
  9. ccxt/async_support/bitfinex2.py +7 -5
  10. ccxt/async_support/blofin.py +0 -1
  11. ccxt/async_support/btcturk.py +3 -3
  12. ccxt/async_support/bybit.py +7 -2
  13. ccxt/async_support/gate.py +3 -2
  14. ccxt/async_support/gemini.py +3 -2
  15. ccxt/async_support/hyperliquid.py +311 -40
  16. ccxt/async_support/independentreserve.py +5 -3
  17. ccxt/async_support/indodax.py +2 -0
  18. ccxt/async_support/kucoin.py +12 -12
  19. ccxt/async_support/mexc.py +78 -154
  20. ccxt/async_support/okx.py +2 -1
  21. ccxt/async_support/p2b.py +0 -1
  22. ccxt/async_support/tradeogre.py +0 -1
  23. ccxt/base/exchange.py +4 -8
  24. ccxt/bingx.py +1 -0
  25. ccxt/bitfinex.py +3 -2
  26. ccxt/bitfinex2.py +6 -5
  27. ccxt/blofin.py +0 -1
  28. ccxt/btcturk.py +3 -3
  29. ccxt/bybit.py +7 -2
  30. ccxt/gate.py +3 -2
  31. ccxt/gemini.py +3 -2
  32. ccxt/hyperliquid.py +311 -40
  33. ccxt/independentreserve.py +4 -3
  34. ccxt/indodax.py +2 -0
  35. ccxt/kucoin.py +2 -2
  36. ccxt/mexc.py +78 -154
  37. ccxt/okx.py +2 -1
  38. ccxt/p2b.py +0 -1
  39. ccxt/pro/__init__.py +1 -1
  40. ccxt/pro/binance.py +90 -2
  41. ccxt/pro/bybit.py +58 -4
  42. ccxt/pro/cryptocom.py +195 -0
  43. ccxt/pro/okx.py +238 -31
  44. ccxt/test/tests_async.py +3 -0
  45. ccxt/test/tests_sync.py +3 -0
  46. ccxt/tradeogre.py +0 -1
  47. {ccxt-4.3.94.dist-info → ccxt-4.3.96.dist-info}/METADATA +5 -5
  48. {ccxt-4.3.94.dist-info → ccxt-4.3.96.dist-info}/RECORD +51 -53
  49. ccxt/abstract/bitbay.py +0 -53
  50. ccxt/abstract/hitbtc3.py +0 -115
  51. {ccxt-4.3.94.dist-info → ccxt-4.3.96.dist-info}/LICENSE.txt +0 -0
  52. {ccxt-4.3.94.dist-info → ccxt-4.3.96.dist-info}/WHEEL +0 -0
  53. {ccxt-4.3.94.dist-info → ccxt-4.3.96.dist-info}/top_level.txt +0 -0
ccxt/pro/cryptocom.py CHANGED
@@ -9,10 +9,12 @@ import hashlib
9
9
  from ccxt.base.types import Balances, Int, Num, Order, OrderBook, OrderSide, OrderType, Position, Str, Strings, Ticker, Trade
10
10
  from ccxt.async_support.base.ws.client import Client
11
11
  from typing import List
12
+ from typing import Any
12
13
  from ccxt.base.errors import ExchangeError
13
14
  from ccxt.base.errors import AuthenticationError
14
15
  from ccxt.base.errors import NetworkError
15
16
  from ccxt.base.errors import ChecksumError
17
+ from ccxt.base.errors import UnsubscribeError
16
18
 
17
19
 
18
20
  class cryptocom(ccxt.async_support.cryptocom):
@@ -86,6 +88,18 @@ class cryptocom(ccxt.async_support.cryptocom):
86
88
  """
87
89
  return await self.watch_order_book_for_symbols([symbol], limit, params)
88
90
 
91
+ async def un_watch_order_book(self, symbol: str, params={}) -> Any:
92
+ """
93
+ unWatches information on open orders with bid(buy) and ask(sell) prices, volumes and other data
94
+ :see: https://exchange-docs.crypto.com/exchange/v1/rest-ws/index.html#book-instrument_name
95
+ :param str symbol: unified symbol of the market to fetch the order book for
96
+ :param dict [params]: extra parameters specific to the exchange API endpoint
97
+ :param str [params.bookSubscriptionType]: The subscription type. Allowed values: SNAPSHOT full snapshot. This is the default if not specified. SNAPSHOT_AND_UPDATE delta updates
98
+ :param int [params.bookUpdateFrequency]: Book update interval in ms. Allowed values: 100 for snapshot subscription 10 for delta subscription
99
+ :returns dict: A dictionary of `order book structures <https://docs.ccxt.com/#/?id=order-book-structure>` indexed by market symbols
100
+ """
101
+ return await self.un_watch_order_book_for_symbols([symbol], params)
102
+
89
103
  async def watch_order_book_for_symbols(self, symbols: List[str], limit: Int = None, params={}) -> OrderBook:
90
104
  """
91
105
  watches information on open orders with bid(buy) and ask(sell) prices, volumes and other data
@@ -127,6 +141,47 @@ class cryptocom(ccxt.async_support.cryptocom):
127
141
  orderbook = await self.watch_public_multiple(messageHashes, topics, params)
128
142
  return orderbook.limit()
129
143
 
144
+ async def un_watch_order_book_for_symbols(self, symbols: List[str], params={}) -> OrderBook:
145
+ """
146
+ unWatches information on open orders with bid(buy) and ask(sell) prices, volumes and other data
147
+ :see: https://exchange-docs.crypto.com/exchange/v1/rest-ws/index.html#book-instrument_name
148
+ :param str[] symbols: unified array of symbols
149
+ :param dict [params]: extra parameters specific to the exchange API endpoint
150
+ :param int [params.limit]: orderbook limit, default is 50
151
+ :param str [params.bookSubscriptionType]: The subscription type. Allowed values: SNAPSHOT full snapshot. This is the default if not specified. SNAPSHOT_AND_UPDATE delta updates
152
+ :param int [params.bookUpdateFrequency]: Book update interval in ms. Allowed values: 100 for snapshot subscription 10 for delta subscription
153
+ :returns dict: A dictionary of `order book structures <https://docs.ccxt.com/#/?id=order-book-structure>` indexed by market symbols
154
+ """
155
+ await self.load_markets()
156
+ symbols = self.market_symbols(symbols)
157
+ topics = []
158
+ subMessageHashes = []
159
+ messageHashes = []
160
+ limit = self.safe_integer(params, 'limit', 50)
161
+ topicParams = self.safe_value(params, 'params')
162
+ if topicParams is None:
163
+ params['params'] = {}
164
+ bookSubscriptionType = None
165
+ bookSubscriptionType2 = None
166
+ bookSubscriptionType, params = self.handle_option_and_params(params, 'watchOrderBook', 'bookSubscriptionType', 'SNAPSHOT_AND_UPDATE')
167
+ bookSubscriptionType2, params = self.handle_option_and_params(params, 'watchOrderBookForSymbols', 'bookSubscriptionType', bookSubscriptionType)
168
+ params['params']['bookSubscriptionType'] = bookSubscriptionType2
169
+ bookUpdateFrequency = None
170
+ bookUpdateFrequency2 = None
171
+ bookUpdateFrequency, params = self.handle_option_and_params(params, 'watchOrderBook', 'bookUpdateFrequency')
172
+ bookUpdateFrequency2, params = self.handle_option_and_params(params, 'watchOrderBookForSymbols', 'bookUpdateFrequency', bookUpdateFrequency)
173
+ if bookUpdateFrequency2 is not None:
174
+ params['params']['bookSubscriptionType'] = bookUpdateFrequency2
175
+ for i in range(0, len(symbols)):
176
+ symbol = symbols[i]
177
+ market = self.market(symbol)
178
+ currentTopic = 'book' + '.' + market['id'] + '.' + str(limit)
179
+ messageHash = 'orderbook:' + market['symbol']
180
+ subMessageHashes.append(messageHash)
181
+ messageHashes.append('unsubscribe:' + messageHash)
182
+ topics.append(currentTopic)
183
+ return await self.un_watch_public_multiple('orderbook', symbols, messageHashes, subMessageHashes, topics, params)
184
+
130
185
  def handle_delta(self, bookside, delta):
131
186
  price = self.safe_float(delta, 0)
132
187
  amount = self.safe_float(delta, 1)
@@ -239,6 +294,18 @@ class cryptocom(ccxt.async_support.cryptocom):
239
294
  """
240
295
  return await self.watch_trades_for_symbols([symbol], since, limit, params)
241
296
 
297
+ async def un_watch_trades(self, symbol: str, params={}) -> List[Trade]:
298
+ """
299
+ get the list of most recent trades for a particular symbol
300
+ :see: https://exchange-docs.crypto.com/exchange/v1/rest-ws/index.html#trade-instrument_name
301
+ :param str symbol: unified symbol of the market to fetch trades for
302
+ :param int [since]: timestamp in ms of the earliest trade to fetch
303
+ :param int [limit]: the maximum amount of trades to fetch
304
+ :param dict [params]: extra parameters specific to the exchange API endpoint
305
+ :returns dict[]: a list of `trade structures <https://docs.ccxt.com/#/?id=public-trades>`
306
+ """
307
+ return await self.un_watch_trades_for_symbols([symbol], params)
308
+
242
309
  async def watch_trades_for_symbols(self, symbols: List[str], since: Int = None, limit: Int = None, params={}) -> List[Trade]:
243
310
  """
244
311
  get the list of most recent trades for a particular symbol
@@ -264,6 +331,26 @@ class cryptocom(ccxt.async_support.cryptocom):
264
331
  limit = trades.getLimit(tradeSymbol, limit)
265
332
  return self.filter_by_since_limit(trades, since, limit, 'timestamp', True)
266
333
 
334
+ async def un_watch_trades_for_symbols(self, symbols: List[str], params={}) -> Any:
335
+ """
336
+ get the list of most recent trades for a particular symbol
337
+ :see: https://exchange-docs.crypto.com/exchange/v1/rest-ws/index.html#trade-instrument_name
338
+ :param str symbol: unified symbol of the market to fetch trades for
339
+ :param dict [params]: extra parameters specific to the exchange API endpoint
340
+ :returns dict[]: a list of `trade structures <https://docs.ccxt.com/#/?id=public-trades>`
341
+ """
342
+ await self.load_markets()
343
+ symbols = self.market_symbols(symbols)
344
+ topics = []
345
+ messageHashes = []
346
+ for i in range(0, len(symbols)):
347
+ symbol = symbols[i]
348
+ market = self.market(symbol)
349
+ currentTopic = 'trade' + '.' + market['id']
350
+ messageHashes.append('unsubscribe:trades:' + market['symbol'])
351
+ topics.append(currentTopic)
352
+ return await self.un_watch_public_multiple('trade', symbols, messageHashes, topics, topics, params)
353
+
267
354
  def handle_trades(self, client: Client, message):
268
355
  #
269
356
  # {
@@ -343,6 +430,20 @@ class cryptocom(ccxt.async_support.cryptocom):
343
430
  messageHash = 'ticker' + '.' + market['id']
344
431
  return await self.watch_public(messageHash, params)
345
432
 
433
+ async def un_watch_ticker(self, symbol: str, params={}) -> Any:
434
+ """
435
+ unWatches a price ticker, a statistical calculation with the information calculated over the past 24 hours for a specific market
436
+ :see: https://exchange-docs.crypto.com/exchange/v1/rest-ws/index.html#ticker-instrument_name
437
+ :param str symbol: unified symbol of the market to fetch the ticker for
438
+ :param dict [params]: extra parameters specific to the exchange API endpoint
439
+ :returns dict: a `ticker structure <https://docs.ccxt.com/#/?id=ticker-structure>`
440
+ """
441
+ await self.load_markets()
442
+ market = self.market(symbol)
443
+ subMessageHash = 'ticker' + '.' + market['id']
444
+ messageHash = 'unsubscribe:ticker:' + market['symbol']
445
+ return await self.un_watch_public_multiple('ticker', [market['symbol']], [messageHash], [subMessageHash], [subMessageHash], params)
446
+
346
447
  def handle_ticker(self, client: Client, message):
347
448
  #
348
449
  # {
@@ -398,6 +499,26 @@ class cryptocom(ccxt.async_support.cryptocom):
398
499
  limit = ohlcv.getLimit(symbol, limit)
399
500
  return self.filter_by_since_limit(ohlcv, since, limit, 0, True)
400
501
 
502
+ async def un_watch_ohlcv(self, symbol: str, timeframe='1m', params={}) -> Any:
503
+ """
504
+ unWatches historical candlestick data containing the open, high, low, and close price, and the volume of a market
505
+ :see: https://exchange-docs.crypto.com/exchange/v1/rest-ws/index.html#candlestick-time_frame-instrument_name
506
+ :param str symbol: unified symbol of the market to fetch OHLCV data for
507
+ :param str timeframe: the length of time each candle represents
508
+ :param dict [params]: extra parameters specific to the exchange API endpoint
509
+ :returns int[][]: A list of candles ordered, open, high, low, close, volume
510
+ """
511
+ await self.load_markets()
512
+ market = self.market(symbol)
513
+ symbol = market['symbol']
514
+ interval = self.safe_string(self.timeframes, timeframe, timeframe)
515
+ subMessageHash = 'candlestick' + '.' + interval + '.' + market['id']
516
+ messageHash = 'unsubscribe:ohlcv:' + market['symbol'] + ':' + timeframe
517
+ subExtend = {
518
+ 'symbolsAndTimeframes': [[market['symbol'], timeframe]],
519
+ }
520
+ return await self.un_watch_public_multiple('ohlcv', [market['symbol']], [messageHash], [subMessageHash], [subMessageHash], params, subExtend)
521
+
401
522
  def handle_ohlcv(self, client: Client, message):
402
523
  #
403
524
  # {
@@ -795,6 +916,27 @@ class cryptocom(ccxt.async_support.cryptocom):
795
916
  message = self.deep_extend(request, params)
796
917
  return await self.watch_multiple(url, messageHashes, message, messageHashes)
797
918
 
919
+ async def un_watch_public_multiple(self, topic: str, symbols: List[str], messageHashes: List[str], subMessageHashes: List[str], topics: List[str], params={}, subExtend={}):
920
+ url = self.urls['api']['ws']['public']
921
+ id = self.nonce()
922
+ request: dict = {
923
+ 'method': 'unsubscribe',
924
+ 'params': {
925
+ 'channels': topics,
926
+ },
927
+ 'nonce': id,
928
+ 'id': str(id),
929
+ }
930
+ subscription = {
931
+ 'id': str(id),
932
+ 'topic': topic,
933
+ 'symbols': symbols,
934
+ 'subMessageHashes': subMessageHashes,
935
+ 'messageHashes': messageHashes,
936
+ }
937
+ message = self.deep_extend(request, params)
938
+ return await self.watch_multiple(url, messageHashes, message, messageHashes, self.extend(subscription, subExtend))
939
+
798
940
  async def watch_private_request(self, nonce, params={}):
799
941
  await self.authenticate()
800
942
  url = self.urls['api']['ws']['private']
@@ -904,6 +1046,9 @@ class cryptocom(ccxt.async_support.cryptocom):
904
1046
  # "channel":"ticker",
905
1047
  # "data":[{}]
906
1048
  #
1049
+ # handle unsubscribe
1050
+ # {"id":1725448572836,"method":"unsubscribe","code":0}
1051
+ #
907
1052
  if self.handle_error_message(client, message):
908
1053
  return
909
1054
  method = self.safe_string(message, 'method')
@@ -916,6 +1061,7 @@ class cryptocom(ccxt.async_support.cryptocom):
916
1061
  'private/cancel-all-orders': self.handle_cancel_all_orders,
917
1062
  'private/close-position': self.handle_order,
918
1063
  'subscribe': self.handle_subscribe,
1064
+ 'unsubscribe': self.handle_unsubscribe,
919
1065
  }
920
1066
  callMethod = self.safe_value(methods, method)
921
1067
  if callMethod is not None:
@@ -953,3 +1099,52 @@ class cryptocom(ccxt.async_support.cryptocom):
953
1099
  #
954
1100
  future = self.safe_value(client.futures, 'authenticated')
955
1101
  future.resolve(True)
1102
+
1103
+ def handle_unsubscribe(self, client: Client, message):
1104
+ id = self.safe_string(message, 'id')
1105
+ keys = list(client.subscriptions.keys())
1106
+ for i in range(0, len(keys)):
1107
+ messageHash = keys[i]
1108
+ if not (messageHash in client.subscriptions):
1109
+ continue
1110
+ # the previous iteration can have deleted the messageHash from the subscriptions
1111
+ if messageHash.startswith('unsubscribe'):
1112
+ subscription = client.subscriptions[messageHash]
1113
+ subId = self.safe_string(subscription, 'id')
1114
+ if id != subId:
1115
+ continue
1116
+ messageHashes = self.safe_list(subscription, 'messageHashes', [])
1117
+ subMessageHashes = self.safe_list(subscription, 'subMessageHashes', [])
1118
+ for j in range(0, len(messageHashes)):
1119
+ unsubHash = messageHashes[j]
1120
+ subHash = subMessageHashes[j]
1121
+ if unsubHash in client.subscriptions:
1122
+ del client.subscriptions[unsubHash]
1123
+ if subHash in client.subscriptions:
1124
+ del client.subscriptions[subHash]
1125
+ error = UnsubscribeError(self.id + ' ' + subHash)
1126
+ client.reject(error, subHash)
1127
+ client.resolve(True, unsubHash)
1128
+ self.clean_cache(subscription)
1129
+
1130
+ def clean_cache(self, subscription: dict):
1131
+ topic = self.safe_string(subscription, 'topic')
1132
+ symbols = self.safe_list(subscription, 'symbols', [])
1133
+ symbolsLength = len(symbols)
1134
+ if topic == 'ohlcv':
1135
+ symbolsAndTimeFrames = self.safe_list(subscription, 'symbolsAndTimeframes', [])
1136
+ for i in range(0, len(symbolsAndTimeFrames)):
1137
+ symbolAndTimeFrame = symbolsAndTimeFrames[i]
1138
+ symbol = self.safe_string(symbolAndTimeFrame, 0)
1139
+ timeframe = self.safe_string(symbolAndTimeFrame, 1)
1140
+ if timeframe in self.ohlcvs[symbol]:
1141
+ del self.ohlcvs[symbol][timeframe]
1142
+ elif symbolsLength > 0:
1143
+ for i in range(0, len(symbols)):
1144
+ symbol = symbols[i]
1145
+ if topic == 'trade':
1146
+ del self.trades[symbol]
1147
+ elif topic == 'orderbook':
1148
+ del self.orderbooks[symbol]
1149
+ elif topic == 'ticker':
1150
+ del self.tickers[symbol]
ccxt/pro/okx.py CHANGED
@@ -27,6 +27,7 @@ class okx(ccxt.async_support.okx):
27
27
  'ws': True,
28
28
  'watchTicker': True,
29
29
  'watchTickers': True,
30
+ 'watchBidsAsks': True,
30
31
  'watchOrderBook': True,
31
32
  'watchTrades': True,
32
33
  'watchTradesForSymbols': True,
@@ -135,9 +136,8 @@ class okx(ccxt.async_support.okx):
135
136
  symbols = self.symbols
136
137
  symbols = self.market_symbols(symbols)
137
138
  url = self.get_url(channel, access)
138
- messageHash = channel
139
+ messageHashes = []
139
140
  args = []
140
- messageHash += '::' + ','.join(symbols)
141
141
  for i in range(0, len(symbols)):
142
142
  marketId = self.market_id(symbols[i])
143
143
  arg: dict = {
@@ -145,11 +145,12 @@ class okx(ccxt.async_support.okx):
145
145
  'instId': marketId,
146
146
  }
147
147
  args.append(self.extend(arg, params))
148
+ messageHashes.append(channel + '::' + symbols[i])
148
149
  request: dict = {
149
150
  'op': 'subscribe',
150
151
  'args': args,
151
152
  }
152
- return await self.watch(url, messageHash, request, messageHash)
153
+ return await self.watch_multiple(url, messageHashes, request, messageHashes)
153
154
 
154
155
  async def subscribe(self, access, messageHash, channel, symbol, params={}):
155
156
  await self.load_markets()
@@ -379,6 +380,17 @@ class okx(ccxt.async_support.okx):
379
380
  ticker = await self.watch_tickers([symbol], params)
380
381
  return self.safe_value(ticker, symbol)
381
382
 
383
+ async def un_watch_ticker(self, symbol: str, params={}) -> Any:
384
+ """
385
+ :see: https://www.okx.com/docs-v5/en/#order-book-trading-market-data-ws-tickers-channel
386
+ unWatches a price ticker, a statistical calculation with the information calculated over the past 24 hours for a specific market
387
+ :param str symbol: unified symbol of the market to fetch the ticker for
388
+ :param dict [params]: extra parameters specific to the exchange API endpoint
389
+ :param str [params.channel]: the channel to subscribe to, tickers by default. Can be tickers, sprd-tickers, index-tickers, block-tickers
390
+ :returns dict: a `ticker structure <https://docs.ccxt.com/#/?id=ticker-structure>`
391
+ """
392
+ return await self.un_watch_tickers([symbol], params)
393
+
382
394
  async def watch_tickers(self, symbols: Strings = None, params={}) -> Tickers:
383
395
  """
384
396
  :see: https://www.okx.com/docs-v5/en/#order-book-trading-market-data-ws-tickers-channel
@@ -397,6 +409,37 @@ class okx(ccxt.async_support.okx):
397
409
  return newTickers
398
410
  return self.filter_by_array(self.tickers, 'symbol', symbols)
399
411
 
412
+ async def un_watch_tickers(self, symbols: Strings = None, params={}) -> Any:
413
+ """
414
+ :see: https://www.okx.com/docs-v5/en/#order-book-trading-market-data-ws-tickers-channel
415
+ unWatches a price ticker, a statistical calculation with the information calculated over the past 24 hours for all markets of a specific list
416
+ :param str[] [symbols]: unified symbol of the market to fetch the ticker for
417
+ :param dict [params]: extra parameters specific to the exchange API endpoint
418
+ :param str [params.channel]: the channel to subscribe to, tickers by default. Can be tickers, sprd-tickers, index-tickers, block-tickers
419
+ :returns dict: a `ticker structure <https://docs.ccxt.com/#/?id=ticker-structure>`
420
+ """
421
+ await self.load_markets()
422
+ symbols = self.market_symbols(symbols, None, False)
423
+ channel = None
424
+ channel, params = self.handle_option_and_params(params, 'watchTickers', 'channel', 'tickers')
425
+ topics = []
426
+ messageHashes = []
427
+ for i in range(0, len(symbols)):
428
+ symbol = symbols[i]
429
+ messageHashes.append('unsubscribe:ticker:' + symbol)
430
+ marketId = self.market_id(symbol)
431
+ topic: dict = {
432
+ 'channel': channel,
433
+ 'instId': marketId,
434
+ }
435
+ topics.append(topic)
436
+ request: dict = {
437
+ 'op': 'unsubscribe',
438
+ 'args': topics,
439
+ }
440
+ url = self.get_url(channel, 'public')
441
+ return await self.watch_multiple(url, messageHashes, request, messageHashes)
442
+
400
443
  def handle_ticker(self, client: Client, message):
401
444
  #
402
445
  # {
@@ -423,27 +466,104 @@ class okx(ccxt.async_support.okx):
423
466
  # ]
424
467
  # }
425
468
  #
469
+ self.handle_bid_ask(client, message)
426
470
  arg = self.safe_value(message, 'arg', {})
471
+ marketId = self.safe_string(arg, 'instId')
472
+ market = self.safe_market(marketId, None, '-')
473
+ symbol = market['symbol']
427
474
  channel = self.safe_string(arg, 'channel')
428
475
  data = self.safe_value(message, 'data', [])
429
- newTickers = []
476
+ newTickers: dict = {}
430
477
  for i in range(0, len(data)):
431
478
  ticker = self.parse_ticker(data[i])
432
- symbol = ticker['symbol']
433
479
  self.tickers[symbol] = ticker
434
- newTickers.append(ticker)
435
- messageHashes = self.find_message_hashes(client, channel + '::')
436
- for i in range(0, len(messageHashes)):
437
- messageHash = messageHashes[i]
438
- parts = messageHash.split('::')
439
- symbolsString = parts[1]
440
- symbols = symbolsString.split(',')
441
- tickers = self.filter_by_array(newTickers, 'symbol', symbols)
442
- tickersSymbols = list(tickers.keys())
443
- numTickers = len(tickersSymbols)
444
- if numTickers > 0:
445
- client.resolve(tickers, messageHash)
446
- return message
480
+ newTickers[symbol] = ticker
481
+ messageHash = channel + '::' + symbol
482
+ client.resolve(newTickers, messageHash)
483
+
484
+ async def watch_bids_asks(self, symbols: Strings = None, params={}) -> Tickers:
485
+ """
486
+ :see: https://www.okx.com/docs-v5/en/#order-book-trading-market-data-ws-tickers-channel
487
+ watches best bid & ask for symbols
488
+ :param str[] symbols: unified symbol of the market to fetch the ticker for
489
+ :param dict [params]: extra parameters specific to the exchange API endpoint
490
+ :returns dict: a `ticker structure <https://docs.ccxt.com/#/?id=ticker-structure>`
491
+ """
492
+ await self.load_markets()
493
+ symbols = self.market_symbols(symbols, None, False)
494
+ channel = None
495
+ channel, params = self.handle_option_and_params(params, 'watchBidsAsks', 'channel', 'tickers')
496
+ url = self.get_url(channel, 'public')
497
+ messageHashes = []
498
+ args = []
499
+ for i in range(0, len(symbols)):
500
+ marketId = self.market_id(symbols[i])
501
+ arg: dict = {
502
+ 'channel': channel,
503
+ 'instId': marketId,
504
+ }
505
+ args.append(self.extend(arg, params))
506
+ messageHashes.append('bidask::' + symbols[i])
507
+ request: dict = {
508
+ 'op': 'subscribe',
509
+ 'args': args,
510
+ }
511
+ newTickers = await self.watch_multiple(url, messageHashes, request, messageHashes)
512
+ if self.newUpdates:
513
+ tickers: dict = {}
514
+ tickers[newTickers['symbol']] = newTickers
515
+ return tickers
516
+ return self.filter_by_array(self.bidsasks, 'symbol', symbols)
517
+
518
+ def handle_bid_ask(self, client: Client, message):
519
+ #
520
+ # {
521
+ # "arg": {channel: "tickers", instId: "BTC-USDT"},
522
+ # "data": [
523
+ # {
524
+ # "instType": "SPOT",
525
+ # "instId": "BTC-USDT",
526
+ # "last": "31500.1",
527
+ # "lastSz": "0.00001754",
528
+ # "askPx": "31500.1",
529
+ # "askSz": "0.00998144",
530
+ # "bidPx": "31500",
531
+ # "bidSz": "3.05652439",
532
+ # "open24h": "31697",
533
+ # "high24h": "32248",
534
+ # "low24h": "31165.6",
535
+ # "sodUtc0": "31385.5",
536
+ # "sodUtc8": "32134.9",
537
+ # "volCcy24h": "503403597.38138519",
538
+ # "vol24h": "15937.10781721",
539
+ # "ts": "1626526618762"
540
+ # }
541
+ # ]
542
+ # }
543
+ #
544
+ data = self.safe_list(message, 'data', [])
545
+ ticker = self.safe_dict(data, 0, {})
546
+ parsedTicker = self.parse_ws_bid_ask(ticker)
547
+ symbol = parsedTicker['symbol']
548
+ self.bidsasks[symbol] = parsedTicker
549
+ messageHash = 'bidask::' + symbol
550
+ client.resolve(parsedTicker, messageHash)
551
+
552
+ def parse_ws_bid_ask(self, ticker, market=None):
553
+ marketId = self.safe_string(ticker, 'instId')
554
+ market = self.safe_market(marketId, market)
555
+ symbol = self.safe_string(market, 'symbol')
556
+ timestamp = self.safe_integer(ticker, 'ts')
557
+ return self.safe_ticker({
558
+ 'symbol': symbol,
559
+ 'timestamp': timestamp,
560
+ 'datetime': self.iso8601(timestamp),
561
+ 'ask': self.safe_string(ticker, 'askPx'),
562
+ 'askVolume': self.safe_string(ticker, 'askSz'),
563
+ 'bid': self.safe_string(ticker, 'bidPx'),
564
+ 'bidVolume': self.safe_string(ticker, 'bidSz'),
565
+ 'info': ticker,
566
+ }, market)
447
567
 
448
568
  async def watch_liquidations_for_symbols(self, symbols: List[str] = None, since: Int = None, limit: Int = None, params={}) -> List[Liquidation]:
449
569
  """
@@ -547,10 +667,24 @@ class okx(ccxt.async_support.okx):
547
667
  await self.authenticate({'access': 'business' if isStop else 'private'})
548
668
  symbols = self.market_symbols(symbols, None, True, True)
549
669
  messageHash = 'myLiquidations'
670
+ messageHashes = []
550
671
  if symbols is not None:
551
- messageHash += '::' + ','.join(symbols)
672
+ for i in range(0, len(symbols)):
673
+ symbol = symbols[i]
674
+ messageHashes.append(messageHash + '::' + symbol)
675
+ else:
676
+ messageHashes.append(messageHash)
552
677
  channel = 'balance_and_position'
553
- newLiquidations = await self.subscribe('private', messageHash, channel, None, params)
678
+ request: dict = {
679
+ 'op': 'subscribe',
680
+ 'args': [
681
+ {
682
+ 'channel': channel,
683
+ },
684
+ ],
685
+ }
686
+ url = self.get_url(channel, 'private')
687
+ newLiquidations = await self.watch_multiple(url, messageHashes, self.deep_extend(request, params), messageHashes)
554
688
  if self.newUpdates:
555
689
  return newLiquidations
556
690
  return self.filter_by_symbols_since_limit(self.liquidations, symbols, since, limit, True)
@@ -710,6 +844,17 @@ class okx(ccxt.async_support.okx):
710
844
  limit = ohlcv.getLimit(symbol, limit)
711
845
  return self.filter_by_since_limit(ohlcv, since, limit, 0, True)
712
846
 
847
+ async def un_watch_ohlcv(self, symbol: str, timeframe='1m', params={}) -> Any:
848
+ """
849
+ watches historical candlestick data containing the open, high, low, and close price, and the volume of a market
850
+ :param str symbol: unified symbol of the market to fetch OHLCV data for
851
+ :param str timeframe: the length of time each candle represents
852
+ :param dict [params]: extra parameters specific to the exchange API endpoint
853
+ :returns int[][]: A list of candles ordered, open, high, low, close, volume
854
+ """
855
+ await self.load_markets()
856
+ return await self.un_watch_ohlcv_for_symbols([[symbol, timeframe]], params)
857
+
713
858
  async def watch_ohlcv_for_symbols(self, symbolsAndTimeframes: List[List[str]], since: Int = None, limit: Int = None, params={}):
714
859
  """
715
860
  watches historical candlestick data containing the open, high, low, and close price, and the volume of a market
@@ -749,6 +894,39 @@ class okx(ccxt.async_support.okx):
749
894
  filtered = self.filter_by_since_limit(candles, since, limit, 0, True)
750
895
  return self.create_ohlcv_object(symbol, timeframe, filtered)
751
896
 
897
+ async def un_watch_ohlcv_for_symbols(self, symbolsAndTimeframes: List[List[str]], params={}):
898
+ """
899
+ unWatches historical candlestick data containing the open, high, low, and close price, and the volume of a market
900
+ :param str[][] symbolsAndTimeframes: array of arrays containing unified symbols and timeframes to fetch OHLCV data for, example [['BTC/USDT', '1m'], ['LTC/USDT', '5m']]
901
+ :param dict [params]: extra parameters specific to the exchange API endpoint
902
+ :returns int[][]: A list of candles ordered, open, high, low, close, volume
903
+ """
904
+ symbolsLength = len(symbolsAndTimeframes)
905
+ if symbolsLength == 0 or not isinstance(symbolsAndTimeframes[0], list):
906
+ raise ArgumentsRequired(self.id + " watchOHLCVForSymbols() requires a an array of symbols and timeframes, like [['BTC/USDT', '1m'], ['LTC/USDT', '5m']]")
907
+ await self.load_markets()
908
+ topics = []
909
+ messageHashes = []
910
+ for i in range(0, len(symbolsAndTimeframes)):
911
+ symbolAndTimeframe = symbolsAndTimeframes[i]
912
+ sym = symbolAndTimeframe[0]
913
+ tf = symbolAndTimeframe[1]
914
+ marketId = self.market_id(sym)
915
+ interval = self.safe_string(self.timeframes, tf, tf)
916
+ channel = 'candle' + interval
917
+ topic: dict = {
918
+ 'channel': channel,
919
+ 'instId': marketId,
920
+ }
921
+ topics.append(topic)
922
+ messageHashes.append('unsubscribe:multi:' + channel + ':' + sym)
923
+ request: dict = {
924
+ 'op': 'unsubscribe',
925
+ 'args': topics,
926
+ }
927
+ url = self.get_url('candle', 'public')
928
+ return await self.watch_multiple(url, messageHashes, request, messageHashes)
929
+
752
930
  def handle_ohlcv(self, client: Client, message):
753
931
  #
754
932
  # {
@@ -1403,6 +1581,9 @@ class okx(ccxt.async_support.okx):
1403
1581
  # }
1404
1582
  #
1405
1583
  arg = self.safe_value(message, 'arg', {})
1584
+ marketId = self.safe_string(arg, 'instId')
1585
+ market = self.safe_market(marketId, None, '-')
1586
+ symbol = market['symbol']
1406
1587
  channel = self.safe_string(arg, 'channel', '')
1407
1588
  data = self.safe_value(message, 'data', [])
1408
1589
  if self.positions is None:
@@ -1420,16 +1601,10 @@ class okx(ccxt.async_support.okx):
1420
1601
  newPositions.append(shortPosition)
1421
1602
  newPositions.append(position)
1422
1603
  cache.append(position)
1423
- messageHashes = self.find_message_hashes(client, channel + '::')
1424
- for i in range(0, len(messageHashes)):
1425
- messageHash = messageHashes[i]
1426
- parts = messageHash.split('::')
1427
- symbolsString = parts[1]
1428
- symbols = symbolsString.split(',')
1429
- positions = self.filter_by_array(newPositions, 'symbol', symbols, False)
1430
- if not self.is_empty(positions):
1431
- client.resolve(positions, messageHash)
1432
- client.resolve(newPositions, channel)
1604
+ messageHash = channel
1605
+ if symbol is not None:
1606
+ messageHash = channel + '::' + symbol
1607
+ client.resolve(newPositions, messageHash)
1433
1608
 
1434
1609
  async def watch_orders(self, symbol: Str = None, since: Int = None, limit: Int = None, params={}) -> List[Order]:
1435
1610
  """
@@ -2008,6 +2183,34 @@ class okx(ccxt.async_support.okx):
2008
2183
  client.reject(error, subMessageHash)
2009
2184
  client.resolve(True, messageHash)
2010
2185
 
2186
+ def handle_unsubscription_ohlcv(self, client: Client, symbol: str, channel: str):
2187
+ tf = channel.replace('candle', '')
2188
+ timeframe = self.find_timeframe(tf)
2189
+ subMessageHash = 'multi:' + channel + ':' + symbol
2190
+ messageHash = 'unsubscribe:' + subMessageHash
2191
+ if subMessageHash in client.subscriptions:
2192
+ del client.subscriptions[subMessageHash]
2193
+ if messageHash in client.subscriptions:
2194
+ del client.subscriptions[messageHash]
2195
+ if timeframe in self.ohlcvs[symbol]:
2196
+ del self.ohlcvs[symbol][timeframe]
2197
+ error = UnsubscribeError(self.id + ' ' + subMessageHash)
2198
+ client.reject(error, subMessageHash)
2199
+ client.resolve(True, messageHash)
2200
+
2201
+ def handle_unsubscription_ticker(self, client: Client, symbol: str, channel):
2202
+ subMessageHash = channel + '::' + symbol
2203
+ messageHash = 'unsubscribe:ticker:' + symbol
2204
+ if subMessageHash in client.subscriptions:
2205
+ del client.subscriptions[subMessageHash]
2206
+ if messageHash in client.subscriptions:
2207
+ del client.subscriptions[messageHash]
2208
+ if symbol in self.tickers:
2209
+ del self.tickers[symbol]
2210
+ error = UnsubscribeError(self.id + ' ' + subMessageHash)
2211
+ client.reject(error, subMessageHash)
2212
+ client.resolve(True, messageHash)
2213
+
2011
2214
  def handle_unsubscription(self, client: Client, message):
2012
2215
  #
2013
2216
  # {
@@ -2020,10 +2223,14 @@ class okx(ccxt.async_support.okx):
2020
2223
  # }
2021
2224
  # arg might be an array or list
2022
2225
  arg = self.safe_dict(message, 'arg', {})
2023
- channel = self.safe_string(arg, 'channel')
2226
+ channel = self.safe_string(arg, 'channel', '')
2024
2227
  marketId = self.safe_string(arg, 'instId')
2025
2228
  symbol = self.safe_symbol(marketId)
2026
2229
  if channel == 'trades':
2027
2230
  self.handle_un_subscription_trades(client, symbol)
2028
2231
  elif channel.startswith('bbo') or channel.startswith('book'):
2029
2232
  self.handle_unsubscription_order_book(client, symbol, channel)
2233
+ elif channel.find('tickers') > -1:
2234
+ self.handle_unsubscription_ticker(client, symbol, channel)
2235
+ elif channel.startswith('candle'):
2236
+ self.handle_unsubscription_ohlcv(client, symbol, channel)
ccxt/test/tests_async.py CHANGED
@@ -920,6 +920,9 @@ class testMainClass(baseMainTestClass):
920
920
  is_disabled = exchange.safe_bool(result, 'disabled', False)
921
921
  if is_disabled:
922
922
  continue
923
+ disabled_string = exchange.safe_string(result, 'disabled', '')
924
+ if disabled_string != '':
925
+ continue
923
926
  is_disabled_c_sharp = exchange.safe_bool(result, 'disabledCS', False)
924
927
  if is_disabled_c_sharp and (self.lang == 'C#'):
925
928
  continue
ccxt/test/tests_sync.py CHANGED
@@ -917,6 +917,9 @@ class testMainClass(baseMainTestClass):
917
917
  is_disabled = exchange.safe_bool(result, 'disabled', False)
918
918
  if is_disabled:
919
919
  continue
920
+ disabled_string = exchange.safe_string(result, 'disabled', '')
921
+ if disabled_string != '':
922
+ continue
920
923
  is_disabled_c_sharp = exchange.safe_bool(result, 'disabledCS', False)
921
924
  if is_disabled_c_sharp and (self.lang == 'C#'):
922
925
  continue