ccxt 4.3.58__py2.py3-none-any.whl → 4.3.60__py2.py3-none-any.whl

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

Potentially problematic release.


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

Files changed (84) hide show
  1. ccxt/__init__.py +5 -5
  2. ccxt/abstract/bingx.py +1 -1
  3. ccxt/abstract/bitmart.py +1 -0
  4. ccxt/abstract/btcbox.py +1 -0
  5. ccxt/abstract/upbit.py +3 -0
  6. ccxt/abstract/xt.py +1 -0
  7. ccxt/async_support/__init__.py +5 -5
  8. ccxt/async_support/base/exchange.py +1 -1
  9. ccxt/async_support/binance.py +91 -2
  10. ccxt/async_support/bingx.py +448 -123
  11. ccxt/async_support/bitfinex.py +38 -4
  12. ccxt/async_support/bitget.py +1 -1
  13. ccxt/async_support/bitmart.py +1 -0
  14. ccxt/async_support/bitso.py +4 -1
  15. ccxt/async_support/btcbox.py +145 -8
  16. ccxt/async_support/bybit.py +3 -5
  17. ccxt/async_support/cex.py +1 -1
  18. ccxt/async_support/coinsph.py +1 -1
  19. ccxt/async_support/cryptocom.py +11 -5
  20. ccxt/async_support/deribit.py +15 -1
  21. ccxt/async_support/digifinex.py +30 -7
  22. ccxt/async_support/gate.py +17 -18
  23. ccxt/async_support/htx.py +7 -7
  24. ccxt/async_support/hyperliquid.py +107 -3
  25. ccxt/async_support/kraken.py +2 -2
  26. ccxt/async_support/mexc.py +11 -11
  27. ccxt/async_support/novadax.py +1 -1
  28. ccxt/async_support/okcoin.py +1 -1
  29. ccxt/async_support/okx.py +11 -2
  30. ccxt/async_support/phemex.py +1 -1
  31. ccxt/async_support/probit.py +1 -1
  32. ccxt/async_support/timex.py +16 -2
  33. ccxt/async_support/tokocrypto.py +1 -1
  34. ccxt/async_support/upbit.py +139 -45
  35. ccxt/async_support/xt.py +71 -8
  36. ccxt/base/errors.py +23 -23
  37. ccxt/base/exchange.py +9 -9
  38. ccxt/binance.py +91 -2
  39. ccxt/bingx.py +448 -123
  40. ccxt/bitfinex.py +38 -4
  41. ccxt/bitget.py +1 -1
  42. ccxt/bitmart.py +1 -0
  43. ccxt/bitso.py +4 -1
  44. ccxt/btcbox.py +145 -8
  45. ccxt/bybit.py +3 -5
  46. ccxt/cex.py +1 -1
  47. ccxt/coinsph.py +1 -1
  48. ccxt/cryptocom.py +11 -5
  49. ccxt/deribit.py +15 -1
  50. ccxt/digifinex.py +30 -7
  51. ccxt/gate.py +17 -18
  52. ccxt/htx.py +7 -7
  53. ccxt/hyperliquid.py +107 -3
  54. ccxt/kraken.py +2 -2
  55. ccxt/mexc.py +11 -11
  56. ccxt/novadax.py +1 -1
  57. ccxt/okcoin.py +1 -1
  58. ccxt/okx.py +11 -2
  59. ccxt/phemex.py +1 -1
  60. ccxt/pro/__init__.py +3 -1
  61. ccxt/pro/binance.py +11 -13
  62. ccxt/pro/bingx.py +11 -8
  63. ccxt/pro/bitmart.py +2 -2
  64. ccxt/pro/bitopro.py +1 -1
  65. ccxt/pro/cex.py +1 -1
  66. ccxt/pro/coincheck.py +1 -1
  67. ccxt/pro/coinone.py +1 -1
  68. ccxt/pro/hyperliquid.py +1 -1
  69. ccxt/pro/kucoin.py +35 -3
  70. ccxt/pro/phemex.py +1 -1
  71. ccxt/pro/xt.py +1046 -0
  72. ccxt/probit.py +1 -1
  73. ccxt/test/tests_async.py +2 -2
  74. ccxt/test/tests_helpers.py +1 -1
  75. ccxt/test/tests_sync.py +2 -2
  76. ccxt/timex.py +16 -2
  77. ccxt/tokocrypto.py +1 -1
  78. ccxt/upbit.py +139 -45
  79. ccxt/xt.py +71 -8
  80. {ccxt-4.3.58.dist-info → ccxt-4.3.60.dist-info}/METADATA +5 -5
  81. {ccxt-4.3.58.dist-info → ccxt-4.3.60.dist-info}/RECORD +84 -83
  82. {ccxt-4.3.58.dist-info → ccxt-4.3.60.dist-info}/LICENSE.txt +0 -0
  83. {ccxt-4.3.58.dist-info → ccxt-4.3.60.dist-info}/WHEEL +0 -0
  84. {ccxt-4.3.58.dist-info → ccxt-4.3.60.dist-info}/top_level.txt +0 -0
