ccxt 3.1.13__py2.py3-none-any.whl → 3.1.15__py2.py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (64) hide show
  1. ccxt/__init__.py +1 -1
  2. ccxt/async_support/__init__.py +1 -1
  3. ccxt/async_support/base/exchange.py +30 -13
  4. ccxt/async_support/huobi.py +4 -1
  5. ccxt/async_support/okx.py +1 -0
  6. ccxt/async_support/poloniex.py +1 -1
  7. ccxt/base/exchange.py +30 -13
  8. ccxt/huobi.py +4 -1
  9. ccxt/okx.py +1 -0
  10. ccxt/poloniex.py +1 -1
  11. ccxt/pro/__init__.py +3 -1
  12. ccxt/pro/alpaca.py +3 -3
  13. ccxt/pro/ascendex.py +2 -2
  14. ccxt/pro/binance.py +7 -5
  15. ccxt/pro/bitfinex.py +1 -1
  16. ccxt/pro/bitfinex2.py +3 -3
  17. ccxt/pro/bitget.py +3 -3
  18. ccxt/pro/bitmart.py +2 -2
  19. ccxt/pro/bitmex.py +3 -3
  20. ccxt/pro/bitopro.py +1 -1
  21. ccxt/pro/bitpanda.py +1 -1
  22. ccxt/pro/bitstamp.py +2 -2
  23. ccxt/pro/bittrex.py +3 -3
  24. ccxt/pro/bitvavo.py +3 -3
  25. ccxt/pro/blockchaincom.py +2 -2
  26. ccxt/pro/btcex.py +3 -3
  27. ccxt/pro/bybit.py +3 -3
  28. ccxt/pro/cex.py +2 -2
  29. ccxt/pro/coinbasepro.py +3 -3
  30. ccxt/pro/coinex.py +18 -12
  31. ccxt/pro/cryptocom.py +3 -3
  32. ccxt/pro/currencycom.py +2 -2
  33. ccxt/pro/deribit.py +3 -3
  34. ccxt/pro/exmo.py +2 -2
  35. ccxt/pro/gate.py +4 -4
  36. ccxt/pro/gemini.py +2 -2
  37. ccxt/pro/hitbtc.py +2 -2
  38. ccxt/pro/hollaex.py +2 -2
  39. ccxt/pro/huobi.py +4 -4
  40. ccxt/pro/huobijp.py +2 -2
  41. ccxt/pro/idex.py +3 -3
  42. ccxt/pro/independentreserve.py +1 -1
  43. ccxt/pro/kraken.py +2 -2
  44. ccxt/pro/krakenfutures.py +4 -4
  45. ccxt/pro/kucoin.py +3 -3
  46. ccxt/pro/kucoinfutures.py +1 -1
  47. ccxt/pro/luno.py +1 -1
  48. ccxt/pro/mexc.py +3 -3
  49. ccxt/pro/ndax.py +2 -2
  50. ccxt/pro/okcoin.py +3 -3
  51. ccxt/pro/okx.py +2 -2
  52. ccxt/pro/phemex.py +3 -3
  53. ccxt/pro/poloniex.py +973 -0
  54. ccxt/pro/poloniexfutures.py +1 -1
  55. ccxt/pro/probit.py +2 -2
  56. ccxt/pro/upbit.py +1 -1
  57. ccxt/pro/wazirx.py +3 -3
  58. ccxt/pro/whitebit.py +4 -4
  59. ccxt/pro/woo.py +2 -2
  60. ccxt/test/base/test_shared_methods.py +13 -13
  61. {ccxt-3.1.13.dist-info → ccxt-3.1.15.dist-info}/METADATA +5 -5
  62. {ccxt-3.1.13.dist-info → ccxt-3.1.15.dist-info}/RECORD +64 -63
  63. {ccxt-3.1.13.dist-info → ccxt-3.1.15.dist-info}/WHEEL +0 -0
  64. {ccxt-3.1.13.dist-info → ccxt-3.1.15.dist-info}/top_level.txt +0 -0
