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

ccxt/pro/blofin.py ADDED
@@ -0,0 +1,608 @@
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, ArrayCacheBySymbolBySide, ArrayCacheByTimestamp
8
+ import hashlib
9
+ from ccxt.base.types import Any, Balances, Int, Market, Order, OrderBook, Position, Str, Strings, Ticker, Tickers, Trade
10
+ from ccxt.async_support.base.ws.client import Client
11
+ from typing import List
12
+ from ccxt.base.errors import ExchangeError
13
+ from ccxt.base.errors import ArgumentsRequired
14
+ from ccxt.base.errors import NotSupported
15
+
16
+
17
+ class blofin(ccxt.async_support.blofin):
18
+
19
+ def describe(self):
20
+ return self.deep_extend(super(blofin, self).describe(), {
21
+ 'has': {
22
+ 'ws': True,
23
+ 'watchTrades': True,
24
+ 'watchTradesForSymbols': True,
25
+ 'watchOrderBook': True,
26
+ 'watchOrderBookForSymbols': True,
27
+ 'watchTicker': True,
28
+ 'watchTickers': True,
29
+ 'watchOHLCV': True,
30
+ 'watchOHLCVForSymbols': True,
31
+ 'watchOrders': True,
32
+ 'watchOrdersForSymbols': True,
33
+ 'watchPositions': True,
34
+ },
35
+ 'urls': {
36
+ 'api': {
37
+ 'ws': {
38
+ 'swap': {
39
+ 'public': 'wss://openapi.blofin.com/ws/public',
40
+ 'private': 'wss://openapi.blofin.com/ws/private',
41
+ },
42
+ },
43
+ },
44
+ },
45
+ 'options': {
46
+ 'defaultType': 'swap',
47
+ 'tradesLimit': 1000,
48
+ # orderbook channel can be one from:
49
+ # - "books": 200 depth levels will be pushed in the initial full snapshot. Incremental data will be pushed every 100 ms for the changes in the order book during that period of time.
50
+ # - "books5": 5 depth levels snapshot will be pushed every time. Snapshot data will be pushed every 100 ms when there are changes in the 5 depth levels snapshot.
51
+ 'watchOrderBook': {
52
+ 'channel': 'books',
53
+ },
54
+ 'watchOrderBookForSymbols': {
55
+ 'channel': 'books',
56
+ },
57
+ },
58
+ 'streaming': {
59
+ 'ping': self.ping,
60
+ 'keepAlive': 25000, # 30 seconds max
61
+ },
62
+ })
63
+
64
+ def ping(self, client):
65
+ return 'ping'
66
+
67
+ def handle_pong(self, client: Client, message):
68
+ #
69
+ # 'pong'
70
+ #
71
+ client.lastPong = self.milliseconds()
72
+
73
+ async def watch_trades(self, symbol: str, since: Int = None, limit: Int = None, params={}) -> List[Trade]:
74
+ """
75
+ get the list of most recent trades for a particular symbol
76
+ :see: https://docs.blofin.com/index.html#ws-trades-channel
77
+ :param str symbol: unified symbol of the market to fetch trades for
78
+ :param int [since]: timestamp in ms of the earliest trade to fetch
79
+ :param int [limit]: the maximum amount of trades to fetch
80
+ :param dict [params]: extra parameters specific to the exchange API endpoint
81
+ :returns dict[]: a list of `trade structures <https://docs.ccxt.com/#/?id=public-trades>`
82
+ """
83
+ params['callerMethodName'] = 'watchTrades'
84
+ return await self.watch_trades_for_symbols([symbol], since, limit, params)
85
+
86
+ async def watch_trades_for_symbols(self, symbols: List[str], since: Int = None, limit: Int = None, params={}) -> List[Trade]:
87
+ """
88
+ get the list of most recent trades for a list of symbols
89
+ :see: https://docs.blofin.com/index.html#ws-trades-channel
90
+ :param str[] symbols: unified symbol of the market to fetch trades for
91
+ :param int [since]: timestamp in ms of the earliest trade to fetch
92
+ :param int [limit]: the maximum amount of trades to fetch
93
+ :param dict [params]: extra parameters specific to the exchange API endpoint
94
+ :returns dict[]: a list of `trade structures <https://docs.ccxt.com/#/?id=public-trades>`
95
+ """
96
+ await self.load_markets()
97
+ trades = await self.watch_multiple_wrapper(True, 'trades', 'watchTradesForSymbols', symbols, params)
98
+ if self.newUpdates:
99
+ firstMarket = self.safe_dict(trades, 0)
100
+ firstSymbol = self.safe_string(firstMarket, 'symbol')
101
+ limit = trades.getLimit(firstSymbol, limit)
102
+ return self.filter_by_since_limit(trades, since, limit, 'timestamp', True)
103
+
104
+ def handle_trades(self, client: Client, message):
105
+ #
106
+ # {
107
+ # arg: {
108
+ # channel: "trades",
109
+ # instId: "DOGE-USDT",
110
+ # },
111
+ # data : [
112
+ # <same object in REST example>,
113
+ # ...
114
+ # ]
115
+ # }
116
+ #
117
+ arg = self.safe_dict(message, 'arg')
118
+ channelName = self.safe_string(arg, 'channel')
119
+ data = self.safe_list(message, 'data')
120
+ if data is None:
121
+ return
122
+ for i in range(0, len(data)):
123
+ rawTrade = data[i]
124
+ trade = self.parse_ws_trade(rawTrade)
125
+ symbol = trade['symbol']
126
+ stored = self.safe_value(self.trades, symbol)
127
+ if stored is None:
128
+ limit = self.safe_integer(self.options, 'tradesLimit', 1000)
129
+ stored = ArrayCache(limit)
130
+ self.trades[symbol] = stored
131
+ stored.append(trade)
132
+ messageHash = channelName + ':' + symbol
133
+ client.resolve(stored, messageHash)
134
+
135
+ def parse_ws_trade(self, trade, market: Market = None) -> Trade:
136
+ return self.parse_trade(trade, market)
137
+
138
+ async def watch_order_book(self, symbol: str, limit: Int = None, params={}) -> OrderBook:
139
+ """
140
+ watches information on open orders with bid(buy) and ask(sell) prices, volumes and other data
141
+ :see: https://docs.blofin.com/index.html#ws-order-book-channel
142
+ :param str symbol: unified symbol of the market to fetch the order book for
143
+ :param int [limit]: the maximum amount of order book entries to return
144
+ :param dict [params]: extra parameters specific to the exchange API endpoint
145
+ :returns dict: A dictionary of `order book structures <https://docs.ccxt.com/#/?id=order-book-structure>` indexed by market symbols
146
+ """
147
+ params['callerMethodName'] = 'watchOrderBook'
148
+ return await self.watch_order_book_for_symbols([symbol], limit, params)
149
+
150
+ async def watch_order_book_for_symbols(self, symbols: List[str], limit: Int = None, params={}) -> OrderBook:
151
+ """
152
+ watches information on open orders with bid(buy) and ask(sell) prices, volumes and other data
153
+ :see: https://docs.blofin.com/index.html#ws-order-book-channel
154
+ :param str[] symbols: unified array of symbols
155
+ :param int [limit]: the maximum amount of order book entries to return
156
+ :param dict [params]: extra parameters specific to the exchange API endpoint
157
+ :param str [params.depth]: the type of order book to subscribe to, default is 'depth/increase100', also accepts 'depth5' or 'depth20' or depth50
158
+ :returns dict: A dictionary of `order book structures <https://docs.ccxt.com/#/?id=order-book-structure>` indexed by market symbols
159
+ """
160
+ await self.load_markets()
161
+ callerMethodName = None
162
+ callerMethodName, params = self.handle_param_string(params, 'callerMethodName', 'watchOrderBookForSymbols')
163
+ channelName = None
164
+ channelName, params = self.handle_option_and_params(params, callerMethodName, 'channel', 'books')
165
+ # due to some problem, temporarily disable other channels
166
+ if channelName != 'books':
167
+ raise NotSupported(self.id + ' ' + callerMethodName + '() at self moment ' + channelName + ' is not supported, coming soon')
168
+ orderbook = await self.watch_multiple_wrapper(True, channelName, callerMethodName, symbols, params)
169
+ return orderbook.limit()
170
+
171
+ def handle_order_book(self, client: Client, message):
172
+ #
173
+ # {
174
+ # arg: {
175
+ # channel: "books",
176
+ # instId: "DOGE-USDT",
177
+ # },
178
+ # action: "snapshot", # can be 'snapshot' or 'update'
179
+ # data: {
180
+ # asks: [ [0.08096, 1], [0.08097, 123], ... ],
181
+ # bids: [ [0.08095, 4], [0.08094, 237], ... ],
182
+ # ts: "1707491587909",
183
+ # prevSeqId: "0", # in case of 'update' there will be some value, less then seqId
184
+ # seqId: "3374250786",
185
+ # },
186
+ # }
187
+ #
188
+ arg = self.safe_dict(message, 'arg')
189
+ channelName = self.safe_string(arg, 'channel')
190
+ data = self.safe_dict(message, 'data')
191
+ marketId = self.safe_string(arg, 'instId')
192
+ market = self.safe_market(marketId)
193
+ symbol = market['symbol']
194
+ messageHash = channelName + ':' + symbol
195
+ if not (symbol in self.orderbooks):
196
+ self.orderbooks[symbol] = self.order_book()
197
+ orderbook = self.orderbooks[symbol]
198
+ timestamp = self.safe_integer(data, 'ts')
199
+ action = self.safe_string(message, 'action')
200
+ if action == 'snapshot':
201
+ orderBookSnapshot = self.parse_order_book(data, symbol, timestamp)
202
+ orderBookSnapshot['nonce'] = self.safe_integer(data, 'seqId')
203
+ orderbook.reset(orderBookSnapshot)
204
+ else:
205
+ asks = self.safe_list(data, 'asks', [])
206
+ bids = self.safe_list(data, 'bids', [])
207
+ self.handle_deltas_with_keys(orderbook['asks'], asks)
208
+ self.handle_deltas_with_keys(orderbook['bids'], bids)
209
+ orderbook['timestamp'] = timestamp
210
+ orderbook['datetime'] = self.iso8601(timestamp)
211
+ self.orderbooks[symbol] = orderbook
212
+ client.resolve(orderbook, messageHash)
213
+
214
+ async def watch_ticker(self, symbol: str, params={}) -> Ticker:
215
+ """
216
+ watches a price ticker, a statistical calculation with the information calculated over the past 24 hours for a specific market
217
+ :see: https://docs.blofin.com/index.html#ws-tickers-channel
218
+ :param str symbol: unified symbol of the market to fetch the ticker for
219
+ :param dict [params]: extra parameters specific to the exchange API endpoint
220
+ :returns dict: a `ticker structure <https://docs.ccxt.com/#/?id=ticker-structure>`
221
+ """
222
+ params['callerMethodName'] = 'watchTicker'
223
+ market = self.market(symbol)
224
+ symbol = market['symbol']
225
+ result = await self.watch_tickers([symbol], params)
226
+ return result[symbol]
227
+
228
+ async def watch_tickers(self, symbols: Strings = None, params={}) -> Tickers:
229
+ """
230
+ watches a price ticker, a statistical calculation with the information calculated over the past 24 hours for all markets of a specific list
231
+ :see: https://docs.blofin.com/index.html#ws-tickers-channel
232
+ :param str[] symbols: unified symbol of the market to fetch the ticker for
233
+ :param dict [params]: extra parameters specific to the exchange API endpoint
234
+ :returns dict: a `ticker structure <https://docs.ccxt.com/#/?id=ticker-structure>`
235
+ """
236
+ if symbols is None:
237
+ raise NotSupported(self.id + ' watchTickers() requires a list of symbols')
238
+ ticker = await self.watch_multiple_wrapper(True, 'tickers', 'watchTickers', symbols, params)
239
+ if self.newUpdates:
240
+ tickers = {}
241
+ tickers[ticker['symbol']] = ticker
242
+ return tickers
243
+ return self.filter_by_array(self.tickers, 'symbol', symbols)
244
+
245
+ def handle_ticker(self, client: Client, message):
246
+ #
247
+ # message
248
+ #
249
+ # {
250
+ # arg: {
251
+ # channel: "tickers",
252
+ # instId: "DOGE-USDT",
253
+ # },
254
+ # data: [
255
+ # <same object in REST example>
256
+ # ],
257
+ # }
258
+ #
259
+ arg = self.safe_dict(message, 'arg')
260
+ channelName = self.safe_string(arg, 'channel')
261
+ data = self.safe_list(message, 'data')
262
+ for i in range(0, len(data)):
263
+ ticker = self.parse_ws_ticker(data[i])
264
+ symbol = ticker['symbol']
265
+ messageHash = channelName + ':' + symbol
266
+ self.tickers[symbol] = ticker
267
+ client.resolve(self.tickers[symbol], messageHash)
268
+
269
+ def parse_ws_ticker(self, ticker, market: Market = None) -> Ticker:
270
+ return self.parse_ticker(ticker, market)
271
+
272
+ async def watch_ohlcv(self, symbol: str, timeframe='1m', since: Int = None, limit: Int = None, params={}) -> List[list]:
273
+ """
274
+ watches historical candlestick data containing the open, high, low, and close price, and the volume of a market
275
+ :param str symbol: unified symbol of the market to fetch OHLCV data for
276
+ :param str timeframe: the length of time each candle represents
277
+ :param int [since]: timestamp in ms of the earliest candle to fetch
278
+ :param int [limit]: the maximum amount of candles to fetch
279
+ :param dict [params]: extra parameters specific to the exchange API endpoint
280
+ :returns int[][]: A list of candles ordered, open, high, low, close, volume
281
+ """
282
+ params['callerMethodName'] = 'watchOHLCV'
283
+ result = await self.watch_ohlcv_for_symbols([[symbol, timeframe]], since, limit, params)
284
+ return result[symbol][timeframe]
285
+
286
+ async def watch_ohlcv_for_symbols(self, symbolsAndTimeframes: List[List[str]], since: Int = None, limit: Int = None, params={}):
287
+ """
288
+ watches historical candlestick data containing the open, high, low, and close price, and the volume of a market
289
+ :see: https://docs.blofin.com/index.html#ws-candlesticks-channel
290
+ :param str[][] symbolsAndTimeframes: array of arrays containing unified symbols and timeframes to fetch OHLCV data for, example [['BTC/USDT', '1m'], ['LTC/USDT', '5m']]
291
+ :param int [since]: timestamp in ms of the earliest candle to fetch
292
+ :param int [limit]: the maximum amount of candles to fetch
293
+ :param dict [params]: extra parameters specific to the exchange API endpoint
294
+ :returns int[][]: A list of candles ordered, open, high, low, close, volume
295
+ """
296
+ symbolsLength = len(symbolsAndTimeframes)
297
+ if symbolsLength == 0 or not isinstance(symbolsAndTimeframes[0], list):
298
+ raise ArgumentsRequired(self.id + " watchOHLCVForSymbols() requires a an array of symbols and timeframes, like [['BTC/USDT', '1m'], ['LTC/USDT', '5m']]")
299
+ await self.load_markets()
300
+ symbol, timeframe, candles = await self.watch_multiple_wrapper(True, 'candle', 'watchOHLCVForSymbols', symbolsAndTimeframes, params)
301
+ if self.newUpdates:
302
+ limit = candles.getLimit(symbol, limit)
303
+ filtered = self.filter_by_since_limit(candles, since, limit, 0, True)
304
+ return self.create_ohlcv_object(symbol, timeframe, filtered)
305
+
306
+ def handle_ohlcv(self, client: Client, message):
307
+ #
308
+ # message
309
+ #
310
+ # {
311
+ # arg: {
312
+ # channel: "candle1m",
313
+ # instId: "DOGE-USDT",
314
+ # },
315
+ # data: [
316
+ # [same object in REST example]
317
+ # ],
318
+ # }
319
+ #
320
+ arg = self.safe_dict(message, 'arg')
321
+ channelName = self.safe_string(arg, 'channel')
322
+ data = self.safe_list(message, 'data')
323
+ marketId = self.safe_string(arg, 'instId')
324
+ market = self.safe_market(marketId)
325
+ symbol = market['symbol']
326
+ interval = channelName.replace('candle', '')
327
+ unifiedTimeframe = self.find_timeframe(interval)
328
+ self.ohlcvs[symbol] = self.safe_dict(self.ohlcvs, symbol, {})
329
+ stored = self.safe_value(self.ohlcvs[symbol], unifiedTimeframe)
330
+ if stored is None:
331
+ limit = self.safe_integer(self.options, 'OHLCVLimit', 1000)
332
+ stored = ArrayCacheByTimestamp(limit)
333
+ self.ohlcvs[symbol][unifiedTimeframe] = stored
334
+ for i in range(0, len(data)):
335
+ candle = data[i]
336
+ parsed = self.parse_ohlcv(candle, market)
337
+ stored.append(parsed)
338
+ resolveData = [symbol, unifiedTimeframe, stored]
339
+ messageHash = 'candle' + interval + ':' + symbol
340
+ client.resolve(resolveData, messageHash)
341
+
342
+ async def watch_balance(self, params={}) -> Balances:
343
+ """
344
+ query for balance and get the amount of funds available for trading or funds locked in orders
345
+ :see: https://docs.blofin.com/index.html#ws-account-channel
346
+ :param dict [params]: extra parameters specific to the exchange API endpoint
347
+ :returns dict: a `balance structure <https://docs.ccxt.com/#/?id=balance-structure>`
348
+ """
349
+ await self.load_markets()
350
+ await self.authenticate()
351
+ marketType = None
352
+ marketType, params = self.handle_market_type_and_params('watchBalance', None, params)
353
+ if marketType == 'spot':
354
+ raise NotSupported(self.id + ' watchBalance() is not supported for spot markets yet')
355
+ messageHash = marketType + ':balance'
356
+ sub = {
357
+ 'channel': 'account',
358
+ }
359
+ request = self.get_subscription_request([sub])
360
+ url = self.implode_hostname(self.urls['api']['ws'][marketType]['private'])
361
+ return await self.watch(url, messageHash, self.deep_extend(request, params), messageHash)
362
+
363
+ def handle_balance(self, client: Client, message):
364
+ #
365
+ # {
366
+ # arg: {
367
+ # channel: "account",
368
+ # },
369
+ # data: <same object in REST example>,
370
+ # }
371
+ #
372
+ marketType = 'swap' # for now
373
+ if not (marketType in self.balance):
374
+ self.balance[marketType] = {}
375
+ self.balance[marketType] = self.parse_ws_balance(message)
376
+ messageHash = marketType + ':balance'
377
+ client.resolve(self.balance[marketType], messageHash)
378
+
379
+ def parse_ws_balance(self, message):
380
+ return self.parse_balance(message)
381
+
382
+ async def watch_orders(self, symbol: Str = None, since: Int = None, limit: Int = None, params={}) -> List[Order]:
383
+ """
384
+ watches information on multiple orders made by the user
385
+ :param str symbol: unified market symbol of the market orders were made in
386
+ :param int [since]: the earliest time in ms to fetch orders for
387
+ :param int [limit]: the maximum number of order structures to retrieve
388
+ :param dict [params]: extra parameters specific to the exchange API endpoint
389
+ :returns dict[]: a list of [order structures]{@link https://docs.ccxt.com/#/?id=order-structure
390
+ """
391
+ params['callerMethodName'] = 'watchOrders'
392
+ symbolsArray = [symbol] if (symbol is not None) else []
393
+ return await self.watch_orders_for_symbols(symbolsArray, since, limit, params)
394
+
395
+ async def watch_orders_for_symbols(self, symbols: List[str], since: Int = None, limit: Int = None, params={}) -> List[Order]:
396
+ """
397
+ watches information on multiple orders made by the user across multiple symbols
398
+ :see: https://docs.blofin.com/index.html#ws-order-channel
399
+ :param str symbol: unified market symbol of the market orders were made in
400
+ :param int [since]: the earliest time in ms to fetch orders for
401
+ :param int [limit]: the maximum number of order structures to retrieve
402
+ :param dict [params]: extra parameters specific to the exchange API endpoint
403
+ :returns dict[]: a list of [order structures]{@link https://docs.ccxt.com/#/?id=order-structure
404
+ """
405
+ await self.authenticate()
406
+ await self.load_markets()
407
+ orders = await self.watch_multiple_wrapper(False, 'orders', 'watchOrdersForSymbols', symbols, params)
408
+ if self.newUpdates:
409
+ first = self.safe_value(orders, 0)
410
+ tradeSymbol = self.safe_string(first, 'symbol')
411
+ limit = orders.getLimit(tradeSymbol, limit)
412
+ return self.filter_by_since_limit(orders, since, limit, 'timestamp', True)
413
+
414
+ def handle_orders(self, client: Client, message):
415
+ #
416
+ # {
417
+ # action: 'update',
418
+ # arg: {channel: 'orders'},
419
+ # data: [
420
+ # <same object in REST example>
421
+ # ]
422
+ # }
423
+ #
424
+ if self.orders is None:
425
+ limit = self.safe_integer(self.options, 'ordersLimit', 1000)
426
+ self.orders = ArrayCacheBySymbolById(limit)
427
+ orders = self.orders
428
+ arg = self.safe_dict(message, 'arg')
429
+ channelName = self.safe_string(arg, 'channel')
430
+ data = self.safe_list(message, 'data')
431
+ for i in range(0, len(data)):
432
+ order = self.parse_ws_order(data[i])
433
+ symbol = order['symbol']
434
+ messageHash = channelName + ':' + symbol
435
+ orders.append(order)
436
+ client.resolve(orders, messageHash)
437
+ client.resolve(orders, channelName)
438
+
439
+ def parse_ws_order(self, order, market: Market = None) -> Order:
440
+ return self.parse_order(order, market)
441
+
442
+ async def watch_positions(self, symbols: Strings = None, since: Int = None, limit: Int = None, params={}) -> List[Position]:
443
+ """
444
+ :see: https://docs.blofin.com/index.html#ws-positions-channel
445
+ watch all open positions
446
+ :param str[]|None symbols: list of unified market symbols
447
+ :param dict params: extra parameters specific to the exchange API endpoint
448
+ :returns dict[]: a list of `position structure <https://docs.ccxt.com/en/latest/manual.html#position-structure>`
449
+ """
450
+ await self.authenticate()
451
+ await self.load_markets()
452
+ newPositions = await self.watch_multiple_wrapper(False, 'positions', 'watchPositions', symbols, params)
453
+ if self.newUpdates:
454
+ return newPositions
455
+ return self.filter_by_symbols_since_limit(self.positions, symbols, since, limit)
456
+
457
+ def handle_positions(self, client: Client, message):
458
+ #
459
+ # {
460
+ # arg: {channel: 'positions'},
461
+ # data: [
462
+ # <same object in REST example>
463
+ # ]
464
+ # }
465
+ #
466
+ if self.positions is None:
467
+ self.positions = ArrayCacheBySymbolBySide()
468
+ cache = self.positions
469
+ arg = self.safe_dict(message, 'arg')
470
+ channelName = self.safe_string(arg, 'channel')
471
+ data = self.safe_list(message, 'data')
472
+ newPositions = []
473
+ for i in range(0, len(data)):
474
+ position = self.parse_ws_position(data[i])
475
+ newPositions.append(position)
476
+ cache.append(position)
477
+ messageHash = channelName + ':' + position['symbol']
478
+ client.resolve(position, messageHash)
479
+
480
+ def parse_ws_position(self, position, market: Market = None) -> Position:
481
+ return self.parse_position(position, market)
482
+
483
+ async def watch_multiple_wrapper(self, isPublic: bool, channelName: str, callerMethodName: str, symbolsArray: List[Any] = None, params={}):
484
+ # underlier method for all watch-multiple symbols
485
+ await self.load_markets()
486
+ callerMethodName, params = self.handle_param_string(params, 'callerMethodName', callerMethodName)
487
+ # if OHLCV method are being called, then symbols would be symbolsAndTimeframes(multi-dimensional) array
488
+ isOHLCV = (channelName == 'candle')
489
+ symbols = self.get_list_from_object_values(symbolsArray, 0) if isOHLCV else symbolsArray
490
+ symbols = self.market_symbols(symbols, None, True, True)
491
+ firstMarket = None
492
+ firstSymbol = self.safe_string(symbols, 0)
493
+ if firstSymbol is not None:
494
+ firstMarket = self.market(firstSymbol)
495
+ marketType = None
496
+ marketType, params = self.handle_market_type_and_params(callerMethodName, firstMarket, params)
497
+ if marketType != 'swap':
498
+ raise NotSupported(self.id + ' ' + callerMethodName + '() does not support ' + marketType + ' markets yet')
499
+ rawSubscriptions = []
500
+ messageHashes = []
501
+ if symbols is None:
502
+ symbols = []
503
+ symbolsLength = len(symbols)
504
+ if symbolsLength > 0:
505
+ for i in range(0, len(symbols)):
506
+ current = symbols[i]
507
+ market = None
508
+ channel = channelName
509
+ if isOHLCV:
510
+ market = self.market(current)
511
+ tfArray = symbolsArray[i]
512
+ tf = tfArray[1]
513
+ interval = self.safe_string(self.timeframes, tf, tf)
514
+ channel += interval
515
+ else:
516
+ market = self.market(current)
517
+ topic = {
518
+ 'channel': channel,
519
+ 'instId': market['id'],
520
+ }
521
+ rawSubscriptions.append(topic)
522
+ messageHashes.append(channel + ':' + market['symbol'])
523
+ else:
524
+ rawSubscriptions.append({'channel': channelName})
525
+ messageHashes.append(channelName)
526
+ # private channel are difference, they only need plural channel name for multiple symbols
527
+ if self.in_array(channelName, ['orders', 'positions']):
528
+ rawSubscriptions = [{'channel': channelName}]
529
+ request = self.get_subscription_request(rawSubscriptions)
530
+ privateOrPublic = 'public' if isPublic else 'private'
531
+ url = self.implode_hostname(self.urls['api']['ws'][marketType][privateOrPublic])
532
+ return await self.watch_multiple(url, messageHashes, self.deep_extend(request, params), messageHashes)
533
+
534
+ def get_subscription_request(self, args):
535
+ return {
536
+ 'op': 'subscribe',
537
+ 'args': args,
538
+ }
539
+
540
+ def handle_message(self, client: Client, message):
541
+ #
542
+ # message examples
543
+ #
544
+ # {
545
+ # arg: {
546
+ # channel: "trades",
547
+ # instId: "DOGE-USDT",
548
+ # },
549
+ # event: "subscribe"
550
+ # }
551
+ #
552
+ # incoming data updates' examples can be seen under each handler method
553
+ #
554
+ methods = {
555
+ # public
556
+ 'pong': self.handle_pong,
557
+ 'trades': self.handle_trades,
558
+ 'books': self.handle_order_book,
559
+ 'tickers': self.handle_ticker,
560
+ 'candle': self.handle_ohlcv, # candle1m, candle5m, etc
561
+ # private
562
+ 'account': self.handle_balance,
563
+ 'orders': self.handle_orders,
564
+ 'positions': self.handle_positions,
565
+ }
566
+ method = None
567
+ if message == 'pong':
568
+ method = self.safe_value(methods, 'pong')
569
+ else:
570
+ event = self.safe_string(message, 'event')
571
+ if event == 'subscribe':
572
+ return
573
+ elif event == 'login':
574
+ client.resolve(message, 'authenticate_hash')
575
+ return
576
+ elif event == 'error':
577
+ raise ExchangeError(self.id + ' error: ' + self.json(message))
578
+ arg = self.safe_dict(message, 'arg')
579
+ channelName = self.safe_string(arg, 'channel')
580
+ method = self.safe_value(methods, channelName)
581
+ if not method and channelName.find('candle') >= 0:
582
+ method = methods['candle']
583
+ if method:
584
+ method(client, message)
585
+
586
+ async def authenticate(self, params={}):
587
+ self.check_required_credentials()
588
+ milliseconds = self.milliseconds()
589
+ messageHash = 'authenticate_hash'
590
+ timestamp = str(milliseconds)
591
+ nonce = 'n_' + timestamp
592
+ auth = '/users/self/verify' + 'GET' + timestamp + '' + nonce
593
+ signature = self.string_to_base64(self.hmac(self.encode(auth), self.encode(self.secret), hashlib.sha256))
594
+ request = {
595
+ 'op': 'login',
596
+ 'args': [
597
+ {
598
+ 'apiKey': self.apiKey,
599
+ 'passphrase': self.password,
600
+ 'timestamp': timestamp,
601
+ 'nonce': nonce,
602
+ 'sign': signature,
603
+ },
604
+ ],
605
+ }
606
+ marketType = 'swap' # for now
607
+ url = self.implode_hostname(self.urls['api']['ws'][marketType]['private'])
608
+ await self.watch(url, messageHash, self.deep_extend(request, params), messageHash)