ccxt/pro/xt.py ADDED
@@ -0,0 +1,1046 @@
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 Balances, Int, Market, Order, OrderBook, Str, Strings, Ticker, Tickers, Trade
9
+ from ccxt.async_support.base.ws.client import Client
10
+ from typing import List
11
+
12
+
13
+ class xt(ccxt.async_support.xt):
14
+
15
+ def describe(self):
16
+ return self.deep_extend(super(xt, self).describe(), {
17
+ 'has': {
18
+ 'ws': True,
19
+ 'watchOHLCV': True,
20
+ 'watchOrderBook': True,
21
+ 'watchTicker': True,
22
+ 'watchTickers': True,
23
+ 'watchTrades': True,
24
+ 'watchBalance': True,
25
+ 'watchOrders': True,
26
+ 'watchMyTrades': True,
27
+ 'watchPositions': None, # TODO https://doc.xt.com/#futures_user_websocket_v2position
28
+ },
29
+ 'urls': {
30
+ 'api': {
31
+ 'ws': {
32
+ 'spot': 'wss://stream.xt.com',
33
+ 'contract': 'wss://fstream.xt.com/ws',
34
+ },
35
+ },
36
+ },
37
+ 'options': {
38
+ 'tradesLimit': 1000,
39
+ 'ordersLimit': 1000,
40
+ 'OHLCVLimit': 1000,
41
+ 'watchTicker': {
42
+ 'method': 'ticker', # agg_ticker(contract only)
43
+ },
44
+ 'watchTickers': {
45
+ 'method': 'tickers', # agg_tickers(contract only)
46
+ },
47
+ },
48
+ 'streaming': {
49
+ 'keepAlive': 20000,
50
+ 'ping': self.ping,
51
+ },
52
+ 'token': None,
53
+ })
54
+
55
+ async def get_listen_key(self, isContract: bool):
56
+ """
57
+ * @ignore
58
+ required for private endpoints
59
+ :param str isContract: True for contract trades
60
+ :see: https://doc.xt.com/#websocket_privategetToken
61
+ :see: https://doc.xt.com/#futures_user_websocket_v2base
62
+ :returns str: listen key / access token
63
+ """
64
+ self.check_required_credentials()
65
+ tradeType = 'contract' if isContract else 'spot'
66
+ url = self.urls['api']['ws'][tradeType]
67
+ if not isContract:
68
+ url = url + '/private'
69
+ client = self.client(url)
70
+ token = self.safe_dict(client.subscriptions, 'token')
71
+ if token is None:
72
+ if isContract:
73
+ response = await self.privateLinearGetFutureUserV1UserListenKey()
74
+ #
75
+ # {
76
+ # returnCode: '0',
77
+ # msgInfo: 'success',
78
+ # error: null,
79
+ # result: '3BC1D71D6CF96DA3458FC35B05B633351684511731128'
80
+ # }
81
+ #
82
+ client.subscriptions['token'] = self.safe_string(response, 'result')
83
+ else:
84
+ response = await self.privateSpotPostWsToken()
85
+ #
86
+ # {
87
+ # "rc": 0,
88
+ # "mc": "SUCCESS",
89
+ # "ma": [],
90
+ # "result": {
91
+ # "token": "eyJhbqGciOiJSUzI1NiJ9.eyJhY2NvdW50SWQiOiIyMTQ2Mjg1MzIyNTU5Iiwic3ViIjoibGh4dDRfMDAwMUBzbmFwbWFpbC5jYyIsInNjb3BlIjoiYXV0aCIsImlzcyI6Inh0LmNvbSIsImxhc3RBdXRoVGltZSI6MTY2MzgxMzY5MDk1NSwic2lnblR5cGUiOiJBSyIsInVzZXJOYW1lIjoibGh4dDRfMDAwMUBzbmFwbWFpbC5jYyIsImV4cCI6MTY2NjQwNTY5MCwiZGV2aWNlIjoidW5rbm93biIsInVzZXJJZCI6MjE0NjI4NTMyMjU1OX0.h3zJlJBQrK2x1HvUxsKivnn6PlSrSDXXXJ7WqHAYSrN2CG5XPTKc4zKnTVoYFbg6fTS0u1fT8wH7wXqcLWXX71vm0YuP8PCvdPAkUIq4-HyzltbPr5uDYd0UByx0FPQtq1exvsQGe7evXQuDXx3SEJXxEqUbq_DNlXPTq_JyScI",
92
+ # "refreshToken": "eyJhbGciOiqJSUzI1NiJ9.eyJhY2NvdW50SWQiOiIyMTQ2Mjg1MzIyNTU5Iiwic3ViIjoibGh4dDRfMDAwMUBzbmFwbWFpbC5jYyIsInNjb3BlIjoicmVmcmVzaCIsImlzcyI6Inh0LmNvbSIsImxhc3RBdXRoVGltZSI6MTY2MzgxMzY5MDk1NSwic2lnblR5cGUiOiJBSyIsInVzZXJOYW1lIjoibGh4dDRfMDAwMUBzbmFwbWFpbC5jYyIsImV4cCI6MTY2NjQwNTY5MCwiZGV2aWNlIjoidW5rbm93biIsInVzZXJJZCI6MjE0NjI4NTMyMjU1OX0.Fs3YVm5YrEOzzYOSQYETSmt9iwxUHBovh2u73liv1hLUec683WGfktA_s28gMk4NCpZKFeQWFii623FvdfNoteXR0v1yZ2519uNvNndtuZICDdv3BQ4wzW1wIHZa1skxFfqvsDnGdXpjqu9UFSbtHwxprxeYfnxChNk4ssei430"
93
+ # }
94
+ # }
95
+ #
96
+ result = self.safe_dict(response, 'result')
97
+ client.subscriptions['token'] = self.safe_string(result, 'accessToken')
98
+ return client.subscriptions['token']
99
+
100
+ def get_cache_index(self, orderbook, cache):
101
+ # return the first index of the cache that can be applied to the orderbook or -1 if not possible
102
+ nonce = self.safe_integer(orderbook, 'nonce')
103
+ firstDelta = self.safe_value(cache, 0)
104
+ firstDeltaNonce = self.safe_integer_2(firstDelta, 'i', 'u')
105
+ if nonce < firstDeltaNonce - 1:
106
+ return -1
107
+ for i in range(0, len(cache)):
108
+ delta = cache[i]
109
+ deltaNonce = self.safe_integer_2(delta, 'i', 'u')
110
+ if deltaNonce >= nonce:
111
+ return i
112
+ return len(cache)
113
+
114
+ def handle_delta(self, orderbook, delta):
115
+ orderbook['nonce'] = self.safe_integer_2(delta, 'i', 'u')
116
+ obAsks = self.safe_list(delta, 'a', [])
117
+ obBids = self.safe_list(delta, 'b', [])
118
+ bids = orderbook['bids']
119
+ asks = orderbook['asks']
120
+ for i in range(0, len(obBids)):
121
+ bid = obBids[i]
122
+ price = self.safe_number(bid, 0)
123
+ quantity = self.safe_number(bid, 1)
124
+ bids.store(price, quantity)
125
+ for i in range(0, len(obAsks)):
126
+ ask = obAsks[i]
127
+ price = self.safe_number(ask, 0)
128
+ quantity = self.safe_number(ask, 1)
129
+ asks.store(price, quantity)
130
+ # self.handleBidAsks(storedBids, bids)
131
+ # self.handleBidAsks(storedAsks, asks)
132
+
133
+ async def subscribe(self, name: str, access: str, methodName: str, market: Market = None, symbols: List[str] = None, params={}):
134
+ """
135
+ * @ignore
136
+ Connects to a websocket channel
137
+ :see: https://doc.xt.com/#websocket_privaterequestFormat
138
+ :see: https://doc.xt.com/#futures_market_websocket_v2base
139
+ :param str name: name of the channel
140
+ :param str access: public or private
141
+ :param str methodName: the name of the CCXT class method
142
+ :param dict [market]: CCXT market
143
+ :param str[] [symbols]: unified market symbols
144
+ :param dict params: extra parameters specific to the xt api
145
+ :returns dict: data from the websocket stream
146
+ """
147
+ privateAccess = access == 'private'
148
+ type = None
149
+ type, params = self.handle_market_type_and_params(methodName, market, params)
150
+ isContract = (type != 'spot')
151
+ subscribe = {
152
+ 'method': 'SUBSCRIBE' if isContract else 'subscribe',
153
+ 'id': self.number_to_string(self.milliseconds()) + name, # call back ID
154
+ }
155
+ if privateAccess:
156
+ if not isContract:
157
+ subscribe['params'] = [name]
158
+ subscribe['listenKey'] = await self.get_listen_key(isContract)
159
+ else:
160
+ listenKey = await self.get_listen_key(isContract)
161
+ param = name + '@' + listenKey
162
+ subscribe['params'] = [param]
163
+ else:
164
+ subscribe['params'] = [name]
165
+ tradeType = 'contract' if isContract else 'spot'
166
+ messageHash = name + '::' + tradeType
167
+ if symbols is not None:
168
+ messageHash = messageHash + '::' + ','.join(symbols)
169
+ request = self.extend(subscribe, params)
170
+ tail = access
171
+ if isContract:
172
+ tail = 'user' if privateAccess else 'market'
173
+ url = self.urls['api']['ws'][tradeType] + '/' + tail
174
+ return await self.watch(url, messageHash, request, messageHash)
175
+
176
+ async def watch_ticker(self, symbol: str, params={}) -> Ticker:
177
+ """
178
+ watches a price ticker, a statistical calculation with the information calculated over the past 24 hours for a specific market
179
+ :see: https://doc.xt.com/#websocket_publictickerRealTime
180
+ :see: https://doc.xt.com/#futures_market_websocket_v2tickerRealTime
181
+ :see: https://doc.xt.com/#futures_market_websocket_v2aggTickerRealTime
182
+ :param str symbol: unified symbol of the market to fetch the ticker for
183
+ :param dict params: extra parameters specific to the xt api endpoint
184
+ :param str [params.method]: 'agg_ticker'(contract only) or 'ticker', default = 'ticker' - the endpoint that will be streamed
185
+ :returns dict: a `ticker structure <https://docs.ccxt.com/en/latest/manual.html#ticker-structure>`
186
+ """
187
+ await self.load_markets()
188
+ market = self.market(symbol)
189
+ options = self.safe_dict(self.options, 'watchTicker')
190
+ defaultMethod = self.safe_string(options, 'method', 'ticker')
191
+ method = self.safe_string(params, 'method', defaultMethod)
192
+ name = method + '@' + market['id']
193
+ return await self.subscribe(name, 'public', 'watchTicker', market, None, params)
194
+
195
+ async def watch_tickers(self, symbols: Strings = None, params={}) -> Tickers:
196
+ """
197
+ watches a price ticker, a statistical calculation with the information calculated over the past 24 hours for a specific market
198
+ :see: https://doc.xt.com/#websocket_publicallTicker
199
+ :see: https://doc.xt.com/#futures_market_websocket_v2allTicker
200
+ :see: https://doc.xt.com/#futures_market_websocket_v2allAggTicker
201
+ :param str [symbols]: unified market symbols
202
+ :param dict params: extra parameters specific to the xt api endpoint
203
+ :param str [params.method]: 'agg_tickers'(contract only) or 'tickers', default = 'tickers' - the endpoint that will be streamed
204
+ :returns dict: a `ticker structure <https://docs.ccxt.com/en/latest/manual.html#ticker-structure>`
205
+ """
206
+ await self.load_markets()
207
+ options = self.safe_dict(self.options, 'watchTickers')
208
+ defaultMethod = self.safe_string(options, 'method', 'tickers')
209
+ name = self.safe_string(params, 'method', defaultMethod)
210
+ market = None
211
+ if symbols is not None:
212
+ market = self.market(symbols[0])
213
+ tickers = await self.subscribe(name, 'public', 'watchTickers', market, symbols, params)
214
+ if self.newUpdates:
215
+ return tickers
216
+ return self.filter_by_array(self.tickers, 'symbol', symbols)
217
+
218
+ async def watch_ohlcv(self, symbol: str, timeframe='1m', since: Int = None, limit: Int = None, params={}) -> List[list]:
219
+ """
220
+ watches historical candlestick data containing the open, high, low, and close price, and the volume of a market
221
+ :see: https://doc.xt.com/#websocket_publicsymbolKline
222
+ :see: https://doc.xt.com/#futures_market_websocket_v2symbolKline
223
+ :param str symbol: unified symbol of the market to fetch OHLCV data for
224
+ :param str timeframe: 1m, 3m, 5m, 15m, 30m, 1h, 2h, 4h, 6h, 8h, 12h, 1d, 3d, 1w, or 1M
225
+ :param int [since]: not used by xt watchOHLCV
226
+ :param int [limit]: not used by xt watchOHLCV
227
+ :param dict params: extra parameters specific to the xt api endpoint
228
+ :returns int[][]: A list of candles ordered, open, high, low, close, volume
229
+ """
230
+ await self.load_markets()
231
+ market = self.market(symbol)
232
+ name = 'kline@' + market['id'] + ',' + timeframe
233
+ ohlcv = await self.subscribe(name, 'public', 'watchOHLCV', market, None, params)
234
+ if self.newUpdates:
235
+ limit = ohlcv.getLimit(symbol, limit)
236
+ return self.filter_by_since_limit(ohlcv, since, limit, 0, True)
237
+
238
+ async def watch_trades(self, symbol: str, since: Int = None, limit: Int = None, params={}) -> List[Trade]:
239
+ """
240
+ get the list of most recent trades for a particular symbol
241
+ :see: https://doc.xt.com/#websocket_publicdealRecord
242
+ :see: https://doc.xt.com/#futures_market_websocket_v2dealRecord
243
+ :param str symbol: unified symbol of the market to fetch trades for
244
+ :param int [since]: timestamp in ms of the earliest trade to fetch
245
+ :param int [limit]: the maximum amount of trades to fetch
246
+ :param dict params: extra parameters specific to the xt api endpoint
247
+ :returns dict[]: a list of `trade structures <https://docs.ccxt.com/en/latest/manual.html?#public-trades>`
248
+ """
249
+ await self.load_markets()
250
+ market = self.market(symbol)
251
+ name = 'trade@' + market['id']
252
+ trades = await self.subscribe(name, 'public', 'watchTrades', market, None, params)
253
+ if self.newUpdates:
254
+ limit = trades.getLimit(symbol, limit)
255
+ return self.filter_by_since_limit(trades, since, limit, 'timestamp')
256
+
257
+ async def watch_order_book(self, symbol: str, limit: Int = None, params={}) -> OrderBook:
258
+ """
259
+ watches information on open orders with bid(buy) and ask(sell) prices, volumes and other data
260
+ :see: https://doc.xt.com/#websocket_publiclimitDepth
261
+ :see: https://doc.xt.com/#websocket_publicincreDepth
262
+ :see: https://doc.xt.com/#futures_market_websocket_v2limitDepth
263
+ :see: https://doc.xt.com/#futures_market_websocket_v2increDepth
264
+ :param str symbol: unified symbol of the market to fetch the order book for
265
+ :param int [limit]: not used by xt watchOrderBook
266
+ :param dict params: extra parameters specific to the xt api endpoint
267
+ :param int [params.levels]: 5, 10, 20, or 50
268
+ :returns dict: A dictionary of `order book structures <https://docs.ccxt.com/en/latest/manual.html#order-book-structure>` indexed by market symbols
269
+ """
270
+ await self.load_markets()
271
+ market = self.market(symbol)
272
+ levels = self.safe_string(params, 'levels')
273
+ params = self.omit(params, 'levels')
274
+ name = 'depth_update@' + market['id']
275
+ if levels is not None:
276
+ name = 'depth@' + market['id'] + ',' + levels
277
+ orderbook = await self.subscribe(name, 'public', 'watchOrderBook', market, None, params)
278
+ return orderbook.limit()
279
+
280
+ async def watch_orders(self, symbol: Str = None, since: Int = None, limit: Int = None, params={}) -> List[Order]:
281
+ """
282
+ watches information on multiple orders made by the user
283
+ :see: https://doc.xt.com/#websocket_privateorderChange
284
+ :see: https://doc.xt.com/#futures_user_websocket_v2order
285
+ :param str [symbol]: unified market symbol
286
+ :param int [since]: not used by xt watchOrders
287
+ :param int [limit]: the maximum number of orders to return
288
+ :param dict params: extra parameters specific to the xt api endpoint
289
+ :returns dict[]: a list of `order structures <https://docs.ccxt.com/en/latest/manual.html#order-structure>`
290
+ """
291
+ await self.load_markets()
292
+ name = 'order'
293
+ market = None
294
+ if symbol is not None:
295
+ market = self.market(symbol)
296
+ orders = await self.subscribe(name, 'private', 'watchOrders', market, None, params)
297
+ if self.newUpdates:
298
+ limit = orders.getLimit(symbol, limit)
299
+ return self.filter_by_since_limit(orders, since, limit, 'timestamp')
300
+
301
+ async def watch_my_trades(self, symbol: Str = None, since: Int = None, limit: Int = None, params={}) -> List[Trade]:
302
+ """
303
+ watches information on multiple trades made by the user
304
+ :see: https://doc.xt.com/#websocket_privateorderDeal
305
+ :see: https://doc.xt.com/#futures_user_websocket_v2trade
306
+ :param str symbol: unified market symbol of the market orders were made in
307
+ :param int [since]: the earliest time in ms to fetch orders for
308
+ :param int [limit]: the maximum number of orde structures to retrieve
309
+ :param dict params: extra parameters specific to the kucoin api endpoint
310
+ :returns dict[]: a list of `trade structures <https://docs.ccxt.com/#/?id=trade-structure>`
311
+ """
312
+ await self.load_markets()
313
+ name = 'trade'
314
+ market = None
315
+ if symbol is not None:
316
+ market = self.market(symbol)
317
+ trades = await self.subscribe(name, 'private', 'watchMyTrades', market, None, params)
318
+ if self.newUpdates:
319
+ limit = trades.getLimit(symbol, limit)
320
+ return self.filter_by_since_limit(trades, since, limit, 'timestamp')
321
+
322
+ async def watch_balance(self, params={}) -> Balances:
323
+ """
324
+ watches information on multiple orders made by the user
325
+ :see: https://doc.xt.com/#websocket_privatebalanceChange
326
+ :see: https://doc.xt.com/#futures_user_websocket_v2balance
327
+ :param dict params: extra parameters specific to the xt api endpoint
328
+ :returns dict[]: a list of `balance structures <https://docs.ccxt.com/#/?id=balance-structure>`
329
+ """
330
+ await self.load_markets()
331
+ name = 'balance'
332
+ return await self.subscribe(name, 'private', 'watchBalance', None, None, params)
333
+
334
+ def handle_ticker(self, client: Client, message: dict):
335
+ #
336
+ # spot
337
+ #
338
+ # {
339
+ # topic: 'ticker',
340
+ # event: 'ticker@btc_usdt',
341
+ # data: {
342
+ # s: 'btc_usdt', # symbol
343
+ # t: 1683501935877, # time(Last transaction time)
344
+ # cv: '-82.67', # priceChangeValue(24 hour price change)
345
+ # cr: '-0.0028', # priceChangeRate 24-hour price change(percentage)
346
+ # o: '28823.87', # open price
347
+ # c: '28741.20', # close price
348
+ # h: '29137.64', # highest price
349
+ # l: '28660.93', # lowest price
350
+ # q: '6372.601573', # quantity
351
+ # v: '184086075.2772391' # volume
352
+ # }
353
+ # }
354
+ #
355
+ # contract
356
+ #
357
+ # {
358
+ # "topic": "ticker",
359
+ # "event": "ticker@btc_usdt",
360
+ # "data": {
361
+ # "s": "btc_index", # trading pair
362
+ # "o": "49000", # opening price
363
+ # "c": "50000", # closing price
364
+ # "h": "0.1", # highest price
365
+ # "l": "0.1", # lowest price
366
+ # "a": "0.1", # volume
367
+ # "v": "0.1", # turnover
368
+ # "ch": "0.21", # quote change
369
+ # "t": 123124124 # timestamp
370
+ # }
371
+ # }
372
+ #
373
+ # agg_ticker(contract)
374
+ #
375
+ # {
376
+ # "topic": "agg_ticker",
377
+ # "event": "agg_ticker@btc_usdt",
378
+ # "data": {
379
+ # "s": "btc_index", # trading pair
380
+ # "o": "49000", # opening price
381
+ # "c": "50000", # closing price
382
+ # "h": "0.1", # highest price
383
+ # "l": "0.1", # lowest price
384
+ # "a": "0.1", # volume
385
+ # "v": "0.1", # turnover
386
+ # "ch": "0.21", # quote change
387
+ # "i": "0.21" , # index price
388
+ # "m": "0.21", # mark price
389
+ # "bp": "0.21", # bid price
390
+ # "ap": "0.21" , # ask price
391
+ # "t": 123124124 # timestamp
392
+ # }
393
+ # }
394
+ #
395
+ data = self.safe_dict(message, 'data')
396
+ marketId = self.safe_string(data, 's')
397
+ if marketId is not None:
398
+ cv = self.safe_string(data, 'cv')
399
+ isSpot = cv is not None
400
+ ticker = self.parse_ticker(data)
401
+ symbol = ticker['symbol']
402
+ self.tickers[symbol] = ticker
403
+ event = self.safe_string(message, 'event')
404
+ messageHashTail = 'spot' if isSpot else 'contract'
405
+ messageHash = event + '::' + messageHashTail
406
+ client.resolve(ticker, messageHash)
407
+ return message
408
+
409
+ def handle_tickers(self, client: Client, message: dict):
410
+ #
411
+ # spot
412
+ #
413
+ # {
414
+ # topic: 'tickers',
415
+ # event: 'tickers',
416
+ # data: [
417
+ # {
418
+ # s: 'elon_usdt',
419
+ # t: 1683502958381,
420
+ # cv: '-0.0000000125',
421
+ # cr: '-0.0495',
422
+ # o: '0.0000002522',
423
+ # c: '0.0000002397',
424
+ # h: '0.0000002690',
425
+ # l: '0.0000002371',
426
+ # q: '3803783034.0000000000',
427
+ # v: '955.3260820022'
428
+ # },
429
+ # ...
430
+ # ]
431
+ # }
432
+ #
433
+ # contract
434
+ #
435
+ # {
436
+ # "topic": "tickers",
437
+ # "event": "tickers",
438
+ # "data": [
439
+ # {
440
+ # "s": "btc_index", # trading pair
441
+ # "o": "49000", # opening price
442
+ # "c": "50000", # closing price
443
+ # "h": "0.1", # highest price
444
+ # "l": "0.1", # lowest price
445
+ # "a": "0.1", # volume
446
+ # "v": "0.1", # turnover
447
+ # "ch": "0.21", # quote change
448
+ # "t": 123124124 # timestamp
449
+ # }
450
+ # ]
451
+ # }
452
+ #
453
+ # agg_ticker(contract)
454
+ #
455
+ # {
456
+ # "topic": "agg_tickers",
457
+ # "event": "agg_tickers",
458
+ # "data": [
459
+ # {
460
+ # "s": "btc_index", # trading pair
461
+ # "o": "49000", # opening price
462
+ # "c": "50000", # closing price
463
+ # "h": "0.1", # highest price
464
+ # "l": "0.1", # lowest price
465
+ # "a": "0.1", # volume
466
+ # "v": "0.1", # turnover
467
+ # "ch": "0.21", # quote change
468
+ # "i": "0.21" , # index price
469
+ # "m": "0.21", # mark price
470
+ # "bp": "0.21", # bid price
471
+ # "ap": "0.21" , # ask price
472
+ # "t": 123124124 # timestamp
473
+ # }
474
+ # ]
475
+ # }
476
+ #
477
+ data = self.safe_list(message, 'data', [])
478
+ firstTicker = self.safe_dict(data, 0)
479
+ spotTest = self.safe_string_2(firstTicker, 'cv', 'aq')
480
+ tradeType = 'spot' if (spotTest is not None) else 'contract'
481
+ newTickers = []
482
+ for i in range(0, len(data)):
483
+ tickerData = data[i]
484
+ ticker = self.parse_ticker(tickerData)
485
+ symbol = ticker['symbol']
486
+ self.tickers[symbol] = ticker
487
+ newTickers.append(ticker)
488
+ messageHashStart = self.safe_string(message, 'topic') + '::' + tradeType
489
+ messageHashes = self.find_message_hashes(client, messageHashStart + '::')
490
+ for i in range(0, len(messageHashes)):
491
+ messageHash = messageHashes[i]
492
+ parts = messageHash.split('::')
493
+ symbolsString = parts[2]
494
+ symbols = symbolsString.split(',')
495
+ tickers = self.filter_by_array(newTickers, 'symbol', symbols)
496
+ tickersSymbols = list(tickers.keys())
497
+ numTickers = len(tickersSymbols)
498
+ if numTickers > 0:
499
+ client.resolve(tickers, messageHash)
500
+ client.resolve(self.tickers, messageHashStart)
501
+ return message
502
+
503
+ def handle_ohlcv(self, client: Client, message: dict):
504
+ #
505
+ # spot
506
+ #
507
+ # {
508
+ # "topic": "kline",
509
+ # "event": "kline@btc_usdt,5m",
510
+ # "data": {
511
+ # "s": "btc_usdt", # symbol
512
+ # "t": 1656043200000, # time
513
+ # "i": "5m", # interval
514
+ # "o": "44000", # open price
515
+ # "c": "50000", # close price
516
+ # "h": "52000", # highest price
517
+ # "l": "36000", # lowest price
518
+ # "q": "34.2", # qty(quantity)
519
+ # "v": "230000" # volume
520
+ # }
521
+ # }
522
+ #
523
+ # contract
524
+ #
525
+ # {
526
+ # "topic": "kline",
527
+ # "event": "kline@btc_usdt,5m",
528
+ # "data": {
529
+ # "s": "btc_index", # trading pair
530
+ # "o": "49000", # opening price
531
+ # "c": "50000", # closing price
532
+ # "h": "0.1", # highest price
533
+ # "l": "0.1", # lowest price
534
+ # "a": "0.1", # volume
535
+ # "v": "0.1", # turnover
536
+ # "ch": "0.21", # quote change
537
+ # "t": 123124124 # timestamp
538
+ # }
539
+ # }
540
+ #
541
+ data = self.safe_dict(message, 'data', {})
542
+ marketId = self.safe_string(data, 's')
543
+ if marketId is not None:
544
+ timeframe = self.safe_string(data, 'i')
545
+ tradeType = 'spot' if ('q' in data) else 'contract'
546
+ market = self.safe_market(marketId, None, None, tradeType)
547
+ symbol = market['symbol']
548
+ parsed = self.parse_ohlcv(data, market)
549
+ self.ohlcvs[symbol] = self.safe_dict(self.ohlcvs, symbol, {})
550
+ stored = self.safe_value(self.ohlcvs[symbol], timeframe)
551
+ if stored is None:
552
+ limit = self.safe_integer(self.options, 'OHLCVLimit', 1000)
553
+ stored = ArrayCacheByTimestamp(limit)
554
+ self.ohlcvs[symbol][timeframe] = stored
555
+ stored.append(parsed)
556
+ event = self.safe_string(message, 'event')
557
+ messageHash = event + '::' + tradeType
558
+ client.resolve(stored, messageHash)
559
+ return message
560
+
561
+ def handle_trade(self, client: Client, message: dict):
562
+ #
563
+ # spot
564
+ #
565
+ # {
566
+ # topic: 'trade',
567
+ # event: 'trade@btc_usdt',
568
+ # data: {
569
+ # s: 'btc_usdt',
570
+ # i: '228825383103928709',
571
+ # t: 1684258222702,
572
+ # p: '27003.65',
573
+ # q: '0.000796',
574
+ # b: True
575
+ # }
576
+ # }
577
+ #
578
+ # contract
579
+ #
580
+ # {
581
+ # "topic": "trade",
582
+ # "event": "trade@btc_usdt",
583
+ # "data": {
584
+ # "s": "btc_index", # trading pair
585
+ # "p": "50000", # price
586
+ # "a": "0.1" # Quantity
587
+ # "m": "BID" # Deal side BID:Buy ASK:Sell
588
+ # "t": 123124124 # timestamp
589
+ # }
590
+ # }
591
+ #
592
+ data = self.safe_dict(message, 'data')
593
+ marketId = self.safe_string_lower(data, 's')
594
+ if marketId is not None:
595
+ trade = self.parse_trade(data)
596
+ i = self.safe_string(data, 'i')
597
+ tradeType = 'spot' if (i is not None) else 'contract'
598
+ market = self.safe_market(marketId, None, None, tradeType)
599
+ symbol = market['symbol']
600
+ event = self.safe_string(message, 'event')
601
+ tradesArray = self.safe_value(self.trades, symbol)
602
+ if tradesArray is None:
603
+ tradesLimit = self.safe_integer(self.options, 'tradesLimit', 1000)
604
+ tradesArray = ArrayCache(tradesLimit)
605
+ self.trades[symbol] = tradesArray
606
+ tradesArray.append(trade)
607
+ messageHash = event + '::' + tradeType
608
+ client.resolve(tradesArray, messageHash)
609
+ return message
610
+
611
+ def handle_order_book(self, client: Client, message: dict):
612
+ #
613
+ # spot
614
+ #
615
+ # {
616
+ # "topic": "depth",
617
+ # "event": "depth@btc_usdt,20",
618
+ # "data": {
619
+ # "s": "btc_usdt", # symbol
620
+ # "fi": 1681433733351, # firstUpdateId = previous lastUpdateId + 1
621
+ # "i": 1681433733371, # updateId
622
+ # "a": [ # asks(sell order)
623
+ # [ # [0]price, [1]quantity
624
+ # "34000", # price
625
+ # "1.2" # quantity
626
+ # ],
627
+ # [
628
+ # "34001",
629
+ # "2.3"
630
+ # ]
631
+ # ],
632
+ # "b": [ # bids(buy order)
633
+ # [
634
+ # "32000",
635
+ # "0.2"
636
+ # ],
637
+ # [
638
+ # "31000",
639
+ # "0.5"
640
+ # ]
641
+ # ]
642
+ # }
643
+ # }
644
+ #
645
+ # contract
646
+ #
647
+ # {
648
+ # "topic": "depth",
649
+ # "event": "depth@btc_usdt,20",
650
+ # "data": {
651
+ # s: "btc_usdt",
652
+ # pu: "548111455664",
653
+ # fu: "548111455665",
654
+ # u: "548111455667",
655
+ # a: [
656
+ # [
657
+ # "26841.5",
658
+ # "50210",
659
+ # ],
660
+ # ],
661
+ # b: [
662
+ # [
663
+ # "26841",
664
+ # "67075",
665
+ # ],
666
+ # ],
667
+ # t: 1684530667083,
668
+ # }
669
+ # }
670
+ #
671
+ data = self.safe_dict(message, 'data')
672
+ marketId = self.safe_string(data, 's')
673
+ if marketId is not None:
674
+ event = self.safe_string(message, 'event')
675
+ splitEvent = event.split(',')
676
+ event = self.safe_string(splitEvent, 0)
677
+ tradeType = 'contract' if ('fu' in data) else 'spot'
678
+ market = self.safe_market(marketId, None, None, tradeType)
679
+ symbol = market['symbol']
680
+ obAsks = self.safe_list(data, 'a')
681
+ obBids = self.safe_list(data, 'b')
682
+ messageHash = event + '::' + tradeType
683
+ if not (symbol in self.orderbooks):
684
+ subscription = self.safe_dict(client.subscriptions, messageHash, {})
685
+ limit = self.safe_integer(subscription, 'limit')
686
+ self.orderbooks[symbol] = self.order_book({}, limit)
687
+ orderbook = self.orderbooks[symbol]
688
+ nonce = self.safe_integer(orderbook, 'nonce')
689
+ if nonce is None:
690
+ cacheLength = len(orderbook.cache)
691
+ snapshotDelay = self.handle_option('watchOrderBook', 'snapshotDelay', 25)
692
+ if cacheLength == snapshotDelay:
693
+ self.spawn(self.load_order_book, client, messageHash, symbol)
694
+ orderbook.cache.append(data)
695
+ return
696
+ if obAsks is not None:
697
+ asks = orderbook['asks']
698
+ for i in range(0, len(obAsks)):
699
+ ask = obAsks[i]
700
+ price = self.safe_number(ask, 0)
701
+ quantity = self.safe_number(ask, 1)
702
+ asks.store(price, quantity)
703
+ if obBids is not None:
704
+ bids = orderbook['bids']
705
+ for i in range(0, len(obBids)):
706
+ bid = obBids[i]
707
+ price = self.safe_number(bid, 0)
708
+ quantity = self.safe_number(bid, 1)
709
+ bids.store(price, quantity)
710
+ timestamp = self.safe_integer(data, 't')
711
+ orderbook['nonce'] = self.safe_integer_2(data, 'i', 'u')
712
+ orderbook['timestamp'] = timestamp
713
+ orderbook['datetime'] = self.iso8601(timestamp)
714
+ orderbook['symbol'] = symbol
715
+ client.resolve(orderbook, messageHash)
716
+
717
+ def parse_ws_order_trade(self, trade: dict, market: Market = None):
718
+ #
719
+ # {
720
+ # "s": "btc_usdt", # symbol
721
+ # "t": 1656043204763, # time happened time
722
+ # "i": "6216559590087220004", # orderId,
723
+ # "ci": "test123", # clientOrderId
724
+ # "st": "PARTIALLY_FILLED", # state
725
+ # "sd": "BUY", # side BUY/SELL
726
+ # "eq": "2", # executedQty executed quantity
727
+ # "ap": "30000", # avg price
728
+ # "f": "0.002" # fee
729
+ # }
730
+ #
731
+ # contract
732
+ #
733
+ # {
734
+ # "symbol": "btc_usdt", # Trading pair
735
+ # "orderId": "1234", # Order Id
736
+ # "origQty": "34244", # Original Quantity
737
+ # "avgPrice": "123", # Quantity
738
+ # "price": "1111", # Average price
739
+ # "executedQty": "34244", # Volume(Cont)
740
+ # "orderSide": "BUY", # BUY, SELL
741
+ # "positionSide": "LONG", # LONG, SHORT
742
+ # "marginFrozen": "123", # Occupied margin
743
+ # "sourceType": "default", # DEFAULT:normal order,ENTRUST:plan commission,PROFIR:Take Profit and Stop Loss
744
+ # "sourceId" : "1231231", # Triggering conditions ID
745
+ # "state": "", # state:NEW:New order(unfilled);PARTIALLY_FILLED:Partial deal;PARTIALLY_CANCELED:Partial revocation;FILLED:Filled;CANCELED:Cancled;REJECTED:Order failed;EXPIRED:Expired
746
+ # "createTime": 1731231231, # CreateTime
747
+ # "clientOrderId": "204788317630342726"
748
+ # }
749
+ #
750
+ marketId = self.safe_string(trade, 's')
751
+ tradeType = 'contract' if ('symbol' in trade) else 'spot'
752
+ market = self.safe_market(marketId, market, None, tradeType)
753
+ timestamp = self.safe_string(trade, 't')
754
+ return self.safe_trade({
755
+ 'info': trade,
756
+ 'id': None,
757
+ 'timestamp': timestamp,
758
+ 'datetime': self.iso8601(timestamp),
759
+ 'symbol': market['symbol'],
760
+ 'order': self.safe_string(trade, 'i', 'orderId'),
761
+ 'type': self.parse_order_status(self.safe_string(trade, 'st', 'state')),
762
+ 'side': self.safe_string_lower(trade, 'sd', 'orderSide'),
763
+ 'takerOrMaker': None,
764
+ 'price': self.safe_number(trade, 'price'),
765
+ 'amount': self.safe_string(trade, 'origQty'),
766
+ 'cost': None,
767
+ 'fee': {
768
+ 'currency': None,
769
+ 'cost': self.safe_number(trade, 'f'),
770
+ 'rate': None,
771
+ },
772
+ }, market)
773
+
774
+ def parse_ws_order(self, order: dict, market: Market = None):
775
+ #
776
+ # spot
777
+ #
778
+ # {
779
+ # "s": "btc_usdt", # symbol
780
+ # "bc": "btc", # base currency
781
+ # "qc": "usdt", # quotation currency
782
+ # "t": 1656043204763, # happened time
783
+ # "ct": 1656043204663, # create time
784
+ # "i": "6216559590087220004", # order id,
785
+ # "ci": "test123", # client order id
786
+ # "st": "PARTIALLY_FILLED", # state NEW/PARTIALLY_FILLED/FILLED/CANCELED/REJECTED/EXPIRED
787
+ # "sd": "BUY", # side BUY/SELL
788
+ # "tp": "LIMIT", # type LIMIT/MARKET
789
+ # "oq": "4" # original quantity
790
+ # "oqq": 48000, # original quotation quantity
791
+ # "eq": "2", # executed quantity
792
+ # "lq": "2", # remaining quantity
793
+ # "p": "4000", # price
794
+ # "ap": "30000", # avg price
795
+ # "f":"0.002" # fee
796
+ # }
797
+ #
798
+ # contract
799
+ #
800
+ # {
801
+ # "symbol": "btc_usdt", # Trading pair
802
+ # "orderId": "1234", # Order Id
803
+ # "origQty": "34244", # Original Quantity
804
+ # "avgPrice": "123", # Quantity
805
+ # "price": "1111", # Average price
806
+ # "executedQty": "34244", # Volume(Cont)
807
+ # "orderSide": "BUY", # BUY, SELL
808
+ # "positionSide": "LONG", # LONG, SHORT
809
+ # "marginFrozen": "123", # Occupied margin
810
+ # "sourceType": "default", # DEFAULT:normal order,ENTRUST:plan commission,PROFIR:Take Profit and Stop Loss
811
+ # "sourceId" : "1231231", # Triggering conditions ID
812
+ # "state": "", # state:NEW:New order(unfilled);PARTIALLY_FILLED:Partial deal;PARTIALLY_CANCELED:Partial revocation;FILLED:Filled;CANCELED:Cancled;REJECTED:Order failed;EXPIRED:Expired
813
+ # "createTime": 1731231231, # CreateTime
814
+ # "clientOrderId": "204788317630342726"
815
+ # }
816
+ #
817
+ marketId = self.safe_string_2(order, 's', 'symbol')
818
+ tradeType = 'contract' if ('symbol' in order) else 'spot'
819
+ market = self.safe_market(marketId, market, None, tradeType)
820
+ timestamp = self.safe_integer_2(order, 'ct', 'createTime')
821
+ return self.safe_order({
822
+ 'info': order,
823
+ 'id': self.safe_string_2(order, 'i', 'orderId'),
824
+ 'clientOrderId': self.safe_string_2(order, 'ci', 'clientOrderId'),
825
+ 'timestamp': timestamp,
826
+ 'datetime': self.iso8601(timestamp),
827
+ 'lastTradeTimestamp': None,
828
+ 'symbol': market['symbol'],
829
+ 'type': market['type'],
830
+ 'timeInForce': None,
831
+ 'postOnly': None,
832
+ 'side': self.safe_string_lower_2(order, 'sd', 'orderSide'),
833
+ 'price': self.safe_number_2(order, 'p', 'price'),
834
+ 'stopPrice': None,
835
+ 'stopLoss': None,
836
+ 'takeProfit': None,
837
+ 'amount': self.safe_string_2(order, 'oq', 'origQty'),
838
+ 'filled': self.safe_string_2(order, 'eq', 'executedQty'),
839
+ 'remaining': self.safe_string(order, 'lq'),
840
+ 'cost': None,
841
+ 'average': self.safe_string_2(order, 'ap', 'avgPrice'),
842
+ 'status': self.parse_order_status(self.safe_string(order, 'st', 'state')),
843
+ 'fee': {
844
+ 'currency': None,
845
+ 'cost': self.safe_number(order, 'f'),
846
+ },
847
+ 'trades': None,
848
+ }, market)
849
+
850
+ def handle_order(self, client: Client, message: dict):
851
+ #
852
+ # spot
853
+ #
854
+ # {
855
+ # "topic": "order",
856
+ # "event": "order",
857
+ # "data": {
858
+ # "s": "btc_usdt", # symbol
859
+ # "t": 1656043204763, # time happened time
860
+ # "i": "6216559590087220004", # orderId,
861
+ # "ci": "test123", # clientOrderId
862
+ # "st": "PARTIALLY_FILLED", # state
863
+ # "sd": "BUY", # side BUY/SELL
864
+ # "eq": "2", # executedQty executed quantity
865
+ # "ap": "30000", # avg price
866
+ # "f": "0.002" # fee
867
+ # }
868
+ # }
869
+ #
870
+ # contract
871
+ #
872
+ # {
873
+ # "topic": "order",
874
+ # "event": "order@123456",
875
+ # "data": {
876
+ # "symbol": "btc_usdt", # Trading pair
877
+ # "orderId": "1234", # Order Id
878
+ # "origQty": "34244", # Original Quantity
879
+ # "avgPrice": "123", # Quantity
880
+ # "price": "1111", # Average price
881
+ # "executedQty": "34244", # Volume(Cont)
882
+ # "orderSide": "BUY", # BUY, SELL
883
+ # "positionSide": "LONG", # LONG, SHORT
884
+ # "marginFrozen": "123", # Occupied margin
885
+ # "sourceType": "default", # DEFAULT:normal order,ENTRUST:plan commission,PROFIR:Take Profit and Stop Loss
886
+ # "sourceId" : "1231231", # Triggering conditions ID
887
+ # "state": "", # state:NEW:New order(unfilled);PARTIALLY_FILLED:Partial deal;PARTIALLY_CANCELED:Partial revocation;FILLED:Filled;CANCELED:Cancled;REJECTED:Order failed;EXPIRED:Expired
888
+ # "createTime": 1731231231, # CreateTime
889
+ # "clientOrderId": "204788317630342726"
890
+ # }
891
+ # }
892
+ #
893
+ orders = self.orders
894
+ if orders is None:
895
+ limit = self.safe_integer(self.options, 'ordersLimit')
896
+ orders = ArrayCacheBySymbolById(limit)
897
+ self.orders = orders
898
+ order = self.safe_dict(message, 'data', {})
899
+ marketId = self.safe_string_2(order, 's', 'symbol')
900
+ if marketId is not None:
901
+ tradeType = 'contract' if ('symbol' in order) else 'spot'
902
+ market = self.safe_market(marketId, None, None, tradeType)
903
+ parsed = self.parse_ws_order(order, market)
904
+ orders.append(parsed)
905
+ client.resolve(orders, 'order::' + tradeType)
906
+ return message
907
+
908
+ def handle_balance(self, client: Client, message: dict):
909
+ #
910
+ # spot
911
+ #
912
+ # {
913
+ # topic: 'balance',
914
+ # event: 'balance',
915
+ # data: {
916
+ # a: 3513677381884,
917
+ # t: 1684250056775,
918
+ # c: 'usdt',
919
+ # b: '7.71000000',
920
+ # f: '0.00000000',
921
+ # z: 'SPOT'
922
+ # }
923
+ # }
924
+ #
925
+ # contract
926
+ #
927
+ # {
928
+ # "topic": "balance",
929
+ # "event": "balance@123456",
930
+ # "data": {
931
+ # "coin": "usdt",
932
+ # "underlyingType": 1, # 1:Coin-M,2:USDT-M
933
+ # "walletBalance": "123", # Balance
934
+ # "openOrderMarginFrozen": "123", # Frozen order
935
+ # "isolatedMargin": "213", # Isolated Margin
936
+ # "crossedMargin": "0" # Crossed Margin
937
+ # "availableBalance": '2.256114450000000000',
938
+ # "coupon": '0',
939
+ # "bonus": '0'
940
+ # }
941
+ # }
942
+ #
943
+ data = self.safe_dict(message, 'data', {})
944
+ currencyId = self.safe_string_2(data, 'c', 'coin')
945
+ code = self.safe_currency_code(currencyId)
946
+ account = self.account()
947
+ account['free'] = self.safe_string(data, 'availableBalance')
948
+ account['used'] = self.safe_string(data, 'f')
949
+ account['total'] = self.safe_string_2(data, 'b', 'walletBalance')
950
+ self.balance[code] = account
951
+ self.balance = self.safe_balance(self.balance)
952
+ tradeType = 'contract' if ('coin' in data) else 'spot'
953
+ client.resolve(self.balance, 'balance::' + tradeType)
954
+
955
+ def handle_my_trades(self, client: Client, message: dict):
956
+ #
957
+ # spot
958
+ #
959
+ # {
960
+ # "topic": "trade",
961
+ # "event": "trade",
962
+ # "data": {
963
+ # "s": "btc_usdt", # symbol
964
+ # "t": 1656043204763, # time
965
+ # "i": "6316559590087251233", # tradeId
966
+ # "oi": "6216559590087220004", # orderId
967
+ # "p": "30000", # trade price
968
+ # "q": "3", # qty quantity
969
+ # "v": "90000" # volume trade amount
970
+ # }
971
+ # }
972
+ #
973
+ # contract
974
+ #
975
+ # {
976
+ # "topic": "trade",
977
+ # "event": "trade@123456",
978
+ # "data": {
979
+ # "symbol": 'btc_usdt',
980
+ # "orderSide": 'SELL',
981
+ # "positionSide": 'LONG',
982
+ # "orderId": '231485367663419328',
983
+ # "price": '27152.7',
984
+ # "quantity": '33',
985
+ # "marginUnfrozen": '2.85318000',
986
+ # "timestamp": 1684892412565
987
+ # }
988
+ # }
989
+ #
990
+ data = self.safe_dict(message, 'data', {})
991
+ stored = self.myTrades
992
+ if stored is None:
993
+ limit = self.safe_integer(self.options, 'tradesLimit', 1000)
994
+ stored = ArrayCacheBySymbolById(limit)
995
+ self.myTrades = stored
996
+ parsedTrade = self.parse_trade(data)
997
+ market = self.market(parsedTrade['symbol'])
998
+ stored.append(parsedTrade)
999
+ tradeType = 'contract' if market['contract'] else 'spot'
1000
+ client.resolve(stored, 'trade::' + tradeType)
1001
+
1002
+ def handle_message(self, client: Client, message):
1003
+ event = self.safe_string(message, 'event')
1004
+ if event == 'pong':
1005
+ client.onPong()
1006
+ elif event is not None:
1007
+ topic = self.safe_string(message, 'topic')
1008
+ methods = {
1009
+ 'kline': self.handle_ohlcv,
1010
+ 'depth': self.handle_order_book,
1011
+ 'depth_update': self.handle_order_book,
1012
+ 'ticker': self.handle_ticker,
1013
+ 'agg_ticker': self.handle_ticker,
1014
+ 'tickers': self.handle_tickers,
1015
+ 'agg_tickers': self.handle_tickers,
1016
+ 'balance': self.handle_balance,
1017
+ 'order': self.handle_order,
1018
+ }
1019
+ method = self.safe_value(methods, topic)
1020
+ if topic == 'trade':
1021
+ data = self.safe_dict(message, 'data')
1022
+ if ('oi' in data) or ('orderId' in data):
1023
+ method = self.handle_my_trades
1024
+ else:
1025
+ method = self.handle_trade
1026
+ if method is not None:
1027
+ method(client, message)
1028
+
1029
+ def ping(self, client: Client):
1030
+ client.lastPong = self.milliseconds()
1031
+ return 'ping'
1032
+
1033
+ def handle_error_message(self, client: Client, message: dict):
1034
+ #
1035
+ # {
1036
+ # "id": "123",
1037
+ # "code": 401,
1038
+ # "msg": "token expire"
1039
+ # }
1040
+ #
1041
+ msg = self.safe_string(message, 'msg')
1042
+ if (msg == 'invalid_listen_key') or (msg == 'token expire'):
1043
+ client.subscriptions['token'] = None
1044
+ self.get_listen_key(True)
1045
+ return
1046
+ client.reject(message)