ccxt/pro/poloniex.py ADDED
@@ -0,0 +1,973 @@
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
+ import hashlib
9
+ from ccxt.async_support.base.ws.client import Client
10
+ from typing import Optional
11
+ from typing import List
12
+ from ccxt.base.errors import ExchangeError
13
+ from ccxt.base.errors import BadRequest
14
+ from ccxt.base.errors import AuthenticationError
15
+ from ccxt.base.precise import Precise
16
+
17
+
18
+ class poloniex(ccxt.async_support.poloniex):
19
+
20
+ def describe(self):
21
+ return self.deep_extend(super(poloniex, self).describe(), {
22
+ 'has': {
23
+ 'ws': True,
24
+ 'watchOHLCV': True,
25
+ 'watchOrderBook': True,
26
+ 'watchTicker': True,
27
+ 'watchTickers': True,
28
+ 'watchTrades': True,
29
+ 'watchBalance': True,
30
+ 'watchStatus': False,
31
+ 'watchOrders': True,
32
+ 'watchMyTrades': True,
33
+ },
34
+ 'urls': {
35
+ 'api': {
36
+ 'ws': {
37
+ 'public': 'wss://ws.poloniex.com/ws/public',
38
+ 'private': 'wss://ws.poloniex.com/ws/private',
39
+ },
40
+ },
41
+ },
42
+ 'options': {
43
+ 'tradesLimit': 1000,
44
+ 'ordersLimit': 1000,
45
+ 'OHLCVLimit': 1000,
46
+ 'watchOrderBook': {
47
+ 'name': 'book_lv2', # can also be 'book'
48
+ },
49
+ 'connectionsLimit': 2000, # 2000 public, 2000 private, 4000 total, only for subscribe events, unsubscribe not restricted
50
+ 'requestsLimit': 500, # per second, only for subscribe events, unsubscribe not restricted
51
+ 'timeframes': {
52
+ '1m': 'candles_minute_1',
53
+ '5m': 'candles_minute_5',
54
+ '10m': 'candles_minute_10',
55
+ '15m': 'candles_minute_15',
56
+ '30m': 'candles_minute_30',
57
+ '1h': 'candles_hour_1',
58
+ '2h': 'candles_hour_2',
59
+ '4h': 'candles_hour_4',
60
+ '6h': 'candles_hour_6',
61
+ '12h': 'candles_hour_12',
62
+ '1d': 'candles_day_1',
63
+ '3d': 'candles_day_3',
64
+ '1w': 'candles_week_1',
65
+ '1M': 'candles_month_1',
66
+ },
67
+ },
68
+ 'streaming': {
69
+ 'keepAlive': 15000,
70
+ 'ping': self.ping,
71
+ },
72
+ })
73
+
74
+ async def authenticate(self, params={}):
75
+ """
76
+ * @ignore
77
+ authenticates the user to access private web socket channels
78
+ see https://docs.poloniex.com/#authenticated-channels-market-data-authentication
79
+ :returns dict: response from exchange
80
+ """
81
+ self.check_required_credentials()
82
+ timestamp = self.number_to_string(self.milliseconds())
83
+ url = self.urls['api']['ws']['private']
84
+ messageHash = 'authenticated'
85
+ client = self.client(url)
86
+ future = self.safe_value(client.subscriptions, messageHash)
87
+ if future is None:
88
+ accessPath = '/ws'
89
+ requestString = 'GET\n' + accessPath + '\nsignTimestamp=' + timestamp
90
+ signature = self.hmac(self.encode(requestString), self.encode(self.secret), hashlib.sha256, 'base64')
91
+ request = {
92
+ 'event': 'subscribe',
93
+ 'channel': ['auth'],
94
+ 'params': {
95
+ 'key': self.apiKey,
96
+ 'signTimestamp': timestamp,
97
+ 'signature': signature,
98
+ 'signatureMethod': 'HmacSHA256', # optional
99
+ 'signatureVersion': '2', # optional
100
+ },
101
+ }
102
+ message = self.extend(request, params)
103
+ future = await self.watch(url, messageHash, message)
104
+ #
105
+ # {
106
+ # "data": {
107
+ # "success": True,
108
+ # "ts": 1645597033915
109
+ # },
110
+ # "channel": "auth"
111
+ # }
112
+ #
113
+ # # Failure to return results
114
+ #
115
+ # {
116
+ # "data": {
117
+ # "success": False,
118
+ # "message": "Authentication failed!",
119
+ # "ts": 1646276295075
120
+ # },
121
+ # "channel": "auth"
122
+ # }
123
+ #
124
+ client.subscriptions[messageHash] = future
125
+ return future
126
+
127
+ async def subscribe(self, name: str, messageHash: str, isPrivate: bool, symbols: Optional[List[str]] = None, params={}):
128
+ """
129
+ * @ignore
130
+ Connects to a websocket channel
131
+ :param str name: name of the channel
132
+ :param boolean isPrivate: True for the authenticated url, False for the public url
133
+ :param [str]|None symbols: CCXT market symbols
134
+ :param dict params: extra parameters specific to the poloniex api
135
+ :returns dict: data from the websocket stream
136
+ """
137
+ publicOrPrivate = 'private' if isPrivate else 'public'
138
+ url = self.urls['api']['ws'][publicOrPrivate]
139
+ subscribe = {
140
+ 'event': 'subscribe',
141
+ 'channel': [
142
+ name,
143
+ ],
144
+ }
145
+ marketIds = []
146
+ if symbols is not None:
147
+ if len(symbols) == 1:
148
+ symbol = symbols[0]
149
+ marketId = self.market_id(symbol)
150
+ marketIds.append(marketId)
151
+ messageHash = messageHash + ':' + symbol
152
+ else:
153
+ for i in range(0, len(symbols)):
154
+ symbol = symbols[i]
155
+ marketIds.append(self.market_id(symbol))
156
+ else:
157
+ marketIds.append('all')
158
+ if name != 'balances':
159
+ subscribe['symbols'] = marketIds
160
+ request = self.extend(subscribe, params)
161
+ return await self.watch(url, messageHash, request, name)
162
+
163
+ async def watch_ohlcv(self, symbol: str, timeframe='1m', since: Optional[int] = None, limit: Optional[int] = None, params={}):
164
+ """
165
+ watches historical candlestick data containing the open, high, low, and close price, and the volume of a market
166
+ see https://docs.poloniex.com/#public-channels-market-data-candlesticks
167
+ :param str symbol: unified symbol of the market to fetch OHLCV data for
168
+ :param str timeframe: the length of time each candle represents
169
+ :param int|None since: timestamp in ms of the earliest candle to fetch
170
+ :param int|None limit: the maximum amount of candles to fetch
171
+ :param dict params: extra parameters specific to the poloniex api endpoint
172
+ :returns: [[int]] A list of candles ordered, open, high, low, close, volume
173
+ """
174
+ await self.load_markets()
175
+ timeframes = self.safe_value(self.options, 'timeframes', {})
176
+ channel = self.safe_string(timeframes, timeframe, timeframe)
177
+ if channel is None:
178
+ raise BadRequest(self.id + ' watchOHLCV cannot take a timeframe of ' + timeframe)
179
+ ohlcv = await self.subscribe(channel, channel, False, [symbol], params)
180
+ if self.newUpdates:
181
+ limit = ohlcv.getLimit(symbol, limit)
182
+ return self.filter_by_since_limit(ohlcv, since, limit, 0, True)
183
+
184
+ async def watch_ticker(self, symbol: str, params={}):
185
+ """
186
+ watches a price ticker, a statistical calculation with the information calculated over the past 24 hours for a specific market
187
+ see https://docs.poloniex.com/#public-channels-market-data-ticker
188
+ :param str symbol: unified symbol of the market to fetch the ticker for
189
+ :param dict params: extra parameters specific to the poloniex api endpoint
190
+ :returns dict: a `ticker structure <https://docs.ccxt.com/en/latest/manual.html#ticker-structure>`
191
+ """
192
+ await self.load_markets()
193
+ name = 'ticker'
194
+ return await self.subscribe(name, name, False, [symbol], params)
195
+
196
+ async def watch_tickers(self, symbols=None, params={}):
197
+ """
198
+ watches a price ticker, a statistical calculation with the information calculated over the past 24 hours for a specific market
199
+ see https://docs.poloniex.com/#public-channels-market-data-ticker
200
+ :param str symbol: unified symbol of the market to fetch the ticker for
201
+ :param dict params: extra parameters specific to the poloniex api endpoint
202
+ :returns dict: a `ticker structure <https://docs.ccxt.com/en/latest/manual.html#ticker-structure>`
203
+ """
204
+ await self.load_markets()
205
+ name = 'ticker'
206
+ return await self.subscribe(name, name, False, symbols, params)
207
+
208
+ async def watch_trades(self, symbol: str, since: Optional[int] = None, limit: Optional[int] = None, params={}):
209
+ """
210
+ get the list of most recent trades for a particular symbol
211
+ see https://docs.poloniex.com/#public-channels-market-data-trades
212
+ :param str symbol: unified symbol of the market to fetch trades for
213
+ :param int|None since: timestamp in ms of the earliest trade to fetch
214
+ :param int|None limit: the maximum amount of trades to fetch
215
+ :param dict params: extra parameters specific to the poloniex api endpoint
216
+ :returns [dict]: a list of `trade structures <https://docs.ccxt.com/en/latest/manual.html?#public-trades>`
217
+ """
218
+ await self.load_markets()
219
+ symbol = self.symbol(symbol)
220
+ name = 'trades'
221
+ trades = await self.subscribe(name, name, False, [symbol], params)
222
+ if self.newUpdates:
223
+ limit = trades.getLimit(symbol, limit)
224
+ return self.filter_by_since_limit(trades, since, limit, 'timestamp', True)
225
+
226
+ async def watch_order_book(self, symbol: str, limit: Optional[int] = None, params={}):
227
+ """
228
+ watches information on open orders with bid(buy) and ask(sell) prices, volumes and other data
229
+ see https://docs.poloniex.com/#public-channels-market-data-book-level-2
230
+ :param str symbol: unified symbol of the market to fetch the order book for
231
+ :param int|None limit: not used by poloniex watchOrderBook
232
+ :param dict params: extra parameters specific to the poloniex api endpoint
233
+ :returns dict: A dictionary of `order book structures <https://docs.ccxt.com/en/latest/manual.html#order-book-structure>` indexed by market symbols
234
+ """
235
+ await self.load_markets()
236
+ watchOrderBookOptions = self.safe_value(self.options, 'watchOrderBook')
237
+ name = self.safe_string(watchOrderBookOptions, 'name', 'book_lv2')
238
+ name, params = self.handle_option_and_params(params, 'method', 'name', name)
239
+ orderbook = await self.subscribe(name, name, False, [symbol], params)
240
+ return orderbook.limit()
241
+
242
+ async def watch_orders(self, symbol: Optional[str] = None, since: Optional[int] = None, limit: Optional[int] = None, params={}):
243
+ """
244
+ watches information on multiple orders made by the user
245
+ see https://docs.poloniex.com/#authenticated-channels-market-data-orders
246
+ :param str|None symbol: unified market symbol of the market orders were made in
247
+ :param int|None since: not used by poloniex watchOrders
248
+ :param int|None limit: not used by poloniex watchOrders
249
+ :param dict params: extra parameters specific to the poloniex api endpoint
250
+ :returns [dict]: a list of `order structures <https://docs.ccxt.com/en/latest/manual.html#order-structure>`
251
+ """
252
+ await self.load_markets()
253
+ name = 'orders'
254
+ await self.authenticate()
255
+ if symbol is not None:
256
+ symbol = self.symbol(symbol)
257
+ symbols = None if (symbol is None) else [symbol]
258
+ orders = await self.subscribe(name, name, True, symbols, params)
259
+ if self.newUpdates:
260
+ limit = orders.getLimit(symbol, limit)
261
+ return self.filter_by_since_limit(orders, since, limit, 'timestamp', True)
262
+
263
+ async def watch_my_trades(self, symbol: Optional[str] = None, since: Optional[int] = None, limit: Optional[int] = None, params={}):
264
+ """
265
+ watches information on multiple trades made by the user using orders stream
266
+ see https://docs.poloniex.com/#authenticated-channels-market-data-orders
267
+ :param str|None symbol: unified market symbol of the market orders were made in
268
+ :param int|None since: not used by poloniex watchMyTrades
269
+ :param int|None limit: not used by poloniex watchMyTrades
270
+ :param dict params: extra parameters specific to the poloniex strean
271
+ :returns [dict]: a list of `trade structures <https://docs.ccxt.com/#/?id=trade-structure>`
272
+ """
273
+ await self.load_markets()
274
+ name = 'orders'
275
+ messageHash = 'myTrades'
276
+ await self.authenticate()
277
+ if symbol is not None:
278
+ symbol = self.symbol(symbol)
279
+ symbols = None if (symbol is None) else [symbol]
280
+ trades = await self.subscribe(name, messageHash, True, symbols, params)
281
+ if self.newUpdates:
282
+ limit = trades.getLimit(symbol, limit)
283
+ return self.filter_by_since_limit(trades, since, limit, 'timestamp', True)
284
+
285
+ async def watch_balance(self, params={}):
286
+ """
287
+ watches information on multiple orders made by the user
288
+ see https://docs.poloniex.com/#authenticated-channels-market-data-balances
289
+ :param str|None symbol: not used by poloniex watchBalance
290
+ :param int|None since: not used by poloniex watchBalance
291
+ :param int|None limit: not used by poloniex watchBalance
292
+ :param dict params: extra parameters specific to the poloniex api endpoint
293
+ :returns [dict]: a list of `order structures <https://docs.ccxt.com/en/latest/manual.html#order-structure>`
294
+ """
295
+ await self.load_markets()
296
+ name = 'balances'
297
+ await self.authenticate()
298
+ return await self.subscribe(name, name, True, None, params)
299
+
300
+ def parse_ws_ohlcv(self, ohlcv, market=None):
301
+ #
302
+ # {
303
+ # symbol: 'BTC_USDT',
304
+ # amount: '840.7240416',
305
+ # high: '24832.35',
306
+ # quantity: '0.033856',
307
+ # tradeCount: 1,
308
+ # low: '24832.35',
309
+ # closeTime: 1676942519999,
310
+ # startTime: 1676942460000,
311
+ # close: '24832.35',
312
+ # open: '24832.35',
313
+ # ts: 1676942492072
314
+ # }
315
+ #
316
+ return [
317
+ self.safe_integer(ohlcv, 'startTime'),
318
+ self.safe_number(ohlcv, 'open'),
319
+ self.safe_number(ohlcv, 'high'),
320
+ self.safe_number(ohlcv, 'low'),
321
+ self.safe_number(ohlcv, 'close'),
322
+ self.safe_number(ohlcv, 'quantity'),
323
+ ]
324
+
325
+ def handle_ohlcv(self, client: Client, message):
326
+ #
327
+ # {
328
+ # channel: 'candles_minute_1',
329
+ # data: [
330
+ # {
331
+ # symbol: 'BTC_USDT',
332
+ # amount: '840.7240416',
333
+ # high: '24832.35',
334
+ # quantity: '0.033856',
335
+ # tradeCount: 1,
336
+ # low: '24832.35',
337
+ # closeTime: 1676942519999,
338
+ # startTime: 1676942460000,
339
+ # close: '24832.35',
340
+ # open: '24832.35',
341
+ # ts: 1676942492072
342
+ # }
343
+ # ]
344
+ # }
345
+ #
346
+ data = self.safe_value(message, 'data')
347
+ data = self.safe_value(data, 0)
348
+ channel = self.safe_string(message, 'channel')
349
+ marketId = self.safe_string(data, 'symbol')
350
+ symbol = self.safe_symbol(marketId)
351
+ market = self.safe_market(symbol)
352
+ timeframe = self.find_timeframe(channel)
353
+ messageHash = channel + ':' + symbol
354
+ parsed = self.parse_ws_ohlcv(data, market)
355
+ self.ohlcvs[symbol] = self.safe_value(self.ohlcvs, symbol, {})
356
+ stored = self.safe_value(self.ohlcvs[symbol], timeframe)
357
+ if symbol is not None:
358
+ if stored is None:
359
+ limit = self.safe_integer(self.options, 'OHLCVLimit', 1000)
360
+ stored = ArrayCacheByTimestamp(limit)
361
+ self.ohlcvs[symbol][timeframe] = stored
362
+ stored.append(parsed)
363
+ client.resolve(stored, messageHash)
364
+ return message
365
+
366
+ def handle_trade(self, client: Client, message):
367
+ #
368
+ # {
369
+ # channel: 'trades',
370
+ # data: [
371
+ # {
372
+ # symbol: 'BTC_USDT',
373
+ # amount: '13.41634893',
374
+ # quantity: '0.000537',
375
+ # takerSide: 'buy',
376
+ # createTime: 1676950548834,
377
+ # price: '24983.89',
378
+ # id: '62486976',
379
+ # ts: 1676950548839
380
+ # }
381
+ # ]
382
+ # }
383
+ #
384
+ data = self.safe_value(message, 'data', [])
385
+ for i in range(0, len(data)):
386
+ item = data[i]
387
+ marketId = self.safe_string(item, 'symbol')
388
+ if marketId is not None:
389
+ trade = self.parse_ws_trade(item)
390
+ symbol = trade['symbol']
391
+ type = 'trades'
392
+ messageHash = type + ':' + symbol
393
+ tradesArray = self.safe_value(self.trades, symbol)
394
+ if tradesArray is None:
395
+ tradesLimit = self.safe_integer(self.options, 'tradesLimit', 1000)
396
+ tradesArray = ArrayCache(tradesLimit)
397
+ self.trades[symbol] = tradesArray
398
+ tradesArray.append(trade)
399
+ client.resolve(tradesArray, messageHash)
400
+ return message
401
+
402
+ def parse_ws_trade(self, trade, market=None):
403
+ #
404
+ # handleTrade
405
+ #
406
+ # {
407
+ # symbol: 'BTC_USDT',
408
+ # amount: '13.41634893',
409
+ # quantity: '0.000537',
410
+ # takerSide: 'buy',
411
+ # createTime: 1676950548834,
412
+ # price: '24983.89',
413
+ # id: '62486976',
414
+ # ts: 1676950548839
415
+ # }
416
+ #
417
+ # private trade
418
+ # {
419
+ # "orderId":"186250258089635840",
420
+ # "tradeId":"62036513",
421
+ # "clientOrderId":"",
422
+ # "accountType":"SPOT",
423
+ # "eventType":"trade",
424
+ # "symbol":"ADA_USDT",
425
+ # "side":"SELL",
426
+ # "type":"MARKET",
427
+ # "price":"0",
428
+ # "quantity":"3",
429
+ # "state":"FILLED",
430
+ # "createTime":1685371921891,
431
+ # "tradeTime":1685371921908,
432
+ # "tradePrice":"0.37694",
433
+ # "tradeQty":"3",
434
+ # "feeCurrency":"USDT",
435
+ # "tradeFee":"0.00226164",
436
+ # "tradeAmount":"1.13082",
437
+ # "filledQuantity":"3",
438
+ # "filledAmount":"1.13082",
439
+ # "ts":1685371921945,
440
+ # "source":"WEB",
441
+ # "orderAmount":"0",
442
+ # "matchRole":"TAKER"
443
+ # }
444
+ #
445
+ marketId = self.safe_string(trade, 'symbol')
446
+ market = self.safe_market(marketId, market)
447
+ timestamp = self.safe_integer(trade, 'createTime')
448
+ takerMaker = self.safe_string_lower_2(trade, 'matchRole', 'taker')
449
+ return self.safe_trade({
450
+ 'info': trade,
451
+ 'id': self.safe_string_2(trade, 'id', 'tradeId'),
452
+ 'symbol': self.safe_string(market, 'symbol'),
453
+ 'timestamp': timestamp,
454
+ 'datetime': self.iso8601(timestamp),
455
+ 'order': self.safe_string(trade, 'orderId'),
456
+ 'type': self.safe_string_lower(trade, 'type'),
457
+ 'side': self.safe_string_lower_2(trade, 'takerSide', 'side'),
458
+ 'takerOrMaker': takerMaker,
459
+ 'price': self.omit_zero(self.safe_number_2(trade, 'tradePrice', 'price')),
460
+ 'amount': self.omit_zero(self.safe_number_2(trade, 'filledQuantity', 'quantity')),
461
+ 'cost': self.safe_string_2(trade, 'amount', 'filledAmount'),
462
+ 'fee': {
463
+ 'rate': None,
464
+ 'cost': self.safe_string(trade, 'tradeFee'),
465
+ 'currency': self.safe_string(trade, 'feeCurrency'),
466
+ },
467
+ }, market)
468
+
469
+ def parse_status(self, status):
470
+ statuses = {
471
+ 'NEW': 'open',
472
+ 'PARTIALLY_FILLED': 'open',
473
+ 'FILLED': 'closed',
474
+ 'PENDING_CANCEL': 'open',
475
+ 'PARTIALLY_CANCELED': 'open',
476
+ 'CANCELED': 'canceled',
477
+ # FAILED
478
+ }
479
+ return self.safe_string(statuses, status, status)
480
+
481
+ def parse_ws_order_trade(self, trade, market=None):
482
+ #
483
+ # {
484
+ # "symbol": "BTC_USDT",
485
+ # "type": "LIMIT",
486
+ # "quantity": "1",
487
+ # "orderId": "32471407854219264",
488
+ # "tradeFee": "0",
489
+ # "clientOrderId": "",
490
+ # "accountType": "SPOT",
491
+ # "feeCurrency": "",
492
+ # "eventType": "place",
493
+ # "source": "API",
494
+ # "side": "BUY",
495
+ # "filledQuantity": "0",
496
+ # "filledAmount": "0",
497
+ # "matchRole": "MAKER",
498
+ # "state": "NEW",
499
+ # "tradeTime": 0,
500
+ # "tradeAmount": "0",
501
+ # "orderAmount": "0",
502
+ # "createTime": 1648708186922,
503
+ # "price": "47112.1",
504
+ # "tradeQty": "0",
505
+ # "tradePrice": "0",
506
+ # "tradeId": "0",
507
+ # "ts": 1648708187469
508
+ # }
509
+ #
510
+ timestamp = self.safe_integer(trade, 'tradeTime')
511
+ marketId = self.safe_string(trade, 'symbol')
512
+ return self.safe_trade({
513
+ 'info': trade,
514
+ 'id': self.safe_string(trade, 'tradeId'),
515
+ 'symbol': self.safe_symbol(marketId, market),
516
+ 'timestamp': timestamp,
517
+ 'datetime': self.iso8601(timestamp),
518
+ 'order': self.safe_string(trade, 'orderId'),
519
+ 'type': self.safe_string_lower(trade, 'type'),
520
+ 'side': self.safe_string(trade, 'side'),
521
+ 'takerOrMaker': self.safe_string_lower(trade, 'matchRole'),
522
+ 'price': self.safe_string(trade, 'price'),
523
+ 'amount': self.safe_string(trade, 'tradeAmount'),
524
+ 'cost': None,
525
+ 'fee': {
526
+ 'rate': None,
527
+ 'cost': self.safe_string(trade, 'tradeFee'),
528
+ 'currency': self.safe_string(trade, 'feeCurrency'),
529
+ },
530
+ }, market)
531
+
532
+ def handle_order(self, client: Client, message):
533
+ #
534
+ # Order is created
535
+ #
536
+ # {
537
+ # channel: 'orders',
538
+ # data: [
539
+ # {
540
+ # "symbol": "BTC_USDT",
541
+ # "type": "LIMIT",
542
+ # "quantity": "1",
543
+ # "orderId": "32471407854219264",
544
+ # "tradeFee": "0",
545
+ # "clientOrderId": "",
546
+ # "accountType": "SPOT",
547
+ # "feeCurrency": "",
548
+ # "eventType": "place",
549
+ # "source": "API",
550
+ # "side": "BUY",
551
+ # "filledQuantity": "0",
552
+ # "filledAmount": "0",
553
+ # "matchRole": "MAKER",
554
+ # "state": "NEW",
555
+ # "tradeTime": 0,
556
+ # "tradeAmount": "0",
557
+ # "orderAmount": "0",
558
+ # "createTime": 1648708186922,
559
+ # "price": "47112.1",
560
+ # "tradeQty": "0",
561
+ # "tradePrice": "0",
562
+ # "tradeId": "0",
563
+ # "ts": 1648708187469
564
+ # }
565
+ # ]
566
+ # }
567
+ #
568
+ data = self.safe_value(message, 'data', [])
569
+ orders = self.orders
570
+ if orders is None:
571
+ limit = self.safe_integer(self.options, 'ordersLimit')
572
+ orders = ArrayCacheBySymbolById(limit)
573
+ self.orders = orders
574
+ marketIds = []
575
+ for i in range(0, len(data)):
576
+ order = self.safe_value(data, i)
577
+ marketId = self.safe_string(order, 'symbol')
578
+ eventType = self.safe_string(order, 'eventType')
579
+ if marketId is not None:
580
+ symbol = self.safe_symbol(marketId)
581
+ orderId = self.safe_string(order, 'orderId')
582
+ clientOrderId = self.safe_string(order, 'clientOrderId')
583
+ if eventType == 'place' or eventType == 'canceled':
584
+ parsed = self.parse_ws_order(order)
585
+ orders.append(parsed)
586
+ else:
587
+ previousOrders = self.safe_value(orders.hashmap, symbol, {})
588
+ previousOrder = self.safe_value_2(previousOrders, orderId, clientOrderId)
589
+ trade = self.parse_ws_trade(order)
590
+ self.handle_my_trades(client, trade)
591
+ if previousOrder['trades'] is None:
592
+ previousOrder['trades'] = []
593
+ previousOrder['trades'].append(trade)
594
+ previousOrder['lastTradeTimestamp'] = trade['timestamp']
595
+ totalCost = '0'
596
+ totalAmount = '0'
597
+ previousOrderTrades = previousOrder['trades']
598
+ for i in range(0, len(previousOrderTrades)):
599
+ previousOrderTrade = previousOrderTrades[i]
600
+ cost = self.number_to_string(previousOrderTrade['cost'])
601
+ amount = self.number_to_string(previousOrderTrade['amount'])
602
+ totalCost = Precise.string_add(totalCost, cost)
603
+ totalAmount = Precise.string_add(totalAmount, amount)
604
+ if Precise.string_gt(totalAmount, '0'):
605
+ previousOrder['average'] = self.parse_number(Precise.string_div(totalCost, totalAmount))
606
+ previousOrder['cost'] = self.parse_number(totalCost)
607
+ if previousOrder['filled'] is not None:
608
+ tradeAmount = self.number_to_string(trade['amount'])
609
+ previousOrderFilled = self.number_to_string(previousOrder['filled'])
610
+ previousOrderFilled = Precise.string_add(previousOrderFilled, tradeAmount)
611
+ previousOrder['filled'] = previousOrderFilled
612
+ if previousOrder['amount'] is not None:
613
+ previousOrderAmount = self.number_to_string(previousOrder['amount'])
614
+ previousOrder['remaining'] = self.parse_number(Precise.string_sub(previousOrderAmount, previousOrderFilled))
615
+ if previousOrder['fee'] is None:
616
+ previousOrder['fee'] = {
617
+ 'rate': None,
618
+ 'cost': 0,
619
+ 'currency': trade['fee']['currency'],
620
+ }
621
+ if (previousOrder['fee']['cost'] is not None) and (trade['fee']['cost'] is not None):
622
+ stringOrderCost = self.number_to_string(previousOrder['fee']['cost'])
623
+ stringTradeCost = self.number_to_string(trade['fee']['cost'])
624
+ previousOrder['fee']['cost'] = Precise.string_add(stringOrderCost, stringTradeCost)
625
+ rawState = self.safe_string(order, 'state')
626
+ state = self.parse_status(rawState)
627
+ previousOrder['status'] = state
628
+ # update the newUpdates count
629
+ orders.append(previousOrder)
630
+ marketIds.append(marketId)
631
+ for i in range(0, len(marketIds)):
632
+ marketId = marketIds[i]
633
+ market = self.market(marketId)
634
+ symbol = market['symbol']
635
+ messageHash = 'orders:' + symbol
636
+ client.resolve(orders[symbol], messageHash)
637
+ client.resolve(orders, 'orders')
638
+ return message
639
+
640
+ def parse_ws_order(self, order, market=None):
641
+ #
642
+ # {
643
+ # "symbol": "BTC_USDT",
644
+ # "type": "LIMIT",
645
+ # "quantity": "1",
646
+ # "orderId": "32471407854219264",
647
+ # "tradeFee": "0",
648
+ # "clientOrderId": "",
649
+ # "accountType": "SPOT",
650
+ # "feeCurrency": "",
651
+ # "eventType": "place",
652
+ # "source": "API",
653
+ # "side": "BUY",
654
+ # "filledQuantity": "0",
655
+ # "filledAmount": "0",
656
+ # "matchRole": "MAKER",
657
+ # "state": "NEW",
658
+ # "tradeTime": 0,
659
+ # "tradeAmount": "0",
660
+ # "orderAmount": "0",
661
+ # "createTime": 1648708186922,
662
+ # "price": "47112.1",
663
+ # "tradeQty": "0",
664
+ # "tradePrice": "0",
665
+ # "tradeId": "0",
666
+ # "ts": 1648708187469
667
+ # }
668
+ #
669
+ id = self.safe_string(order, 'orderId')
670
+ clientOrderId = self.safe_string(order, 'clientOrderId')
671
+ marketId = self.safe_string(order, 'symbol')
672
+ timestamp = self.safe_string(order, 'ts')
673
+ filledAmount = self.safe_string(order, 'filledAmount')
674
+ status = self.safe_string(order, 'state')
675
+ trades = None
676
+ if not Precise.string_eq(filledAmount, '0'):
677
+ trades = []
678
+ trade = self.parse_ws_order_trade(order)
679
+ trades.append(trade)
680
+ return self.safe_order({
681
+ 'info': order,
682
+ 'symbol': self.safe_symbol(marketId, market),
683
+ 'id': id,
684
+ 'clientOrderId': clientOrderId,
685
+ 'timestamp': timestamp,
686
+ 'datetime': self.iso8601(timestamp),
687
+ 'lastTradeTimestamp': None,
688
+ 'type': self.safe_string(order, 'type'),
689
+ 'timeInForce': None,
690
+ 'postOnly': None,
691
+ 'side': self.safe_string(order, 'side'),
692
+ 'price': self.safe_string(order, 'price'),
693
+ 'stopPrice': None,
694
+ 'triggerPrice': None,
695
+ 'amount': self.safe_string(order, 'quantity'),
696
+ 'cost': None,
697
+ 'average': None,
698
+ 'filled': filledAmount,
699
+ 'remaining': self.safe_string(order, 'remaining_size'),
700
+ 'status': self.parse_status(status),
701
+ 'fee': {
702
+ 'rate': None,
703
+ 'cost': self.safe_string(order, 'tradeFee'),
704
+ 'currency': self.safe_string(order, 'feeCurrency'),
705
+ },
706
+ 'trades': trades,
707
+ })
708
+
709
+ def handle_ticker(self, client: Client, message):
710
+ #
711
+ # {
712
+ # channel: 'ticker',
713
+ # data: [
714
+ # {
715
+ # symbol: 'BTC_USDT',
716
+ # startTime: 1677280800000,
717
+ # open: '23154.32',
718
+ # high: '23212.21',
719
+ # low: '22761.01',
720
+ # close: '23148.86',
721
+ # quantity: '105.179566',
722
+ # amount: '2423161.17436702',
723
+ # tradeCount: 17582,
724
+ # dailyChange: '-0.0002',
725
+ # markPrice: '23151.09',
726
+ # closeTime: 1677367197924,
727
+ # ts: 1677367251090
728
+ # }
729
+ # ]
730
+ # }
731
+ #
732
+ data = self.safe_value(message, 'data', [])
733
+ for i in range(0, len(data)):
734
+ item = data[i]
735
+ marketId = self.safe_string(item, 'symbol')
736
+ if marketId is not None:
737
+ ticker = self.parse_ticker(item)
738
+ symbol = ticker['symbol']
739
+ self.tickers[symbol] = ticker
740
+ messageHash = 'ticker:' + symbol
741
+ client.resolve(ticker, messageHash)
742
+ client.resolve(self.tickers, 'ticker')
743
+ return message
744
+
745
+ def handle_order_book(self, client: Client, message):
746
+ #
747
+ # snapshot
748
+ #
749
+ # {
750
+ # channel: 'book_lv2',
751
+ # data: [
752
+ # {
753
+ # symbol: 'BTC_USDT',
754
+ # createTime: 1677368876253,
755
+ # "asks": [
756
+ # ["5.65", "0.02"],
757
+ # ...
758
+ # ],
759
+ # "bids": [
760
+ # ["6.16", "0.6"],
761
+ # ...
762
+ # ],
763
+ # lastId: 164148724,
764
+ # id: 164148725,
765
+ # ts: 1677368876316
766
+ # }
767
+ # ],
768
+ # action: 'snapshot'
769
+ # }
770
+ #
771
+ # update
772
+ #
773
+ # {
774
+ # channel: 'book_lv2',
775
+ # data: [
776
+ # {
777
+ # symbol: 'BTC_USDT',
778
+ # createTime: 1677368876882,
779
+ # "asks": [
780
+ # ["6.35", "3"]
781
+ # ],
782
+ # "bids": [
783
+ # ["5.65", "0.02"]
784
+ # ],
785
+ # lastId: 164148725,
786
+ # id: 164148726,
787
+ # ts: 1677368876890
788
+ # }
789
+ # ],
790
+ # action: 'update'
791
+ # }
792
+ #
793
+ data = self.safe_value(message, 'data', [])
794
+ type = self.safe_string(message, 'action')
795
+ snapshot = type == 'snapshot'
796
+ update = type == 'update'
797
+ for i in range(0, len(data)):
798
+ item = data[i]
799
+ marketId = self.safe_string(item, 'symbol')
800
+ market = self.safe_market(marketId)
801
+ symbol = market['symbol']
802
+ name = 'book_lv2'
803
+ messageHash = name + ':' + symbol
804
+ subscription = self.safe_value(client.subscriptions, messageHash, {})
805
+ limit = self.safe_integer(subscription, 'limit')
806
+ timestamp = self.safe_integer(item, 'ts')
807
+ asks = self.safe_value(item, 'asks')
808
+ bids = self.safe_value(item, 'bids')
809
+ if snapshot or update:
810
+ if snapshot:
811
+ self.orderbooks[symbol] = self.order_book({}, limit)
812
+ orderbook = self.orderbooks[symbol]
813
+ if bids is not None:
814
+ for i in range(0, len(bids)):
815
+ bid = self.safe_value(bids, i)
816
+ price = self.safe_number(bid, 0)
817
+ amount = self.safe_number(bid, 1)
818
+ orderbook['bids'].store(price, amount)
819
+ if asks is not None:
820
+ for i in range(0, len(asks)):
821
+ ask = self.safe_value(asks, i)
822
+ price = self.safe_number(ask, 0)
823
+ amount = self.safe_number(ask, 1)
824
+ orderbook['asks'].store(price, amount)
825
+ orderbook['symbol'] = symbol
826
+ orderbook['timestamp'] = timestamp
827
+ orderbook['datetime'] = self.iso8601(timestamp)
828
+ client.resolve(orderbook, messageHash)
829
+
830
+ def handle_balance(self, client: Client, message):
831
+ #
832
+ # {
833
+ # "channel": "balances",
834
+ # "data": [
835
+ # {
836
+ # "changeTime": 1657312008411,
837
+ # "accountId": "1234",
838
+ # "accountType": "SPOT",
839
+ # "eventType": "place_order",
840
+ # "available": "9999999983.668",
841
+ # "currency": "BTC",
842
+ # "id": 60018450912695040,
843
+ # "userId": 12345,
844
+ # "hold": "16.332",
845
+ # "ts": 1657312008443
846
+ # }
847
+ # ]
848
+ # }
849
+ #
850
+ data = self.safe_value(message, 'data', [])
851
+ messageHash = 'balances'
852
+ self.balance = self.parse_ws_balance(data)
853
+ client.resolve(self.balance, messageHash)
854
+
855
+ def parse_ws_balance(self, response):
856
+ #
857
+ # [
858
+ # {
859
+ # "changeTime": 1657312008411,
860
+ # "accountId": "1234",
861
+ # "accountType": "SPOT",
862
+ # "eventType": "place_order",
863
+ # "available": "9999999983.668",
864
+ # "currency": "BTC",
865
+ # "id": 60018450912695040,
866
+ # "userId": 12345,
867
+ # "hold": "16.332",
868
+ # "ts": 1657312008443
869
+ # }
870
+ # ]
871
+ #
872
+ firstBalance = self.safe_value(response, 0, {})
873
+ timestamp = self.safe_integer(firstBalance, 'ts')
874
+ result = {
875
+ 'info': response,
876
+ 'timestamp': timestamp,
877
+ 'datetime': self.iso8601(timestamp),
878
+ }
879
+ for i in range(0, len(response)):
880
+ balance = self.safe_value(response, i)
881
+ currencyId = self.safe_string(balance, 'currency')
882
+ code = self.safe_currency_code(currencyId)
883
+ newAccount = self.account()
884
+ newAccount['free'] = self.safe_string(balance, 'available')
885
+ newAccount['used'] = self.safe_string(balance, 'hold')
886
+ result[code] = newAccount
887
+ return self.safe_balance(result)
888
+
889
+ def handle_my_trades(self, client: Client, parsedTrade):
890
+ # emulated using the orders' stream
891
+ messageHash = 'myTrades'
892
+ symbol = parsedTrade['symbol']
893
+ if self.myTrades is None:
894
+ limit = self.safe_integer(self.options, 'tradesLimit', 1000)
895
+ self.myTrades = ArrayCacheBySymbolById(limit)
896
+ trades = self.myTrades
897
+ trades.append(parsedTrade)
898
+ client.resolve(trades, messageHash)
899
+ symbolMessageHash = messageHash + ':' + symbol
900
+ client.resolve(trades, symbolMessageHash)
901
+
902
+ def handle_message(self, client: Client, message):
903
+ if self.handle_error_message(client, message):
904
+ return
905
+ type = self.safe_string(message, 'channel')
906
+ event = self.safe_string(message, 'event')
907
+ if event == 'pong':
908
+ client.lastPong = self.milliseconds()
909
+ methods = {
910
+ 'candles_minute_1': self.handle_ohlcv,
911
+ 'candles_minute_5': self.handle_ohlcv,
912
+ 'candles_minute_10': self.handle_ohlcv,
913
+ 'candles_minute_15': self.handle_ohlcv,
914
+ 'candles_minute_30': self.handle_ohlcv,
915
+ 'candles_hour_1': self.handle_ohlcv,
916
+ 'candles_hour_2': self.handle_ohlcv,
917
+ 'candles_hour_4': self.handle_ohlcv,
918
+ 'candles_hour_6': self.handle_ohlcv,
919
+ 'candles_hour_12': self.handle_ohlcv,
920
+ 'candles_day_1': self.handle_ohlcv,
921
+ 'candles_day_3': self.handle_ohlcv,
922
+ 'candles_week_1': self.handle_ohlcv,
923
+ 'candles_month_1': self.handle_ohlcv,
924
+ 'book': self.handle_order_book,
925
+ 'book_lv2': self.handle_order_book,
926
+ 'ticker': self.handle_ticker,
927
+ 'trades': self.handle_trade,
928
+ 'orders': self.handle_order,
929
+ 'balances': self.handle_balance,
930
+ }
931
+ method = self.safe_value(methods, type)
932
+ if type == 'auth':
933
+ self.handle_authenticate(client, message)
934
+ else:
935
+ data = self.safe_value(message, 'data', [])
936
+ dataLength = len(data)
937
+ if dataLength > 0:
938
+ return method(client, message)
939
+
940
+ def handle_error_message(self, client: Client, message):
941
+ #
942
+ # {message: 'Invalid channel value ["ordersss"]', event: 'error'}
943
+ event = self.safe_string(message, 'event')
944
+ if event == 'error':
945
+ error = self.safe_string(message, 'message')
946
+ raise ExchangeError(self.id + ' error: ' + self.json(error))
947
+ return False
948
+
949
+ def handle_authenticate(self, client: Client, message):
950
+ #
951
+ # {
952
+ # success: True,
953
+ # ret_msg: '',
954
+ # op: 'auth',
955
+ # conn_id: 'ce3dpomvha7dha97tvp0-2xh'
956
+ # }
957
+ #
958
+ data = self.safe_value(message, 'data')
959
+ success = self.safe_value(data, 'success')
960
+ messageHash = 'authenticated'
961
+ if success:
962
+ client.resolve(message, messageHash)
963
+ else:
964
+ error = AuthenticationError(self.id + ' ' + self.json(message))
965
+ client.reject(error, messageHash)
966
+ if messageHash in client.subscriptions:
967
+ del client.subscriptions[messageHash]
968
+ return message
969
+
970
+ def ping(self, client):
971
+ return {
972
+ 'event': 'ping',
973
+ }