ccxt 4.4.77__py2.py3-none-any.whl → 4.4.80__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 (133) hide show
  1. ccxt/__init__.py +3 -3
  2. ccxt/abstract/apex.py +31 -0
  3. ccxt/abstract/bitmart.py +1 -0
  4. ccxt/apex.py +1884 -0
  5. ccxt/ascendex.py +23 -6
  6. ccxt/async_support/__init__.py +3 -3
  7. ccxt/async_support/apex.py +1884 -0
  8. ccxt/async_support/ascendex.py +23 -6
  9. ccxt/async_support/base/exchange.py +5 -1
  10. ccxt/async_support/binance.py +9 -3
  11. ccxt/async_support/bingx.py +4 -4
  12. ccxt/async_support/bitfinex.py +61 -36
  13. ccxt/async_support/bitflyer.py +2 -2
  14. ccxt/async_support/bitget.py +186 -128
  15. ccxt/async_support/bitmart.py +9 -4
  16. ccxt/async_support/bitmex.py +14 -7
  17. ccxt/async_support/bitopro.py +5 -1
  18. ccxt/async_support/bitrue.py +2 -1
  19. ccxt/async_support/bitso.py +1 -1
  20. ccxt/async_support/bitteam.py +2 -0
  21. ccxt/async_support/bitvavo.py +25 -10
  22. ccxt/async_support/btcalpha.py +1 -1
  23. ccxt/async_support/btcmarkets.py +1 -1
  24. ccxt/async_support/btcturk.py +1 -1
  25. ccxt/async_support/bybit.py +27 -15
  26. ccxt/async_support/cex.py +1 -1
  27. ccxt/async_support/coinbase.py +17 -4
  28. ccxt/async_support/coincatch.py +66 -0
  29. ccxt/async_support/coinex.py +2 -1
  30. ccxt/async_support/coinlist.py +1 -0
  31. ccxt/async_support/coinone.py +1 -0
  32. ccxt/async_support/cryptocom.py +2 -2
  33. ccxt/async_support/defx.py +1 -1
  34. ccxt/async_support/delta.py +4 -1
  35. ccxt/async_support/deribit.py +3 -2
  36. ccxt/async_support/derive.py +2 -2
  37. ccxt/async_support/digifinex.py +2 -2
  38. ccxt/async_support/gate.py +1 -1
  39. ccxt/async_support/hitbtc.py +5 -2
  40. ccxt/async_support/hollaex.py +1 -0
  41. ccxt/async_support/htx.py +9 -5
  42. ccxt/async_support/huobijp.py +1 -0
  43. ccxt/async_support/hyperliquid.py +14 -6
  44. ccxt/async_support/kraken.py +4 -2
  45. ccxt/async_support/krakenfutures.py +2 -2
  46. ccxt/async_support/kucoinfutures.py +2 -2
  47. ccxt/async_support/mexc.py +50 -52
  48. ccxt/async_support/okx.py +2 -2
  49. ccxt/async_support/oxfun.py +2 -2
  50. ccxt/async_support/paradex.py +2 -2
  51. ccxt/async_support/phemex.py +4 -3
  52. ccxt/async_support/poloniex.py +4 -3
  53. ccxt/async_support/probit.py +1 -0
  54. ccxt/async_support/timex.py +2 -2
  55. ccxt/async_support/tradeogre.py +2 -1
  56. ccxt/async_support/upbit.py +243 -63
  57. ccxt/async_support/vertex.py +2 -2
  58. ccxt/async_support/whitebit.py +66 -12
  59. ccxt/async_support/woo.py +5 -3
  60. ccxt/async_support/woofipro.py +2 -2
  61. ccxt/async_support/xt.py +9 -2
  62. ccxt/base/exchange.py +69 -2
  63. ccxt/binance.py +9 -3
  64. ccxt/bingx.py +4 -4
  65. ccxt/bitfinex.py +61 -36
  66. ccxt/bitflyer.py +2 -2
  67. ccxt/bitget.py +186 -128
  68. ccxt/bitmart.py +9 -4
  69. ccxt/bitmex.py +14 -7
  70. ccxt/bitopro.py +5 -1
  71. ccxt/bitrue.py +2 -1
  72. ccxt/bitso.py +1 -1
  73. ccxt/bitteam.py +2 -0
  74. ccxt/bitvavo.py +25 -10
  75. ccxt/btcalpha.py +1 -1
  76. ccxt/btcmarkets.py +1 -1
  77. ccxt/btcturk.py +1 -1
  78. ccxt/bybit.py +27 -15
  79. ccxt/cex.py +1 -1
  80. ccxt/coinbase.py +17 -4
  81. ccxt/coincatch.py +66 -0
  82. ccxt/coinex.py +2 -1
  83. ccxt/coinlist.py +1 -0
  84. ccxt/coinone.py +1 -0
  85. ccxt/cryptocom.py +2 -2
  86. ccxt/defx.py +1 -1
  87. ccxt/delta.py +4 -1
  88. ccxt/deribit.py +3 -2
  89. ccxt/derive.py +2 -2
  90. ccxt/digifinex.py +2 -2
  91. ccxt/gate.py +1 -1
  92. ccxt/hitbtc.py +5 -2
  93. ccxt/hollaex.py +1 -0
  94. ccxt/htx.py +9 -5
  95. ccxt/huobijp.py +1 -0
  96. ccxt/hyperliquid.py +14 -6
  97. ccxt/kraken.py +4 -2
  98. ccxt/krakenfutures.py +2 -2
  99. ccxt/kucoinfutures.py +2 -2
  100. ccxt/mexc.py +50 -52
  101. ccxt/okx.py +2 -2
  102. ccxt/oxfun.py +2 -2
  103. ccxt/paradex.py +2 -2
  104. ccxt/phemex.py +4 -3
  105. ccxt/poloniex.py +4 -3
  106. ccxt/pro/__init__.py +5 -1
  107. ccxt/pro/apex.py +984 -0
  108. ccxt/pro/binance.py +3 -3
  109. ccxt/pro/coinbase.py +43 -57
  110. ccxt/pro/gate.py +22 -2
  111. ccxt/pro/hollaex.py +2 -2
  112. ccxt/pro/p2b.py +2 -2
  113. ccxt/pro/tradeogre.py +272 -0
  114. ccxt/pro/upbit.py +42 -0
  115. ccxt/probit.py +1 -0
  116. ccxt/test/tests_async.py +4 -1
  117. ccxt/test/tests_sync.py +4 -1
  118. ccxt/timex.py +2 -2
  119. ccxt/tradeogre.py +2 -1
  120. ccxt/upbit.py +243 -63
  121. ccxt/vertex.py +2 -2
  122. ccxt/whitebit.py +66 -12
  123. ccxt/woo.py +5 -3
  124. ccxt/woofipro.py +2 -2
  125. ccxt/xt.py +9 -2
  126. {ccxt-4.4.77.dist-info → ccxt-4.4.80.dist-info}/METADATA +9 -11
  127. {ccxt-4.4.77.dist-info → ccxt-4.4.80.dist-info}/RECORD +130 -128
  128. ccxt/abstract/ace.py +0 -15
  129. ccxt/ace.py +0 -1152
  130. ccxt/async_support/ace.py +0 -1152
  131. {ccxt-4.4.77.dist-info → ccxt-4.4.80.dist-info}/LICENSE.txt +0 -0
  132. {ccxt-4.4.77.dist-info → ccxt-4.4.80.dist-info}/WHEEL +0 -0
  133. {ccxt-4.4.77.dist-info → ccxt-4.4.80.dist-info}/top_level.txt +0 -0
