ccxt 4.2.70__py2.py3-none-any.whl → 4.2.71__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 (105) hide show
  1. ccxt/__init__.py +1 -1
  2. ccxt/abstract/coinbase.py +2 -0
  3. ccxt/ascendex.py +1 -1
  4. ccxt/async_support/__init__.py +1 -1
  5. ccxt/async_support/ascendex.py +1 -1
  6. ccxt/async_support/base/exchange.py +1 -1
  7. ccxt/async_support/bigone.py +1 -1
  8. ccxt/async_support/binance.py +1 -1
  9. ccxt/async_support/bingx.py +1 -1
  10. ccxt/async_support/bitfinex.py +1 -1
  11. ccxt/async_support/bitfinex2.py +1 -1
  12. ccxt/async_support/bitget.py +13 -7
  13. ccxt/async_support/bitmart.py +1 -1
  14. ccxt/async_support/bitmex.py +1 -1
  15. ccxt/async_support/bitrue.py +1 -1
  16. ccxt/async_support/bitstamp.py +1 -1
  17. ccxt/async_support/bl3p.py +1 -1
  18. ccxt/async_support/blofin.py +1 -1
  19. ccxt/async_support/btcalpha.py +1 -1
  20. ccxt/async_support/bybit.py +1 -1
  21. ccxt/async_support/coinbase.py +104 -89
  22. ccxt/async_support/coinex.py +1 -1
  23. ccxt/async_support/coinlist.py +1 -1
  24. ccxt/async_support/coinmetro.py +1 -1
  25. ccxt/async_support/deribit.py +1 -1
  26. ccxt/async_support/digifinex.py +3 -2
  27. ccxt/async_support/gate.py +1 -1
  28. ccxt/async_support/hitbtc.py +1 -1
  29. ccxt/async_support/htx.py +1 -1
  30. ccxt/async_support/hyperliquid.py +2 -2
  31. ccxt/async_support/kraken.py +2 -1
  32. ccxt/async_support/krakenfutures.py +1 -1
  33. ccxt/async_support/kucoin.py +1 -1
  34. ccxt/async_support/kucoinfutures.py +1 -1
  35. ccxt/async_support/latoken.py +1 -1
  36. ccxt/async_support/lykke.py +1 -1
  37. ccxt/async_support/mexc.py +10 -1
  38. ccxt/async_support/ndax.py +1 -1
  39. ccxt/async_support/novadax.py +1 -1
  40. ccxt/async_support/okcoin.py +1 -1
  41. ccxt/async_support/okx.py +1 -1
  42. ccxt/async_support/paymium.py +1 -1
  43. ccxt/async_support/phemex.py +1 -1
  44. ccxt/async_support/poloniex.py +1 -1
  45. ccxt/async_support/wazirx.py +1 -1
  46. ccxt/async_support/whitebit.py +1 -1
  47. ccxt/async_support/woo.py +1 -1
  48. ccxt/async_support/zonda.py +1 -1
  49. ccxt/base/exchange.py +1 -1
  50. ccxt/bigone.py +1 -1
  51. ccxt/binance.py +1 -1
  52. ccxt/bingx.py +1 -1
  53. ccxt/bitfinex.py +1 -1
  54. ccxt/bitfinex2.py +1 -1
  55. ccxt/bitget.py +13 -7
  56. ccxt/bitmart.py +1 -1
  57. ccxt/bitmex.py +1 -1
  58. ccxt/bitrue.py +1 -1
  59. ccxt/bitstamp.py +1 -1
  60. ccxt/bl3p.py +1 -1
  61. ccxt/blofin.py +1 -1
  62. ccxt/btcalpha.py +1 -1
  63. ccxt/bybit.py +1 -1
  64. ccxt/coinbase.py +104 -89
  65. ccxt/coinex.py +1 -1
  66. ccxt/coinlist.py +1 -1
  67. ccxt/coinmetro.py +1 -1
  68. ccxt/deribit.py +1 -1
  69. ccxt/digifinex.py +3 -2
  70. ccxt/gate.py +1 -1
  71. ccxt/hitbtc.py +1 -1
  72. ccxt/htx.py +1 -1
  73. ccxt/hyperliquid.py +2 -2
  74. ccxt/kraken.py +2 -1
  75. ccxt/krakenfutures.py +1 -1
  76. ccxt/kucoin.py +1 -1
  77. ccxt/kucoinfutures.py +1 -1
  78. ccxt/latoken.py +1 -1
  79. ccxt/lykke.py +1 -1
  80. ccxt/mexc.py +10 -1
  81. ccxt/ndax.py +1 -1
  82. ccxt/novadax.py +1 -1
  83. ccxt/okcoin.py +1 -1
  84. ccxt/okx.py +1 -1
  85. ccxt/paymium.py +1 -1
  86. ccxt/phemex.py +1 -1
  87. ccxt/poloniex.py +1 -1
  88. ccxt/pro/__init__.py +3 -1
  89. ccxt/pro/hyperliquid.py +524 -0
  90. ccxt/pro/luno.py +1 -1
  91. ccxt/test/base/test_balance.py +3 -0
  92. ccxt/test/base/test_market.py +1 -1
  93. ccxt/test/base/test_order_book.py +1 -0
  94. ccxt/test/base/test_shared_methods.py +12 -2
  95. ccxt/test/base/test_trade.py +1 -1
  96. ccxt/test/test_async.py +78 -39
  97. ccxt/test/test_sync.py +78 -39
  98. ccxt/wazirx.py +1 -1
  99. ccxt/whitebit.py +1 -1
  100. ccxt/woo.py +1 -1
  101. ccxt/zonda.py +1 -1
  102. {ccxt-4.2.70.dist-info → ccxt-4.2.71.dist-info}/METADATA +5 -5
  103. {ccxt-4.2.70.dist-info → ccxt-4.2.71.dist-info}/RECORD +105 -104
  104. {ccxt-4.2.70.dist-info → ccxt-4.2.71.dist-info}/WHEEL +0 -0
  105. {ccxt-4.2.70.dist-info → ccxt-4.2.71.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,524 @@
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, ArrayCacheBySymbolById, ArrayCacheByTimestamp
8
+ from ccxt.base.types import Int, Market, Order, OrderBook, Str, Trade
9
+ from ccxt.async_support.base.ws.client import Client
10
+ from typing import List
11
+ from ccxt.base.errors import ExchangeError
12
+
13
+
14
+ class hyperliquid(ccxt.async_support.hyperliquid):
15
+
16
+ def describe(self):
17
+ return self.deep_extend(super(hyperliquid, self).describe(), {
18
+ 'has': {
19
+ 'ws': True,
20
+ 'watchBalance': False,
21
+ 'watchMyTrades': True,
22
+ 'watchOHLCV': True,
23
+ 'watchOrderBook': True,
24
+ 'watchOrders': True,
25
+ 'watchTicker': False,
26
+ 'watchTickers': False,
27
+ 'watchTrades': True,
28
+ 'watchPosition': False,
29
+ },
30
+ 'urls': {
31
+ 'api': {
32
+ 'ws': {
33
+ 'public': 'wss://api.hyperliquid.xyz/ws',
34
+ },
35
+ },
36
+ 'test': {
37
+ 'ws': {
38
+ 'public': 'wss://api.hyperliquid-testnet.xyz/ws',
39
+ },
40
+ },
41
+ },
42
+ 'options': {
43
+ },
44
+ 'streaming': {
45
+ 'ping': self.ping,
46
+ 'keepAlive': 20000,
47
+ },
48
+ 'exceptions': {
49
+ 'ws': {
50
+ 'exact': {
51
+ },
52
+ },
53
+ },
54
+ })
55
+
56
+ async def watch_order_book(self, symbol: str, limit: Int = None, params={}) -> OrderBook:
57
+ """
58
+ watches information on open orders with bid(buy) and ask(sell) prices, volumes and other data
59
+ :param str symbol: unified symbol of the market to fetch the order book for
60
+ :param int [limit]: the maximum amount of order book entries to return
61
+ :param dict [params]: extra parameters specific to the exchange API endpoint
62
+ :returns dict: A dictionary of `order book structures <https://docs.ccxt.com/#/?id=order-book-structure>` indexed by market symbols
63
+ """
64
+ await self.load_markets()
65
+ market = self.market(symbol)
66
+ symbol = market['symbol']
67
+ messageHash = 'orderbook:' + symbol
68
+ url = self.urls['api']['ws']['public']
69
+ request = {
70
+ 'method': 'subscribe',
71
+ 'subscription': {
72
+ 'type': 'l2Book',
73
+ 'coin': market['base'],
74
+ },
75
+ }
76
+ message = self.extend(request, params)
77
+ orderbook = await self.watch(url, messageHash, message, messageHash)
78
+ return orderbook.limit()
79
+
80
+ def handle_order_book(self, client, message):
81
+ #
82
+ # {
83
+ # "channel": "l2Book",
84
+ # "data": {
85
+ # "coin": "BTC",
86
+ # "time": 1710131872708,
87
+ # "levels": [
88
+ # [
89
+ # {
90
+ # "px": "68674.0",
91
+ # "sz": "0.97139",
92
+ # "n": 4
93
+ # }
94
+ # ],
95
+ # [
96
+ # {
97
+ # "px": "68675.0",
98
+ # "sz": "0.04396",
99
+ # "n": 1
100
+ # }
101
+ # ]
102
+ # ]
103
+ # }
104
+ # }
105
+ #
106
+ entry = self.safe_dict(message, 'data', {})
107
+ coin = self.safe_string(entry, 'coin')
108
+ marketId = coin + '/USDC:USDC'
109
+ market = self.market(marketId)
110
+ symbol = market['symbol']
111
+ rawData = self.safe_list(entry, 'levels', [])
112
+ data = {
113
+ 'bids': self.safe_list(rawData, 0, []),
114
+ 'asks': self.safe_list(rawData, 1, []),
115
+ }
116
+ timestamp = self.safe_integer(entry, 'time')
117
+ snapshot = self.parse_order_book(data, symbol, timestamp, 'bids', 'asks', 'px', 'sz')
118
+ if not (symbol in self.orderbooks):
119
+ ob = self.order_book(snapshot)
120
+ self.orderbooks[symbol] = ob
121
+ orderbook = self.orderbooks[symbol]
122
+ orderbook.reset(snapshot)
123
+ messageHash = 'orderbook:' + symbol
124
+ client.resolve(orderbook, messageHash)
125
+
126
+ async def watch_my_trades(self, symbol: Str = None, since: Int = None, limit: Int = None, params={}) -> List[Trade]:
127
+ """
128
+ watches information on multiple trades made by the user
129
+ :param str symbol: unified market symbol of the market orders were made in
130
+ :param int [since]: the earliest time in ms to fetch orders for
131
+ :param int [limit]: the maximum number of order structures to retrieve
132
+ :param dict [params]: extra parameters specific to the exchange API endpoint
133
+ :param str [params.user]: user address, will default to self.walletAddress if not provided
134
+ :returns dict[]: a list of [order structures]{@link https://docs.ccxt.com/#/?id=order-structure
135
+ """
136
+ userAddress = None
137
+ userAddress, params = self.handlePublicAddress('watchMyTrades', params)
138
+ await self.load_markets()
139
+ messageHash = 'myTrades'
140
+ if symbol is not None:
141
+ symbol = self.symbol(symbol)
142
+ messageHash += ':' + symbol
143
+ url = self.urls['api']['ws']['public']
144
+ request = {
145
+ 'method': 'subscribe',
146
+ 'subscription': {
147
+ 'type': 'userFills',
148
+ 'user': userAddress,
149
+ },
150
+ }
151
+ message = self.extend(request, params)
152
+ trades = await self.watch(url, messageHash, message, messageHash)
153
+ if self.newUpdates:
154
+ limit = trades.getLimit(symbol, limit)
155
+ return self.filter_by_symbol_since_limit(trades, symbol, since, limit, True)
156
+
157
+ def handle_my_trades(self, client: Client, message):
158
+ #
159
+ # {
160
+ # "channel": "userFills",
161
+ # "data": {
162
+ # "isSnapshot": True,
163
+ # "user": "0x15f43d1f2dee81424afd891943262aa90f22cc2a",
164
+ # "fills": [
165
+ # {
166
+ # "coin": "BTC",
167
+ # "px": "72528.0",
168
+ # "sz": "0.11693",
169
+ # "side": "A",
170
+ # "time": 1710208712815,
171
+ # "startPosition": "0.11693",
172
+ # "dir": "Close Long",
173
+ # "closedPnl": "-0.81851",
174
+ # "hash": "0xc5adaf35f8402750c218040b0a7bc301130051521273b6f398b3caad3e1f3f5f",
175
+ # "oid": 7484888874,
176
+ # "crossed": True,
177
+ # "fee": "2.968244",
178
+ # "liquidationMarkPx": null,
179
+ # "tid": 567547935839686,
180
+ # "cloid": null
181
+ # }
182
+ # ]
183
+ # }
184
+ # }
185
+ #
186
+ entry = self.safe_dict(message, 'data', {})
187
+ if self.myTrades is None:
188
+ limit = self.safe_integer(self.options, 'tradesLimit', 1000)
189
+ self.myTrades = ArrayCacheBySymbolById(limit)
190
+ trades = self.myTrades
191
+ symbols = {}
192
+ data = self.safe_list(entry, 'fills', [])
193
+ dataLength = len(data)
194
+ if dataLength == 0:
195
+ return
196
+ for i in range(0, len(data)):
197
+ rawTrade = data[i]
198
+ parsed = self.parse_ws_trade(rawTrade)
199
+ symbol = parsed['symbol']
200
+ symbols[symbol] = True
201
+ trades.append(parsed)
202
+ keys = list(symbols.keys())
203
+ for i in range(0, len(keys)):
204
+ currentMessageHash = 'myTrades:' + keys[i]
205
+ client.resolve(trades, currentMessageHash)
206
+ # non-symbol specific
207
+ messageHash = 'myTrades'
208
+ client.resolve(trades, messageHash)
209
+
210
+ async def watch_trades(self, symbol: str, since: Int = None, limit: Int = None, params={}) -> List[Trade]:
211
+ """
212
+ watches information on multiple trades made in a market
213
+ :param str symbol: unified market symbol of the market trades were made in
214
+ :param int [since]: the earliest time in ms to fetch trades for
215
+ :param int [limit]: the maximum number of trade structures to retrieve
216
+ :param dict [params]: extra parameters specific to the exchange API endpoint
217
+ :returns dict[]: a list of [trade structures]{@link https://docs.ccxt.com/#/?id=trade-structure
218
+ """
219
+ await self.load_markets()
220
+ market = self.market(symbol)
221
+ symbol = market['symbol']
222
+ messageHash = 'trade:' + symbol
223
+ url = self.urls['api']['ws']['public']
224
+ request = {
225
+ 'method': 'subscribe',
226
+ 'subscription': {
227
+ 'type': 'trades',
228
+ 'coin': market['base'],
229
+ },
230
+ }
231
+ message = self.extend(request, params)
232
+ trades = await self.watch(url, messageHash, message, messageHash)
233
+ if self.newUpdates:
234
+ limit = trades.getLimit(symbol, limit)
235
+ return self.filter_by_since_limit(trades, since, limit, 'timestamp', True)
236
+
237
+ def handle_trades(self, client: Client, message):
238
+ #
239
+ # {
240
+ # "channel": "trades",
241
+ # "data": [
242
+ # {
243
+ # "coin": "BTC",
244
+ # "side": "A",
245
+ # "px": "68517.0",
246
+ # "sz": "0.005",
247
+ # "time": 1710125266669,
248
+ # "hash": "0xc872699f116e012186620407fc08a802015e0097c5cce74710697f7272e6e959",
249
+ # "tid": 981894269203506
250
+ # }
251
+ # ]
252
+ # }
253
+ #
254
+ entry = self.safe_list(message, 'data', [])
255
+ first = self.safe_dict(entry, 0, {})
256
+ coin = self.safe_string(first, 'coin')
257
+ marketId = coin + '/USDC:USDC'
258
+ market = self.market(marketId)
259
+ symbol = market['symbol']
260
+ if not (symbol in self.trades):
261
+ limit = self.safe_integer(self.options, 'tradesLimit', 1000)
262
+ stored = ArrayCache(limit)
263
+ self.trades[symbol] = stored
264
+ trades = self.trades[symbol]
265
+ for i in range(0, len(entry)):
266
+ data = self.safe_dict(entry, i)
267
+ trade = self.parse_ws_trade(data)
268
+ trades.append(trade)
269
+ messageHash = 'trade:' + symbol
270
+ client.resolve(trades, messageHash)
271
+
272
+ def parse_ws_trade(self, trade, market: Market = None) -> Trade:
273
+ #
274
+ # fetchMyTrades
275
+ #
276
+ # {
277
+ # "coin": "BTC",
278
+ # "px": "72528.0",
279
+ # "sz": "0.11693",
280
+ # "side": "A",
281
+ # "time": 1710208712815,
282
+ # "startPosition": "0.11693",
283
+ # "dir": "Close Long",
284
+ # "closedPnl": "-0.81851",
285
+ # "hash": "0xc5adaf35f8402750c218040b0a7bc301130051521273b6f398b3caad3e1f3f5f",
286
+ # "oid": 7484888874,
287
+ # "crossed": True,
288
+ # "fee": "2.968244",
289
+ # "liquidationMarkPx": null,
290
+ # "tid": 567547935839686,
291
+ # "cloid": null
292
+ # }
293
+ #
294
+ # fetchTrades
295
+ #
296
+ # {
297
+ # "coin": "BTC",
298
+ # "side": "A",
299
+ # "px": "68517.0",
300
+ # "sz": "0.005",
301
+ # "time": 1710125266669,
302
+ # "hash": "0xc872699f116e012186620407fc08a802015e0097c5cce74710697f7272e6e959",
303
+ # "tid": 981894269203506
304
+ # }
305
+ #
306
+ timestamp = self.safe_integer(trade, 'time')
307
+ price = self.safe_string(trade, 'px')
308
+ amount = self.safe_string(trade, 'sz')
309
+ coin = self.safe_string(trade, 'coin')
310
+ marketId = coin + '/USDC:USDC'
311
+ market = self.safe_market(marketId, None)
312
+ symbol = market['symbol']
313
+ id = self.safe_string(trade, 'tid')
314
+ side = self.safe_string(trade, 'side')
315
+ if side is not None:
316
+ side = 'sell' if (side == 'A') else 'buy'
317
+ fee = self.safe_string(trade, 'fee')
318
+ return self.safe_trade({
319
+ 'info': trade,
320
+ 'timestamp': timestamp,
321
+ 'datetime': self.iso8601(timestamp),
322
+ 'symbol': symbol,
323
+ 'id': id,
324
+ 'order': None,
325
+ 'type': None,
326
+ 'side': side,
327
+ 'takerOrMaker': None,
328
+ 'price': price,
329
+ 'amount': amount,
330
+ 'cost': None,
331
+ 'fee': {'cost': fee, 'currency': 'USDC'},
332
+ }, market)
333
+
334
+ async def watch_ohlcv(self, symbol: str, timeframe='1m', since: Int = None, limit: Int = None, params={}) -> List[list]:
335
+ """
336
+ watches historical candlestick data containing the open, high, low, close price, and the volume of a market
337
+ :param str symbol: unified symbol of the market to fetch OHLCV data for
338
+ :param str timeframe: the length of time each candle represents
339
+ :param int [since]: timestamp in ms of the earliest candle to fetch
340
+ :param int [limit]: the maximum amount of candles to fetch
341
+ :param dict [params]: extra parameters specific to the exchange API endpoint
342
+ :returns int[][]: A list of candles ordered, open, high, low, close, volume
343
+ """
344
+ await self.load_markets()
345
+ market = self.market(symbol)
346
+ symbol = market['symbol']
347
+ url = self.urls['api']['ws']['public']
348
+ request = {
349
+ 'method': 'subscribe',
350
+ 'subscription': {
351
+ 'type': 'candle',
352
+ 'coin': market['base'],
353
+ 'interval': timeframe,
354
+ },
355
+ }
356
+ messageHash = 'candles:' + timeframe + ':' + symbol
357
+ message = self.extend(request, params)
358
+ ohlcv = await self.watch(url, messageHash, message, messageHash)
359
+ if self.newUpdates:
360
+ limit = ohlcv.getLimit(symbol, limit)
361
+ return self.filter_by_since_limit(ohlcv, since, limit, 0, True)
362
+
363
+ def handle_ohlcv(self, client: Client, message):
364
+ #
365
+ # {
366
+ # channel: 'candle',
367
+ # data: {
368
+ # t: 1710146280000,
369
+ # T: 1710146339999,
370
+ # s: 'BTC',
371
+ # i: '1m',
372
+ # o: '71400.0',
373
+ # c: '71411.0',
374
+ # h: '71422.0',
375
+ # l: '71389.0',
376
+ # v: '1.20407',
377
+ # n: 20
378
+ # }
379
+ # }
380
+ #
381
+ data = self.safe_dict(message, 'data', {})
382
+ base = self.safe_string(data, 's')
383
+ symbol = base + '/USDC:USDC'
384
+ timeframe = self.safe_string(data, 'i')
385
+ if not (symbol in self.ohlcvs):
386
+ self.ohlcvs[symbol] = {}
387
+ if not (timeframe in self.ohlcvs[symbol]):
388
+ limit = self.safe_integer(self.options, 'OHLCVLimit', 1000)
389
+ stored = ArrayCacheByTimestamp(limit)
390
+ self.ohlcvs[symbol][timeframe] = stored
391
+ ohlcv = self.ohlcvs[symbol][timeframe]
392
+ parsed = self.parse_ohlcv(data)
393
+ ohlcv.append(parsed)
394
+ messageHash = 'candles:' + timeframe + ':' + symbol
395
+ client.resolve(ohlcv, messageHash)
396
+
397
+ async def watch_orders(self, symbol: Str = None, since: Int = None, limit: Int = None, params={}) -> List[Order]:
398
+ """
399
+ watches information on multiple orders made by the user
400
+ :param str symbol: unified market symbol of the market orders were made in
401
+ :param int [since]: the earliest time in ms to fetch orders for
402
+ :param int [limit]: the maximum number of order structures to retrieve
403
+ :param dict [params]: extra parameters specific to the exchange API endpoint
404
+ :param str [params.user]: user address, will default to self.walletAddress if not provided
405
+ :returns dict[]: a list of [order structures]{@link https://docs.ccxt.com/#/?id=order-structure
406
+ """
407
+ await self.load_markets()
408
+ userAddress = None
409
+ userAddress, params = self.handlePublicAddress('watchOrders', params)
410
+ market = None
411
+ messageHash = 'order'
412
+ if symbol is not None:
413
+ market = self.market(symbol)
414
+ symbol = market['symbol']
415
+ messageHash = messageHash + ':' + symbol
416
+ url = self.urls['api']['ws']['public']
417
+ request = {
418
+ 'method': 'subscribe',
419
+ 'subscription': {
420
+ 'type': 'orderUpdates',
421
+ 'user': userAddress,
422
+ },
423
+ }
424
+ message = self.extend(request, params)
425
+ orders = await self.watch(url, messageHash, message, messageHash)
426
+ if self.newUpdates:
427
+ limit = orders.getLimit(symbol, limit)
428
+ return self.filter_by_symbol_since_limit(orders, symbol, since, limit, True)
429
+
430
+ def handle_order(self, client: Client, message):
431
+ #
432
+ # {
433
+ # channel: 'orderUpdates',
434
+ # data: [
435
+ # {
436
+ # order: {
437
+ # coin: 'BTC',
438
+ # side: 'B',
439
+ # limitPx: '30000.0',
440
+ # sz: '0.001',
441
+ # oid: 7456484275,
442
+ # timestamp: 1710163596492,
443
+ # origSz: '0.001'
444
+ # },
445
+ # status: 'open',
446
+ # statusTimestamp: 1710163596492
447
+ # }
448
+ # ]
449
+ # }
450
+ #
451
+ data = self.safe_list(message, 'data', [])
452
+ if self.orders is None:
453
+ limit = self.safe_integer(self.options, 'ordersLimit', 1000)
454
+ self.orders = ArrayCacheBySymbolById(limit)
455
+ dataLength = len(data)
456
+ if dataLength == 0:
457
+ return
458
+ stored = self.orders
459
+ messageHash = 'order'
460
+ marketSymbols = {}
461
+ for i in range(0, len(data)):
462
+ rawOrder = data[i]
463
+ order = self.parse_order(rawOrder)
464
+ stored.append(order)
465
+ symbol = self.safe_string(order, 'symbol')
466
+ marketSymbols[symbol] = True
467
+ keys = list(marketSymbols.keys())
468
+ for i in range(0, len(keys)):
469
+ symbol = keys[i]
470
+ innerMessageHash = messageHash + ':' + symbol
471
+ client.resolve(stored, innerMessageHash)
472
+ client.resolve(stored, messageHash)
473
+
474
+ def handle_error_message(self, client: Client, message):
475
+ #
476
+ # {
477
+ # "channel": "error",
478
+ # "data": "Error parsing JSON into valid websocket request: {\"type\": \"allMids\"}"
479
+ # }
480
+ #
481
+ channel = self.safe_string(message, 'channel', '')
482
+ ret_msg = self.safe_string(message, 'data', '')
483
+ if channel == 'error':
484
+ raise ExchangeError(self.id + ' ' + ret_msg)
485
+ else:
486
+ return False
487
+
488
+ def handle_message(self, client: Client, message):
489
+ if self.handle_error_message(client, message):
490
+ return
491
+ topic = self.safe_string(message, 'channel', '')
492
+ methods = {
493
+ 'pong': self.handle_pong,
494
+ 'trades': self.handle_trades,
495
+ 'l2Book': self.handle_order_book,
496
+ 'candle': self.handle_ohlcv,
497
+ 'orderUpdates': self.handle_order,
498
+ 'userFills': self.handle_my_trades,
499
+ }
500
+ exacMethod = self.safe_value(methods, topic)
501
+ if exacMethod is not None:
502
+ exacMethod(client, message)
503
+ return
504
+ keys = list(methods.keys())
505
+ for i in range(0, len(keys)):
506
+ key = keys[i]
507
+ if topic.find(keys[i]) >= 0:
508
+ method = methods[key]
509
+ method(client, message)
510
+ return
511
+
512
+ def ping(self, client):
513
+ return {
514
+ 'method': 'ping',
515
+ }
516
+
517
+ def handle_pong(self, client: Client, message):
518
+ #
519
+ # {
520
+ # "channel": "pong"
521
+ # }
522
+ #
523
+ client.lastPong = self.safe_integer(message, 'pong')
524
+ return message
ccxt/pro/luno.py CHANGED
@@ -5,7 +5,7 @@
5
5
 
6
6
  import ccxt.async_support
7
7
  from ccxt.async_support.base.ws.cache import ArrayCache
8
- from ccxt.base.types import Int, OrderBook, IndexType, Trade
8
+ from ccxt.base.types import IndexType, Int, OrderBook, Trade
9
9
  from ccxt.async_support.base.ws.client import Client
10
10
  from typing import List
11
11
 
@@ -28,6 +28,9 @@ def test_balance(exchange, skipped_properties, method, entry):
28
28
  codes_total = list(entry['total'].keys())
29
29
  codes_free = list(entry['free'].keys())
30
30
  codes_used = list(entry['used'].keys())
31
+ test_shared_methods.assert_non_emtpy_array(exchange, skipped_properties, method, codes_total, 'total')
32
+ test_shared_methods.assert_non_emtpy_array(exchange, skipped_properties, method, codes_free, 'free')
33
+ test_shared_methods.assert_non_emtpy_array(exchange, skipped_properties, method, codes_used, 'used')
31
34
  all_codes = exchange.array_concat(codes_total, codes_free)
32
35
  all_codes = exchange.array_concat(all_codes, codes_used)
33
36
  codes_length = len(codes_total)
@@ -183,7 +183,7 @@ def test_market(exchange, skipped_properties, method, market):
183
183
  if min_string is not None:
184
184
  test_shared_methods.assert_greater_or_equal(exchange, skipped_properties, method, limit_entry, 'max', min_string)
185
185
  # check whether valid currency ID and CODE is used
186
- if not ('currencyIdAndCode' in skipped_properties):
186
+ if not ('currency' in skipped_properties) and not ('currencyIdAndCode' in skipped_properties):
187
187
  test_shared_methods.assert_valid_currency_id_and_code(exchange, skipped_properties, method, market, market['baseId'], market['base'])
188
188
  test_shared_methods.assert_valid_currency_id_and_code(exchange, skipped_properties, method, market, market['quoteId'], market['quote'])
189
189
  test_shared_methods.assert_valid_currency_id_and_code(exchange, skipped_properties, method, market, market['settleId'], market['settle'])
@@ -32,6 +32,7 @@ def test_order_book(exchange, skipped_properties, method, orderbook, symbol):
32
32
  #
33
33
  if ('bid' in skipped_properties) or ('ask' in skipped_properties):
34
34
  return
35
+ # todo: check non-emtpy arrays for bids/asks for toptier exchanges
35
36
  bids = orderbook['bids']
36
37
  bids_length = len(bids)
37
38
  for i in range(0, bids_length):
@@ -147,7 +147,7 @@ def assert_timestamp_and_datetime(exchange, skipped_properties, method, entry, n
147
147
 
148
148
 
149
149
  def assert_currency_code(exchange, skipped_properties, method, entry, actual_code, expected_code=None):
150
- if 'currency' in skipped_properties:
150
+ if ('currency' in skipped_properties) or ('currencyIdAndCode' in skipped_properties):
151
151
  return
152
152
  log_text = log_template(exchange, method, entry)
153
153
  if actual_code is not None:
@@ -159,7 +159,7 @@ def assert_currency_code(exchange, skipped_properties, method, entry, actual_cod
159
159
 
160
160
  def assert_valid_currency_id_and_code(exchange, skipped_properties, method, entry, currency_id, currency_code):
161
161
  # this is exclusive exceptional key name to be used in `skip-tests.json`, to skip check for currency id and code
162
- if 'currencyIdAndCode' in skipped_properties:
162
+ if ('currency' in skipped_properties) or ('currencyIdAndCode' in skipped_properties):
163
163
  return
164
164
  log_text = log_template(exchange, method, entry)
165
165
  undefined_values = currency_id is None and currency_code is None
@@ -333,3 +333,13 @@ def set_proxy_options(exchange, skipped_properties, proxy_url, http_proxy, https
333
333
  exchange.http_proxy = http_proxy
334
334
  exchange.https_proxy = https_proxy
335
335
  exchange.socks_proxy = socks_proxy
336
+
337
+
338
+ def assert_non_emtpy_array(exchange, skipped_properties, method, entry, hint=None):
339
+ log_text = log_template(exchange, method, entry)
340
+ if hint is not None:
341
+ log_text = log_text + ' ' + hint
342
+ assert isinstance(entry, list), 'response is expected to be an array' + log_text
343
+ if not ('emptyResponse' in skipped_properties):
344
+ return
345
+ assert len(entry) > 0, 'response is expected to be a non-empty array' + log_text + ' (add \"emptyResponse\" in skip-tests.json to skip this check)'
@@ -41,7 +41,7 @@ def test_trade(exchange, skipped_properties, method, entry, symbol, now):
41
41
  test_shared_methods.assert_in_array(exchange, skipped_properties, method, entry, 'takerOrMaker', ['taker', 'maker'])
42
42
  test_shared_methods.assert_fee_structure(exchange, skipped_properties, method, entry, 'fee')
43
43
  if not ('fees' in skipped_properties):
44
- # todo: remove undefined check
44
+ # todo: remove undefined check and probably non-empty array check later
45
45
  if entry['fees'] is not None:
46
46
  for i in range(0, len(entry['fees'])):
47
47
  test_shared_methods.assert_fee_structure(exchange, skipped_properties, method, entry['fees'], i)