ccxt/pro/binance.py CHANGED
@@ -71,12 +71,12 @@ class binance(ccxt.async_support.binance):
71
71
  'urls': {
72
72
  'test': {
73
73
  'ws': {
74
- 'spot': 'wss://testnet.binance.vision/ws',
75
- 'margin': 'wss://testnet.binance.vision/ws',
74
+ 'spot': 'wss://stream.testnet.binance.vision/ws',
75
+ 'margin': 'wss://stream.testnet.binance.vision/ws',
76
76
  'future': 'wss://fstream.binancefuture.com/ws',
77
77
  'delivery': 'wss://dstream.binancefuture.com/ws',
78
78
  'ws-api': {
79
- 'spot': 'wss://testnet.binance.vision/ws-api/v3',
79
+ 'spot': 'wss://ws-api.testnet.binance.vision/ws-api/v3',
80
80
  'future': 'wss://testnet.binancefuture.com/ws-fapi/v1',
81
81
  'delivery': 'wss://testnet.binancefuture.com/ws-dapi/v1',
82
82
  },
ccxt/pro/coinbase.py CHANGED
@@ -78,7 +78,7 @@ class coinbase(ccxt.async_support.coinbase):
78
78
  messageHash = messageHash + '::' + ','.join(symbol)
79
79
  elif symbol is not None:
80
80
  market = self.market(symbol)
81
- messageHash = name + '::' + market['id']
81
+ messageHash = name + '::' + symbol
82
82
  productIds = [market['id']]
83
83
  url = self.urls['api']['ws']
84
84
  subscribe = {
@@ -115,7 +115,7 @@ class coinbase(ccxt.async_support.coinbase):
115
115
  market = self.market(symbol)
116
116
  marketId = market['id']
117
117
  productIds.append(marketId)
118
- messageHashes.append(name + '::' + marketId)
118
+ messageHashes.append(name + '::' + symbol)
119
119
  url = self.urls['api']['ws']
120
120
  subscribe = {
121
121
  'type': 'subscribe',
@@ -176,8 +176,11 @@ class coinbase(ccxt.async_support.coinbase):
176
176
  if symbols is None:
177
177
  symbols = self.symbols
178
178
  name = 'ticker_batch'
179
- tickers = await self.subscribe(name, False, symbols, params)
179
+ ticker = await self.subscribe_multiple(name, False, symbols, params)
180
180
  if self.newUpdates:
181
+ tickers = {}
182
+ symbol = ticker['symbol']
183
+ tickers[symbol] = ticker
181
184
  return tickers
182
185
  return self.tickers
183
186
 
@@ -272,7 +275,7 @@ class coinbase(ccxt.async_support.coinbase):
272
275
  #
273
276
  #
274
277
  channel = self.safe_string(message, 'channel')
275
- events = self.safe_value(message, 'events', [])
278
+ events = self.safe_list(message, 'events', [])
276
279
  datetime = self.safe_string(message, 'timestamp')
277
280
  timestamp = self.parse8601(datetime)
278
281
  newTickers = []
@@ -281,31 +284,18 @@ class coinbase(ccxt.async_support.coinbase):
281
284
  tickers = self.safe_list(tickersObj, 'tickers', [])
282
285
  for j in range(0, len(tickers)):
283
286
  ticker = tickers[j]
287
+ wsMarketId = self.safe_string(ticker, 'product_id')
288
+ if wsMarketId is None:
289
+ continue
284
290
  result = self.parse_ws_ticker(ticker)
285
291
  result['timestamp'] = timestamp
286
292
  result['datetime'] = datetime
287
293
  symbol = result['symbol']
288
294
  self.tickers[symbol] = result
289
- wsMarketId = self.safe_string(ticker, 'product_id')
290
- if wsMarketId is None:
291
- continue
292
- messageHash = channel + '::' + wsMarketId
293
295
  newTickers.append(result)
296
+ messageHash = channel + '::' + symbol
294
297
  client.resolve(result, messageHash)
295
- if messageHash.endswith('USD'):
296
- client.resolve(result, messageHash + 'C') # sometimes we subscribe to BTC/USDC and coinbase returns BTC/USD
297
- messageHashes = self.find_message_hashes(client, 'ticker_batch::')
298
- for i in range(0, len(messageHashes)):
299
- messageHash = messageHashes[i]
300
- parts = messageHash.split('::')
301
- symbolsString = parts[1]
302
- symbols = symbolsString.split(',')
303
- tickers = self.filter_by_array(newTickers, 'symbol', symbols)
304
- if not self.is_empty(tickers):
305
- client.resolve(tickers, messageHash)
306
- if messageHash.endswith('USD'):
307
- client.resolve(tickers, messageHash + 'C') # sometimes we subscribe to BTC/USDC and coinbase returns BTC/USD
308
- return message
298
+ self.try_resolve_usdc(client, messageHash, result)
309
299
 
310
300
  def parse_ws_ticker(self, ticker, market=None):
311
301
  #
@@ -470,13 +460,13 @@ class coinbase(ccxt.async_support.coinbase):
470
460
  # ]
471
461
  # }
472
462
  #
473
- events = self.safe_value(message, 'events')
463
+ events = self.safe_list(message, 'events')
474
464
  event = self.safe_value(events, 0)
475
- trades = self.safe_value(event, 'trades')
476
- trade = self.safe_value(trades, 0)
465
+ trades = self.safe_list(event, 'trades')
466
+ trade = self.safe_dict(trades, 0)
477
467
  marketId = self.safe_string(trade, 'product_id')
478
- messageHash = 'market_trades::' + marketId
479
468
  symbol = self.safe_symbol(marketId)
469
+ messageHash = 'market_trades::' + symbol
480
470
  tradesArray = self.safe_value(self.trades, symbol)
481
471
  if tradesArray is None:
482
472
  tradesLimit = self.safe_integer(self.options, 'tradesLimit', 1000)
@@ -484,14 +474,12 @@ class coinbase(ccxt.async_support.coinbase):
484
474
  self.trades[symbol] = tradesArray
485
475
  for i in range(0, len(events)):
486
476
  currentEvent = events[i]
487
- currentTrades = self.safe_value(currentEvent, 'trades')
477
+ currentTrades = self.safe_list(currentEvent, 'trades')
488
478
  for j in range(0, len(currentTrades)):
489
479
  item = currentTrades[i]
490
480
  tradesArray.append(self.parse_trade(item))
491
481
  client.resolve(tradesArray, messageHash)
492
- if marketId.endswith('USD'):
493
- client.resolve(tradesArray, messageHash + 'C') # sometimes we subscribe to BTC/USDC and coinbase returns BTC/USD
494
- return message
482
+ self.try_resolve_usdc(client, messageHash, tradesArray)
495
483
 
496
484
  def handle_order(self, client, message):
497
485
  #
@@ -522,14 +510,14 @@ class coinbase(ccxt.async_support.coinbase):
522
510
  # ]
523
511
  # }
524
512
  #
525
- events = self.safe_value(message, 'events')
513
+ events = self.safe_list(message, 'events')
526
514
  marketIds = []
527
515
  if self.orders is None:
528
516
  limit = self.safe_integer(self.options, 'ordersLimit', 1000)
529
517
  self.orders = ArrayCacheBySymbolById(limit)
530
518
  for i in range(0, len(events)):
531
519
  event = events[i]
532
- responseOrders = self.safe_value(event, 'orders')
520
+ responseOrders = self.safe_list(event, 'orders')
533
521
  for j in range(0, len(responseOrders)):
534
522
  responseOrder = responseOrders[j]
535
523
  parsed = self.parse_ws_order(responseOrder)
@@ -540,12 +528,11 @@ class coinbase(ccxt.async_support.coinbase):
540
528
  cachedOrders.append(parsed)
541
529
  for i in range(0, len(marketIds)):
542
530
  marketId = marketIds[i]
543
- messageHash = 'user::' + marketId
531
+ symbol = self.safe_symbol(marketId)
532
+ messageHash = 'user::' + symbol
544
533
  client.resolve(self.orders, messageHash)
545
- if messageHash.endswith('USD'):
546
- client.resolve(self.orders, messageHash + 'C') # sometimes we subscribe to BTC/USDC and coinbase returns BTC/USD
534
+ self.try_resolve_usdc(client, messageHash, self.orders)
547
535
  client.resolve(self.orders, 'user')
548
- return message
549
536
 
550
537
  def parse_ws_order(self, order, market=None):
551
538
  #
@@ -635,36 +622,35 @@ class coinbase(ccxt.async_support.coinbase):
635
622
  # ]
636
623
  # }
637
624
  #
638
- events = self.safe_value(message, 'events')
625
+ events = self.safe_list(message, 'events')
639
626
  datetime = self.safe_string(message, 'timestamp')
640
627
  for i in range(0, len(events)):
641
628
  event = events[i]
642
- updates = self.safe_value(event, 'updates', [])
629
+ updates = self.safe_list(event, 'updates', [])
643
630
  marketId = self.safe_string(event, 'product_id')
644
- messageHash = 'level2::' + marketId
631
+ # sometimes we subscribe to BTC/USDC and coinbase returns BTC/USD, are aliases
632
+ market = self.safe_market(marketId)
633
+ symbol = market['symbol']
634
+ messageHash = 'level2::' + symbol
645
635
  subscription = self.safe_value(client.subscriptions, messageHash, {})
646
636
  limit = self.safe_integer(subscription, 'limit')
647
- symbol = self.safe_symbol(marketId)
648
637
  type = self.safe_string(event, 'type')
649
638
  if type == 'snapshot':
650
639
  self.orderbooks[symbol] = self.order_book({}, limit)
651
- orderbook = self.orderbooks[symbol]
652
- self.handle_order_book_helper(orderbook, updates)
653
- orderbook['timestamp'] = self.parse8601(datetime)
654
- orderbook['datetime'] = datetime
655
- orderbook['symbol'] = symbol
656
- client.resolve(orderbook, messageHash)
657
- if messageHash.endswith('USD'):
658
- client.resolve(orderbook, messageHash + 'C') # sometimes we subscribe to BTC/USDC and coinbase returns BTC/USD
659
- elif type == 'update':
660
- orderbook = self.orderbooks[symbol]
661
- self.handle_order_book_helper(orderbook, updates)
662
- orderbook['datetime'] = datetime
663
- orderbook['timestamp'] = self.parse8601(datetime)
664
- orderbook['symbol'] = symbol
665
- client.resolve(orderbook, messageHash)
666
- if messageHash.endswith('USD'):
667
- client.resolve(orderbook, messageHash + 'C') # sometimes we subscribe to BTC/USDC and coinbase returns BTC/USD
640
+ # unknown bug, can't reproduce, but sometimes orderbook is None
641
+ if not (symbol in self.orderbooks) and self.orderbooks[symbol] is None:
642
+ continue
643
+ orderbook = self.orderbooks[symbol]
644
+ self.handle_order_book_helper(orderbook, updates)
645
+ orderbook['timestamp'] = self.parse8601(datetime)
646
+ orderbook['datetime'] = datetime
647
+ orderbook['symbol'] = symbol
648
+ client.resolve(orderbook, messageHash)
649
+ self.try_resolve_usdc(client, messageHash, orderbook)
650
+
651
+ def try_resolve_usdc(self, client, messageHash, result):
652
+ if messageHash.endswith('/USD') or messageHash.endswith('-USD'):
653
+ client.resolve(result, messageHash + 'C') # when subscribing to BTC/USDC and coinbase returns BTC/USD, so resolve USDC too
668
654
 
669
655
  def handle_subscription_status(self, client, message):
670
656
  #
ccxt/pro/gate.py CHANGED
@@ -1175,8 +1175,28 @@ class gate(ccxt.async_support.gate):
1175
1175
  for i in range(0, len(data)):
1176
1176
  rawPosition = data[i]
1177
1177
  position = self.parse_position(rawPosition)
1178
- newPositions.append(position)
1179
- cache.append(position)
1178
+ symbol = self.safe_string(position, 'symbol')
1179
+ side = self.safe_string(position, 'side')
1180
+ # Control when position is closed no side is returned
1181
+ if side is None:
1182
+ prevLongPosition = self.safe_dict(cache, symbol + 'long')
1183
+ if prevLongPosition is not None:
1184
+ position['side'] = prevLongPosition['side']
1185
+ newPositions.append(position)
1186
+ cache.append(position)
1187
+ prevShortPosition = self.safe_dict(cache, symbol + 'short')
1188
+ if prevShortPosition is not None:
1189
+ position['side'] = prevShortPosition['side']
1190
+ newPositions.append(position)
1191
+ cache.append(position)
1192
+ # if no prev position is found, default to long
1193
+ if prevLongPosition is None and prevShortPosition is None:
1194
+ position['side'] = 'long'
1195
+ newPositions.append(position)
1196
+ cache.append(position)
1197
+ else:
1198
+ newPositions.append(position)
1199
+ cache.append(position)
1180
1200
  messageHashes = self.find_message_hashes(client, type + ':positions::')
1181
1201
  for i in range(0, len(messageHashes)):
1182
1202
  messageHash = messageHashes[i]
ccxt/pro/hollaex.py CHANGED
@@ -572,8 +572,8 @@ class hollaex(ccxt.async_support.hollaex):
572
572
 
573
573
  def on_error(self, client: Client, error):
574
574
  self.options['ws-expires'] = None
575
- self.on_error(client, error)
575
+ super(hollaex, self).on_error(client, error)
576
576
 
577
577
  def on_close(self, client: Client, error):
578
578
  self.options['ws-expires'] = None
579
- self.on_close(client, error)
579
+ super(hollaex, self).on_close(client, error)
ccxt/pro/p2b.py CHANGED
@@ -478,8 +478,8 @@ class p2b(ccxt.async_support.p2b):
478
478
 
479
479
  def on_error(self, client: Client, error):
480
480
  self.options['tickerSubs'] = self.create_safe_dictionary()
481
- self.on_error(client, error)
481
+ super(p2b, self).on_error(client, error)
482
482
 
483
483
  def on_close(self, client: Client, error):
484
484
  self.options['tickerSubs'] = self.create_safe_dictionary()
485
- self.on_close(client, error)
485
+ super(p2b, self).on_close(client, error)
ccxt/pro/tradeogre.py ADDED
@@ -0,0 +1,272 @@
1
+ # -*- coding: utf-8 -*-
2
+
3
+ # PLEASE DO NOT EDIT THIS FILE, IT IS GENERATED AND WILL BE OVERWRITTEN:
4
+ # https://github.com/ccxt/ccxt/blob/master/CONTRIBUTING.md#how-to-contribute-code
5
+
6
+ import ccxt.async_support
7
+ from ccxt.async_support.base.ws.cache import ArrayCache
8
+ from ccxt.base.types import Any, Int, OrderBook, Trade
9
+ from ccxt.async_support.base.ws.client import Client
10
+ from typing import List
11
+
12
+
13
+ class tradeogre(ccxt.async_support.tradeogre):
14
+
15
+ def describe(self) -> Any:
16
+ return self.deep_extend(super(tradeogre, self).describe(), {
17
+ 'has': {
18
+ 'ws': True,
19
+ 'watchTrades': True,
20
+ 'watchTradesForSymbols': True,
21
+ 'watchOrderBook': True,
22
+ 'watchOrderBookForSymbols': False,
23
+ 'watchOHLCV': False,
24
+ 'watchOHLCVForSymbols': False,
25
+ 'watchOrders': False,
26
+ 'watchMyTrades': False,
27
+ 'watchTicker': False,
28
+ 'watchTickers': False,
29
+ 'watchBidsAsks': False,
30
+ 'watchBalance': False,
31
+ 'createOrderWs': False,
32
+ 'editOrderWs': False,
33
+ 'cancelOrderWs': False,
34
+ 'cancelOrdersWs': False,
35
+ },
36
+ 'urls': {
37
+ 'api': {
38
+ 'ws': 'wss://tradeogre.com:8443',
39
+ },
40
+ },
41
+ 'options': {
42
+ },
43
+ 'streaming': {
44
+ },
45
+ })
46
+
47
+ async def watch_order_book(self, symbol: str, limit: Int = None, params={}) -> OrderBook:
48
+ """
49
+ watches information on open orders with bid(buy) and ask(sell) prices, volumes and other data
50
+
51
+ https://tradeogre.com/help/api
52
+
53
+ :param str symbol: unified symbol of the market to fetch the order book for
54
+ :param int [limit]: the maximum amount of order book entries to return(not used by the exchange)
55
+ :param dict [params]: extra parameters specific to the exchange API endpoint
56
+ :returns dict: A dictionary of `order book structures <https://docs.ccxt.com/#/?id=order-book-structure>` indexed by market symbols
57
+ """
58
+ await self.load_markets()
59
+ market = self.market(symbol)
60
+ url = self.urls['api']['ws']
61
+ messageHash = 'orderbook' + ':' + market['symbol']
62
+ request: dict = {
63
+ 'a': 'subscribe',
64
+ 'e': 'book',
65
+ 't': market['id'],
66
+ }
67
+ orderbook = await self.watch(url, messageHash, self.extend(request, params), messageHash)
68
+ return orderbook.limit()
69
+
70
+ def handle_order_book(self, client: Client, message):
71
+ #
72
+ # initial snapshot is fetched with ccxt's fetchOrderBook
73
+ # the feed does not include a snapshot, just the deltas
74
+ #
75
+ # {
76
+ # "e": "book",
77
+ # "t": "ETH-USDT",
78
+ # "s": "10752324",
79
+ # "d": {
80
+ # "bids": {"1787.02497915": "0"},
81
+ # "asks": {}
82
+ # }
83
+ # }
84
+ #
85
+ marketId = self.safe_string(message, 't')
86
+ symbol = self.safe_symbol(marketId)
87
+ if not (symbol in self.orderbooks):
88
+ self.orderbooks[symbol] = self.order_book({})
89
+ storedOrderBook = self.orderbooks[symbol]
90
+ nonce = self.safe_integer(storedOrderBook, 'nonce')
91
+ deltaNonce = self.safe_integer(message, 's')
92
+ messageHash = 'orderbook:' + symbol
93
+ if nonce is None:
94
+ cacheLength = len(storedOrderBook.cache)
95
+ snapshotDelay = self.handle_option('watchOrderBook', 'snapshotDelay', 6)
96
+ if cacheLength == snapshotDelay:
97
+ self.spawn(self.load_order_book, client, messageHash, symbol, None, {})
98
+ storedOrderBook.cache.append(message)
99
+ return
100
+ elif nonce >= deltaNonce:
101
+ return
102
+ self.handle_delta(storedOrderBook, message)
103
+ client.resolve(storedOrderBook, messageHash)
104
+
105
+ def handle_delta(self, orderbook, delta):
106
+ # timestamp = self.milliseconds() # todo check if self is correct
107
+ # orderbook['timestamp'] = timestamp
108
+ # orderbook['datetime'] = self.iso8601(timestamp)
109
+ orderbook['nonce'] = self.safe_integer(delta, 's')
110
+ data = self.safe_dict(delta, 'd', {})
111
+ bids = self.safe_dict(data, 'bids', {})
112
+ asks = self.safe_dict(data, 'asks', {})
113
+ storedBids = orderbook['bids']
114
+ storedAsks = orderbook['asks']
115
+ self.handle_bid_asks(storedBids, bids)
116
+ self.handle_bid_asks(storedAsks, asks)
117
+
118
+ def handle_bid_asks(self, bookSide, bidAsks):
119
+ keys = list(bidAsks.keys())
120
+ for i in range(0, len(keys)):
121
+ price = self.safe_string(keys, i)
122
+ amount = self.safe_number(bidAsks, price)
123
+ bidAsk = [self.parse_number(price), amount]
124
+ bookSide.storeArray(bidAsk)
125
+ # for i in range(0, len(bidAsks)):
126
+ # bidAsk = self.parse_bid_ask(bidAsks[i])
127
+ # bookSide.storeArray(bidAsk)
128
+ # }
129
+
130
+ def get_cache_index(self, orderbook, deltas):
131
+ firstElement = deltas[0]
132
+ firstElementNonce = self.safe_integer(firstElement, 's')
133
+ nonce = self.safe_integer(orderbook, 'nonce')
134
+ if nonce < firstElementNonce:
135
+ return -1
136
+ for i in range(0, len(deltas)):
137
+ delta = deltas[i]
138
+ deltaNonce = self.safe_integer(delta, 's')
139
+ if deltaNonce == nonce:
140
+ return i + 1
141
+ return len(deltas)
142
+
143
+ async def watch_trades(self, symbol: str, since: Int = None, limit: Int = None, params={}) -> List[Trade]:
144
+ """
145
+ watches information on multiple trades made in a market
146
+
147
+ https://tradeogre.com/help/api
148
+
149
+ :param str symbol: unified market symbol of the market trades were made in
150
+ :param int [since]: the earliest time in ms to fetch trades for
151
+ :param int [limit]: the maximum number of trade structures to retrieve
152
+ :param dict [params]: extra parameters specific to the exchange API endpoint
153
+ :returns dict[]: a list of `trade structures <https://docs.ccxt.com/#/?id=trade-structure>`
154
+ """
155
+ await self.load_markets()
156
+ market = self.market(symbol)
157
+ symbol = market['symbol']
158
+ return await self.watch_trades_for_symbols([symbol], since, limit, params)
159
+
160
+ async def watch_trades_for_symbols(self, symbols: List[str], since: Int = None, limit: Int = None, params={}) -> List[Trade]:
161
+ """
162
+
163
+ https://tradeogre.com/help/api
164
+
165
+ get the list of most recent trades for a list of symbols
166
+ :param str[] symbols: unified symbol of the market to fetch trades for(empty array means all markets)
167
+ :param int [since]: timestamp in ms of the earliest trade to fetch
168
+ :param int [limit]: the maximum amount of trades to fetch
169
+ :param dict [params]: extra parameters specific to the exchange API endpoint
170
+ :returns dict[]: a list of `trade structures <https://docs.ccxt.com/#/?id=public-trades>`
171
+ """
172
+ await self.load_markets()
173
+ symbols = self.market_symbols(symbols, None, True)
174
+ messageHashes = []
175
+ symbolsLength = 0
176
+ if symbols is not None:
177
+ symbolsLength = len(symbols)
178
+ if symbolsLength > 0:
179
+ for i in range(0, len(symbols)):
180
+ symbol = symbols[i]
181
+ messageHash = 'trades:' + symbol
182
+ messageHashes.append(messageHash)
183
+ else:
184
+ messageHash = 'trades'
185
+ messageHashes.append(messageHash)
186
+ request: dict = {
187
+ 'a': 'subscribe',
188
+ 'e': 'trade',
189
+ 't': '*',
190
+ }
191
+ url = self.urls['api']['ws']
192
+ trades = await self.watch_multiple(url, messageHashes, self.extend(request, params), ['trades'])
193
+ if self.newUpdates:
194
+ first = self.safe_dict(trades, 0)
195
+ tradeSymbol = self.safe_string(first, 'symbol')
196
+ limit = trades.getLimit(tradeSymbol, limit)
197
+ return self.filter_by_since_limit(trades, since, limit, 'timestamp', True)
198
+
199
+ def handle_trade(self, client: Client, message):
200
+ #
201
+ # {
202
+ # "e": "trade",
203
+ # "t": "LTC-USDT",
204
+ # "d": {
205
+ # "t": 0,
206
+ # "p": "84.50000000",
207
+ # "q": "1.28471270",
208
+ # "d": "1745392002"
209
+ # }
210
+ # }
211
+ #
212
+ marketId = self.safe_string(message, 't')
213
+ market = self.safe_market(marketId)
214
+ data = self.safe_dict(message, 'd', {})
215
+ symbol = market['symbol']
216
+ if not (symbol in self.trades):
217
+ limit = self.safe_integer(self.options, 'tradesLimit', 1000)
218
+ stored = ArrayCache(limit)
219
+ self.trades[symbol] = stored
220
+ cache = self.trades[symbol]
221
+ trade = self.parse_ws_trade(data, market)
222
+ cache.append(trade)
223
+ messageHash = 'trades:' + symbol
224
+ client.resolve(cache, messageHash)
225
+ client.resolve(cache, 'trades')
226
+
227
+ def parse_ws_trade(self, trade, market=None):
228
+ #
229
+ # {
230
+ # "t": 0,
231
+ # "p": "84.50000000",
232
+ # "q": "1.28471270",
233
+ # "d": "1745392002"
234
+ # }
235
+ #
236
+ timestamp = self.safe_integer_product(trade, 'd', 1000)
237
+ sideEnum = self.safe_string(trade, 't')
238
+ return self.safe_trade({
239
+ 'info': trade,
240
+ 'id': None,
241
+ 'timestamp': timestamp,
242
+ 'datetime': self.iso8601(timestamp),
243
+ 'symbol': self.safe_string(market, 'symbol'),
244
+ 'order': None,
245
+ 'type': None,
246
+ 'side': self.parse_ws_trade_side(sideEnum),
247
+ 'takerOrMaker': None,
248
+ 'price': self.safe_string(trade, 'p'),
249
+ 'amount': self.safe_string(trade, 'q'),
250
+ 'cost': None,
251
+ 'fee': {
252
+ 'currency': None,
253
+ 'cost': None,
254
+ },
255
+ }, market)
256
+
257
+ def parse_ws_trade_side(self, side):
258
+ sides = {
259
+ '0': 'buy',
260
+ '1': 'sell',
261
+ }
262
+ return self.safe_string(sides, side, side)
263
+
264
+ def handle_message(self, client: Client, message):
265
+ methods: dict = {
266
+ 'book': self.handle_order_book,
267
+ 'trade': self.handle_trade,
268
+ }
269
+ event = self.safe_string(message, 'e')
270
+ method = self.safe_value(methods, event)
271
+ if method is not None:
272
+ method(client, message)
ccxt/pro/upbit.py CHANGED
@@ -8,6 +8,7 @@ from ccxt.async_support.base.ws.cache import ArrayCache, ArrayCacheBySymbolById
8
8
  from ccxt.base.types import Any, Balances, Int, Order, OrderBook, Str, Strings, Ticker, Tickers, Trade
9
9
  from ccxt.async_support.base.ws.client import Client
10
10
  from typing import List
11
+ from ccxt.base.errors import NotSupported
11
12
 
12
13
 
13
14
  class upbit(ccxt.async_support.upbit):
@@ -21,6 +22,7 @@ class upbit(ccxt.async_support.upbit):
21
22
  'watchTickers': True,
22
23
  'watchTrades': True,
23
24
  'watchTradesForSymbols': True,
25
+ 'watchOHLCV': True,
24
26
  'watchOrders': True,
25
27
  'watchMyTrades': True,
26
28
  'watchBalance': True,
@@ -190,6 +192,25 @@ class upbit(ccxt.async_support.upbit):
190
192
  orderbook = await self.watch_public(symbol, 'orderbook')
191
193
  return orderbook.limit()
192
194
 
195
+ async def watch_ohlcv(self, symbol: str, timeframe='1s', since: Int = None, limit: Int = None, params={}) -> List[list]:
196
+ """
197
+ watches information an OHLCV with timestamp, openingPrice, highPrice, lowPrice, tradePrice, baseVolume in 1s.
198
+
199
+ https://docs.upbit.com/kr/reference/websocket-candle for Upbit KR
200
+ https://global-docs.upbit.com/reference/websocket-candle for Upbit Global
201
+
202
+ :param str symbol: unified market symbol of the market orders were made in
203
+ :param str timeframe: specifies the OHLCV candle interval to watch. As of now, Upbit only supports 1s candles.
204
+ :param int [since]: the earliest time in ms to fetch orders for
205
+ :param int [limit]: the maximum number of order structures to retrieve
206
+ :param dict [params]: extra parameters specific to the exchange API endpoint
207
+ :returns OHLCV[]: a list of `OHLCV structures <https://docs.ccxt.com/#/?id=ohlcv-structure>`
208
+ """
209
+ if timeframe != '1s':
210
+ raise NotSupported(self.id + ' watchOHLCV does not support' + timeframe + ' candle.')
211
+ timeFrameOHLCV = 'candle.' + timeframe
212
+ return await self.watch_public(symbol, timeFrameOHLCV)
213
+
193
214
  def handle_ticker(self, client: Client, message):
194
215
  # 2020-03-17T23:07:36.511Z "onMessage" <Buffer 7b 22 74 79 70 65 22 3a 22 74 69 63 6b 65 72 22 2c 22 63 6f 64 65 22 3a 22 42 54 43 2d 45 54 48 22 2c 22 6f 70 65 6e 69 6e 67 5f 70 72 69 63 65 22 3a ... >
195
216
  # {type: "ticker",
@@ -313,6 +334,26 @@ class upbit(ccxt.async_support.upbit):
313
334
  messageHash = 'trade:' + marketId
314
335
  client.resolve(stored, messageHash)
315
336
 
337
+ def handle_ohlcv(self, client: Client, message):
338
+ # {
339
+ # type: 'candle.1s',
340
+ # code: 'KRW-USDT',
341
+ # candle_date_time_utc: '2025-04-22T09:50:34',
342
+ # candle_date_time_kst: '2025-04-22T18:50:34',
343
+ # opening_price: 1438,
344
+ # high_price: 1438,
345
+ # low_price: 1438,
346
+ # trade_price: 1438,
347
+ # candle_acc_trade_volume: 1145.8935,
348
+ # candle_acc_trade_price: 1647794.853,
349
+ # timestamp: 1745315434125,
350
+ # stream_type: 'REALTIME'
351
+ # }
352
+ marketId = self.safe_string(message, 'code')
353
+ messageHash = 'candle.1s:' + marketId
354
+ ohlcv = self.parse_ohlcv(message)
355
+ client.resolve(ohlcv, messageHash)
356
+
316
357
  async def authenticate(self, params={}):
317
358
  self.check_required_credentials()
318
359
  wsOptions: dict = self.safe_dict(self.options, 'ws', {})
@@ -611,6 +652,7 @@ class upbit(ccxt.async_support.upbit):
611
652
  'trade': self.handle_trades,
612
653
  'myOrder': self.handle_my_order,
613
654
  'myAsset': self.handle_balance,
655
+ 'candle.1s': self.handle_ohlcv,
614
656
  }
615
657
  methodName = self.safe_string(message, 'type')
616
658
  method = self.safe_value(methods, methodName)
ccxt/probit.py CHANGED
@@ -563,6 +563,7 @@ class probit(Exchange, ImplicitAPI):
563
563
  'active': active,
564
564
  'deposit': deposit,
565
565
  'withdraw': withdraw,
566
+ 'type': 'crypto',
566
567
  'fee': fee,
567
568
  'precision': self.parse_number(self.parse_precision(self.safe_string(platform, 'precision'))),
568
569
  'limits': {
ccxt/test/tests_async.py CHANGED
@@ -348,7 +348,6 @@ class testMainClass:
348
348
  'fetchOHLCV': [symbol],
349
349
  'fetchTrades': [symbol],
350
350
  'fetchOrderBook': [symbol],
351
- 'fetchL2OrderBook': [symbol],
352
351
  'fetchOrderBooks': [],
353
352
  'fetchBidsAsks': [],
354
353
  'fetchStatus': [],
@@ -896,6 +895,8 @@ class testMainClass:
896
895
  async def test_request_statically(self, exchange, method, data, type, skip_keys):
897
896
  output = None
898
897
  request_url = None
898
+ if self.info:
899
+ dump('[INFO] STATIC REQUEST TEST:', method, ':', data['description'])
899
900
  try:
900
901
  if not is_sync():
901
902
  await call_exchange_method_dynamically(exchange, method, self.sanitize_data_input(data['input']))
@@ -918,6 +919,8 @@ class testMainClass:
918
919
  async def test_response_statically(self, exchange, method, skip_keys, data):
919
920
  expected_result = exchange.safe_value(data, 'parsedResponse')
920
921
  mocked_exchange = set_fetch_response(exchange, data['httpResponse'])
922
+ if self.info:
923
+ dump('[INFO] STATIC RESPONSE TEST:', method, ':', data['description'])
921
924
  try:
922
925
  if not is_sync():
923
926
  unified_result = await call_exchange_method_dynamically(exchange, method, self.sanitize_data_input(data['input']))