ccxt 4.3.89__py2.py3-none-any.whl → 4.3.91__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 (57) hide show
  1. ccxt/__init__.py +1 -1
  2. ccxt/abstract/binance.py +1 -1
  3. ccxt/abstract/binancecoinm.py +1 -1
  4. ccxt/abstract/binanceus.py +1 -1
  5. ccxt/abstract/binanceusdm.py +1 -1
  6. ccxt/abstract/kucoin.py +1 -0
  7. ccxt/abstract/kucoinfutures.py +1 -0
  8. ccxt/alpaca.py +2 -2
  9. ccxt/ascendex.py +95 -97
  10. ccxt/async_support/__init__.py +1 -1
  11. ccxt/async_support/alpaca.py +2 -2
  12. ccxt/async_support/ascendex.py +95 -97
  13. ccxt/async_support/base/exchange.py +1 -1
  14. ccxt/async_support/binance.py +1 -1
  15. ccxt/async_support/bingx.py +29 -17
  16. ccxt/async_support/bitfinex2.py +21 -22
  17. ccxt/async_support/bitget.py +2 -2
  18. ccxt/async_support/bitmart.py +4 -8
  19. ccxt/async_support/coinbaseinternational.py +2 -1
  20. ccxt/async_support/coinex.py +1 -16
  21. ccxt/async_support/hitbtc.py +2 -0
  22. ccxt/async_support/huobijp.py +0 -8
  23. ccxt/async_support/kucoin.py +53 -21
  24. ccxt/async_support/kucoinfutures.py +22 -2
  25. ccxt/async_support/latoken.py +1 -0
  26. ccxt/async_support/okx.py +1 -8
  27. ccxt/async_support/whitebit.py +5 -3
  28. ccxt/async_support/woo.py +1 -1
  29. ccxt/base/exchange.py +1 -1
  30. ccxt/binance.py +1 -1
  31. ccxt/bingx.py +29 -17
  32. ccxt/bitfinex2.py +21 -22
  33. ccxt/bitget.py +2 -2
  34. ccxt/bitmart.py +4 -8
  35. ccxt/coinbaseinternational.py +2 -1
  36. ccxt/coinex.py +1 -16
  37. ccxt/hitbtc.py +2 -0
  38. ccxt/huobijp.py +0 -8
  39. ccxt/kucoin.py +53 -21
  40. ccxt/kucoinfutures.py +22 -2
  41. ccxt/latoken.py +1 -0
  42. ccxt/okx.py +1 -8
  43. ccxt/pro/__init__.py +1 -1
  44. ccxt/pro/binance.py +280 -0
  45. ccxt/pro/bingx.py +235 -85
  46. ccxt/pro/bithumb.py +4 -0
  47. ccxt/pro/bybit.py +1 -1
  48. ccxt/pro/coinex.py +941 -662
  49. ccxt/pro/lbank.py +1 -2
  50. ccxt/pro/okx.py +142 -2
  51. ccxt/whitebit.py +5 -3
  52. ccxt/woo.py +1 -1
  53. {ccxt-4.3.89.dist-info → ccxt-4.3.91.dist-info}/METADATA +4 -4
  54. {ccxt-4.3.89.dist-info → ccxt-4.3.91.dist-info}/RECORD +57 -57
  55. {ccxt-4.3.89.dist-info → ccxt-4.3.91.dist-info}/LICENSE.txt +0 -0
  56. {ccxt-4.3.89.dist-info → ccxt-4.3.91.dist-info}/WHEEL +0 -0
  57. {ccxt-4.3.89.dist-info → ccxt-4.3.91.dist-info}/top_level.txt +0 -0
ccxt/pro/coinex.py CHANGED
@@ -4,7 +4,8 @@
4
4
  # https://github.com/ccxt/ccxt/blob/master/CONTRIBUTING.md#how-to-contribute-code
5
5
 
6
6
  import ccxt.async_support
7
- from ccxt.async_support.base.ws.cache import ArrayCache, ArrayCacheBySymbolById, ArrayCacheByTimestamp
7
+ from ccxt.async_support.base.ws.cache import ArrayCache, ArrayCacheBySymbolById
8
+ import hashlib
8
9
  from ccxt.base.types import Balances, Int, Order, OrderBook, Str, Strings, Ticker, Tickers, Trade
9
10
  from ccxt.async_support.base.ws.client import Client
10
11
  from typing import List
@@ -12,9 +13,9 @@ from ccxt.base.errors import ExchangeError
12
13
  from ccxt.base.errors import AuthenticationError
13
14
  from ccxt.base.errors import BadRequest
14
15
  from ccxt.base.errors import NotSupported
16
+ from ccxt.base.errors import RateLimitExceeded
15
17
  from ccxt.base.errors import ExchangeNotAvailable
16
18
  from ccxt.base.errors import RequestTimeout
17
- from ccxt.base.precise import Precise
18
19
 
19
20
 
20
21
  class coinex(ccxt.async_support.coinex):
@@ -24,25 +25,30 @@ class coinex(ccxt.async_support.coinex):
24
25
  'has': {
25
26
  'ws': True,
26
27
  'watchBalance': True,
28
+ 'watchBidsAsks': True,
27
29
  'watchTicker': True,
28
30
  'watchTickers': True,
29
31
  'watchTrades': True,
30
- 'watchMyTrades': False, # can query but can't subscribe
32
+ 'watchTradesForSymbols': True,
33
+ 'watchMyTrades': True,
31
34
  'watchOrders': True,
32
35
  'watchOrderBook': True,
33
- 'watchOHLCV': True, # only for swap markets
34
- 'fetchOHLCVWs': True,
36
+ 'watchOrderBookForSymbols': True,
37
+ 'watchOHLCV': False,
38
+ 'fetchOHLCVWs': False,
35
39
  },
36
40
  'urls': {
37
41
  'api': {
38
42
  'ws': {
39
- 'spot': 'wss://socket.coinex.com/',
40
- 'swap': 'wss://perpetual.coinex.com/',
43
+ 'spot': 'wss://socket.coinex.com/v2/spot/',
44
+ 'swap': 'wss://socket.coinex.com/v2/futures/',
41
45
  },
42
46
  },
43
47
  },
44
48
  'options': {
45
- 'watchOHLCVWarning': True,
49
+ 'ws': {
50
+ 'gunzip': True,
51
+ },
46
52
  'timeframes': {
47
53
  '1m': 60,
48
54
  '3m': 180,
@@ -62,21 +68,32 @@ class coinex(ccxt.async_support.coinex):
62
68
  'watchOrderBook': {
63
69
  'limits': [5, 10, 20, 50],
64
70
  'defaultLimit': 50,
65
- 'aggregations': ['10', '1', '0', '0.1', '0.01'],
71
+ 'aggregations': ['1000', '100', '10', '1', '0', '0.1', '0.01', '0.001', '0.0001', '0.00001', '0.000001', '0.0000001', '0.00000001', '0.000000001', '0.0000000001', '0.00000000001'],
66
72
  'defaultAggregation': '0',
67
73
  },
68
74
  },
69
75
  'streaming': {
70
76
  },
71
77
  'exceptions': {
72
- 'codes': {
73
- '1': BadRequest, # Parameter error
74
- '2': ExchangeError, # Internal error
75
- '3': ExchangeNotAvailable, # Service unavailable
76
- '4': NotSupported, # Method unavailable
77
- '5': RequestTimeout, # Service timeout
78
- '6': AuthenticationError, # Permission denied
78
+ 'exact': {
79
+ '20001': BadRequest, # Invalid argument
80
+ '20002': NotSupported, # Method unavailable
81
+ '21001': AuthenticationError, # Authentication required
82
+ '21002': AuthenticationError, # Incorrect signature
83
+ '23001': RequestTimeout, # Request service timeout
84
+ '23002': RateLimitExceeded, # Requests too frequently
85
+ '24001': ExchangeError, # Internal error
86
+ '24002': ExchangeNotAvailable, # Service unavailable temporarily
87
+ '30001': BadRequest, # Invalid argument
88
+ '30002': NotSupported, # Method unavailable
89
+ '31001': AuthenticationError, # Authentication required
90
+ '31002': AuthenticationError, # Incorrect signature
91
+ '33001': RequestTimeout, # Request service timeout
92
+ '33002': RateLimitExceeded, # Requests too frequently
93
+ '34001': ExchangeError, # Internal error
94
+ '34002': ExchangeNotAvailable, # Service unavailable temporarily
79
95
  },
96
+ 'broad': {},
80
97
  },
81
98
  })
82
99
 
@@ -91,61 +108,66 @@ class coinex(ccxt.async_support.coinex):
91
108
  #
92
109
  # {
93
110
  # "method": "state.update",
94
- # "params": [{
95
- # "BTCUSDT": {
96
- # "last": "31577.89",
97
- # "open": "29318.36",
98
- # "close": "31577.89",
99
- # "high": "32222.19",
100
- # "low": "29317.21",
101
- # "volume": "630.43024965",
102
- # "sell_total": "13.66143951",
103
- # "buy_total": "2.76410939",
104
- # "period": 86400,
105
- # "deal": "19457487.84611409070000000000"
106
- # }
107
- # }]
111
+ # "data": {
112
+ # "state_list": [
113
+ # {
114
+ # "market": "LATUSDT",
115
+ # "last": "0.008157",
116
+ # "open": "0.008286",
117
+ # "close": "0.008157",
118
+ # "high": "0.008390",
119
+ # "low": "0.008106",
120
+ # "volume": "807714.49139758",
121
+ # "volume_sell": "286170.69645599",
122
+ # "volume_buy": "266161.23236408",
123
+ # "value": "6689.21644207",
124
+ # "period": 86400
125
+ # },
126
+ # ]
127
+ # },
128
+ # "id": null
108
129
  # }
109
130
  #
110
131
  # swap
111
132
  #
112
133
  # {
113
134
  # "method": "state.update",
114
- # "params": [{
115
- # "BTCUSDT": {
116
- # "period": 86400,
117
- # "funding_time": 422,
118
- # "position_amount": "285.6246",
119
- # "funding_rate_last": "-0.00097933",
120
- # "funding_rate_next": "0.00022519",
121
- # "funding_rate_predict": "0.00075190",
122
- # "insurance": "17474289.49925859030905338270",
123
- # "last": "31570.08",
124
- # "sign_price": "31568.09",
125
- # "index_price": "31561.85000000",
126
- # "open": "29296.11",
127
- # "close": "31570.08",
128
- # "high": "32463.40",
129
- # "low": "29296.11",
130
- # "volume": "8774.7318",
131
- # "deal": "270675177.827928219109030017258398",
132
- # "sell_total": "19.2230",
133
- # "buy_total": "25.7814"
134
- # }
135
- # }]
135
+ # "data": {
136
+ # "state_list": [
137
+ # {
138
+ # "market": "ETHUSD_SIGNPRICE",
139
+ # "last": "1892.29",
140
+ # "open": "1884.62",
141
+ # "close": "1892.29",
142
+ # "high": "1894.09",
143
+ # "low": "1863.72",
144
+ # "volume": "0",
145
+ # "value": "0",
146
+ # "volume_sell": "0",
147
+ # "volume_buy": "0",
148
+ # "open_interest_size": "0",
149
+ # "insurance_fund_size": "0",
150
+ # "latest_funding_rate": "0",
151
+ # "next_funding_rate": "0",
152
+ # "latest_funding_time": 0,
153
+ # "next_funding_time": 0,
154
+ # "period": 86400
155
+ # },
156
+ # ]
157
+ # ],
158
+ # "id": null
136
159
  # }
137
160
  #
138
161
  defaultType = self.safe_string(self.options, 'defaultType')
139
- params = self.safe_value(message, 'params', [])
140
- rawTickers = self.safe_value(params, 0, {})
141
- keys = list(rawTickers.keys())
162
+ data = self.safe_dict(message, 'data', {})
163
+ rawTickers = self.safe_list(data, 'state_list', [])
142
164
  newTickers = []
143
- for i in range(0, len(keys)):
144
- marketId = keys[i]
145
- rawTicker = rawTickers[marketId]
165
+ for i in range(0, len(rawTickers)):
166
+ entry = rawTickers[i]
167
+ marketId = self.safe_string(entry, 'market')
146
168
  symbol = self.safe_symbol(marketId, None, None, defaultType)
147
169
  market = self.safe_market(marketId, None, None, defaultType)
148
- parsedTicker = self.parse_ws_ticker(rawTicker, market)
170
+ parsedTicker = self.parse_ws_ticker(entry, market)
149
171
  self.tickers[symbol] = parsedTicker
150
172
  newTickers.append(parsedTicker)
151
173
  messageHashes = self.find_message_hashes(client, 'tickers::')
@@ -166,52 +188,53 @@ class coinex(ccxt.async_support.coinex):
166
188
  # spot
167
189
  #
168
190
  # {
169
- # "last": "31577.89",
170
- # "open": "29318.36",
171
- # "close": "31577.89",
172
- # "high": "32222.19",
173
- # "low": "29317.21",
174
- # "volume": "630.43024965",
175
- # "sell_total": "13.66143951",
176
- # "buy_total": "2.76410939",
177
- # "period": 86400,
178
- # "deal": "19457487.84611409070000000000"
191
+ # "market": "LATUSDT",
192
+ # "last": "0.008157",
193
+ # "open": "0.008286",
194
+ # "close": "0.008157",
195
+ # "high": "0.008390",
196
+ # "low": "0.008106",
197
+ # "volume": "807714.49139758",
198
+ # "volume_sell": "286170.69645599",
199
+ # "volume_buy": "266161.23236408",
200
+ # "value": "6689.21644207",
201
+ # "period": 86400
179
202
  # }
180
203
  #
181
204
  # swap
182
205
  #
183
206
  # {
184
- # "period": 86400,
185
- # "funding_time": 422,
186
- # "position_amount": "285.6246",
187
- # "funding_rate_last": "-0.00097933",
188
- # "funding_rate_next": "0.00022519",
189
- # "funding_rate_predict": "0.00075190",
190
- # "insurance": "17474289.49925859030905338270",
191
- # "last": "31570.08",
192
- # "sign_price": "31568.09",
193
- # "index_price": "31561.85000000",
194
- # "open": "29296.11",
195
- # "close": "31570.08",
196
- # "high": "32463.40",
197
- # "low": "29296.11",
198
- # "volume": "8774.7318",
199
- # "deal": "270675177.827928219109030017258398",
200
- # "sell_total": "19.2230",
201
- # "buy_total": "25.7814"
207
+ # "market": "ETHUSD_SIGNPRICE",
208
+ # "last": "1892.29",
209
+ # "open": "1884.62",
210
+ # "close": "1892.29",
211
+ # "high": "1894.09",
212
+ # "low": "1863.72",
213
+ # "volume": "0",
214
+ # "value": "0",
215
+ # "volume_sell": "0",
216
+ # "volume_buy": "0",
217
+ # "open_interest_size": "0",
218
+ # "insurance_fund_size": "0",
219
+ # "latest_funding_rate": "0",
220
+ # "next_funding_rate": "0",
221
+ # "latest_funding_time": 0,
222
+ # "next_funding_time": 0,
223
+ # "period": 86400
202
224
  # }
203
225
  #
204
226
  defaultType = self.safe_string(self.options, 'defaultType')
227
+ marketId = self.safe_string(ticker, 'market')
205
228
  return self.safe_ticker({
206
- 'symbol': self.safe_symbol(None, market, None, defaultType),
229
+ 'symbol': self.safe_symbol(marketId, market, None, defaultType),
207
230
  'timestamp': None,
208
231
  'datetime': None,
209
232
  'high': self.safe_string(ticker, 'high'),
210
233
  'low': self.safe_string(ticker, 'low'),
211
234
  'bid': None,
212
- 'bidVolume': self.safe_string(ticker, 'buy_total'),
235
+ 'bidVolume': self.safe_string(ticker, 'volume_buy'),
213
236
  'ask': None,
214
- 'askVolume': self.safe_string(ticker, 'sell_total'),
237
+ 'askVolume': self.safe_string(ticker, 'volume_sell'),
215
238
  'vwap': None,
216
239
  'open': self.safe_string(ticker, 'open'),
217
240
  'close': self.safe_string(ticker, 'close'),
@@ -221,84 +244,274 @@ class coinex(ccxt.async_support.coinex):
221
244
  'percentage': None,
222
245
  'average': None,
223
246
  'baseVolume': self.safe_string(ticker, 'volume'),
224
- 'quoteVolume': self.safe_string(ticker, 'deal'),
247
+ 'quoteVolume': self.safe_string(ticker, 'value'),
225
248
  'info': ticker,
226
249
  }, market)
227
250
 
228
251
  async def watch_balance(self, params={}) -> Balances:
229
252
  """
230
253
  watch balance and get the amount of funds available for trading or funds locked in orders
254
+ :see: https://docs.coinex.com/api/v2/assets/balance/ws/spot_balance
255
+ :see: https://docs.coinex.com/api/v2/assets/balance/ws/futures_balance
231
256
  :param dict [params]: extra parameters specific to the exchange API endpoint
232
257
  :returns dict: a `balance structure <https://docs.ccxt.com/#/?id=balance-structure>`
233
258
  """
234
259
  await self.load_markets()
235
- await self.authenticate(params)
236
- messageHash = 'balance'
237
260
  type = None
238
- type, params = self.handle_market_type_and_params('watchBalance', None, params)
261
+ type, params = self.handle_market_type_and_params('watchBalance', None, params, 'spot')
262
+ await self.authenticate(type)
239
263
  url = self.urls['api']['ws'][type]
240
264
  currencies = list(self.currencies_by_id.keys())
265
+ if currencies is None:
266
+ currencies = []
267
+ messageHash = 'balances'
268
+ if type == 'spot':
269
+ messageHash += ':spot'
270
+ else:
271
+ messageHash += ':swap'
241
272
  subscribe: dict = {
242
- 'method': 'asset.subscribe',
243
- 'params': currencies,
273
+ 'method': 'balance.subscribe',
274
+ 'params': {'ccy_list': currencies},
244
275
  'id': self.request_id(),
245
276
  }
246
277
  request = self.deep_extend(subscribe, params)
247
278
  return await self.watch(url, messageHash, request, messageHash)
248
279
 
249
280
  def handle_balance(self, client: Client, message):
281
+ #
282
+ # spot
250
283
  #
251
284
  # {
252
- # "method": "asset.update",
253
- # "params": [
254
- # {
255
- # "BTC": {
256
- # "available": "250",
257
- # "frozen": "10",
258
- # }
259
- # }
260
- # ],
285
+ # "method": "balance.update",
286
+ # "data": {
287
+ # "balance_list": [
288
+ # {
289
+ # "margin_market": "BTCUSDT",
290
+ # "ccy": "BTC",
291
+ # "available": "44.62207740",
292
+ # "frozen": "0.00000000",
293
+ # "updated_at": 1689152421692
294
+ # },
295
+ # ]
296
+ # },
261
297
  # "id": null
262
298
  # }
263
299
  #
264
- params = self.safe_value(message, 'params', [])
265
- first = self.safe_value(params, 0, {})
266
- self.balance['info'] = first
267
- currencies = list(first.keys())
268
- for i in range(0, len(currencies)):
269
- currencyId = currencies[i]
270
- code = self.safe_currency_code(currencyId)
271
- available = self.safe_string(first[currencyId], 'available')
272
- frozen = self.safe_string(first[currencyId], 'frozen')
273
- account = self.account()
274
- account['free'] = available
275
- account['used'] = frozen
300
+ # swap
301
+ #
302
+ # {
303
+ # "method": "balance.update",
304
+ # "data": {
305
+ # "balance_list": [
306
+ # {
307
+ # "ccy": "USDT",
308
+ # "available": "97.92470982756335000001",
309
+ # "frozen": "0.00000000000000000000",
310
+ # "margin": "0.61442700000000000000",
311
+ # "transferrable": "97.92470982756335000001",
312
+ # "unrealized_pnl": "-0.00807000000000000000",
313
+ # "equity": "97.92470982756335000001"
314
+ # },
315
+ # ]
316
+ # },
317
+ # "id": null
318
+ # }
319
+ #
320
+ if self.balance is None:
321
+ self.balance = {}
322
+ data = self.safe_dict(message, 'data', {})
323
+ balances = self.safe_list(data, 'balance_list', [])
324
+ firstEntry = balances[0]
325
+ updated = self.safe_integer(firstEntry, 'updated_at')
326
+ unrealizedPnl = self.safe_string(firstEntry, 'unrealized_pnl')
327
+ isSpot = (updated is not None)
328
+ isSwap = (unrealizedPnl is not None)
329
+ info = None
330
+ account = None
331
+ rawBalances = []
332
+ if isSpot:
333
+ account = 'spot'
334
+ for i in range(0, len(balances)):
335
+ rawBalances = self.array_concat(rawBalances, balances)
336
+ info = rawBalances
337
+ if isSwap:
338
+ account = 'swap'
339
+ for i in range(0, len(balances)):
340
+ rawBalances = self.array_concat(rawBalances, balances)
341
+ info = rawBalances
342
+ for i in range(0, len(rawBalances)):
343
+ entry = rawBalances[i]
344
+ self.parse_ws_balance(entry, account)
345
+ messageHash = None
346
+ if account is not None:
347
+ if self.safe_value(self.balance, account) is None:
348
+ self.balance[account] = {}
349
+ self.balance[account]['info'] = info
350
+ self.balance[account] = self.safe_balance(self.balance[account])
351
+ messageHash = 'balances:' + account
352
+ client.resolve(self.balance[account], messageHash)
353
+
354
+ def parse_ws_balance(self, balance, accountType=None):
355
+ #
356
+ # spot
357
+ #
358
+ # {
359
+ # "margin_market": "BTCUSDT",
360
+ # "ccy": "BTC",
361
+ # "available": "44.62207740",
362
+ # "frozen": "0.00000000",
363
+ # "updated_at": 1689152421692
364
+ # }
365
+ #
366
+ # swap
367
+ #
368
+ # {
369
+ # "ccy": "USDT",
370
+ # "available": "97.92470982756335000001",
371
+ # "frozen": "0.00000000000000000000",
372
+ # "margin": "0.61442700000000000000",
373
+ # "transferrable": "97.92470982756335000001",
374
+ # "unrealized_pnl": "-0.00807000000000000000",
375
+ # "equity": "97.92470982756335000001"
376
+ # }
377
+ #
378
+ account = self.account()
379
+ currencyId = self.safe_string(balance, 'ccy')
380
+ code = self.safe_currency_code(currencyId)
381
+ account['free'] = self.safe_string(balance, 'available')
382
+ account['used'] = self.safe_string(balance, 'frozen')
383
+ if accountType is not None:
384
+ if self.safe_value(self.balance, accountType) is None:
385
+ self.balance[accountType] = {}
386
+ self.balance[accountType][code] = account
387
+ else:
276
388
  self.balance[code] = account
277
- self.balance = self.safe_balance(self.balance)
278
- messageHash = 'balance'
279
- client.resolve(self.balance, messageHash)
389
+
390
+ async def watch_my_trades(self, symbol: Str = None, since: Int = None, limit: Int = None, params={}) -> List[Trade]:
391
+ """
392
+ watches information on multiple trades made by the user
393
+ :see: https://docs.coinex.com/api/v2/spot/deal/ws/user-deals
394
+ :see: https://docs.coinex.com/api/v2/futures/deal/ws/user-deals
395
+ :param str [symbol]: unified symbol of the market the trades were made in
396
+ :param int [since]: the earliest time in ms to watch trades
397
+ :param int [limit]: the maximum number of trade structures to retrieve
398
+ :param dict [params]: extra parameters specific to the exchange API endpoint
399
+ :returns dict[]: a list of `trade structures <https://docs.ccxt.com/#/?id=trade-structure>`
400
+ """
401
+ await self.load_markets()
402
+ market = None
403
+ if symbol is not None:
404
+ market = self.market(symbol)
405
+ symbol = market['symbol']
406
+ type = None
407
+ type, params = self.handle_market_type_and_params('watchMyTrades', market, params, 'spot')
408
+ await self.authenticate(type)
409
+ url = self.urls['api']['ws'][type]
410
+ subscribedSymbols = []
411
+ messageHash = 'myTrades'
412
+ if market is not None:
413
+ messageHash += ':' + symbol
414
+ subscribedSymbols.append(market['id'])
415
+ else:
416
+ if type == 'spot':
417
+ messageHash += ':spot'
418
+ else:
419
+ messageHash += ':swap'
420
+ message: dict = {
421
+ 'method': 'user_deals.subscribe',
422
+ 'params': {'market_list': subscribedSymbols},
423
+ 'id': self.request_id(),
424
+ }
425
+ request = self.deep_extend(message, params)
426
+ trades = await self.watch(url, messageHash, request, messageHash)
427
+ if self.newUpdates:
428
+ limit = trades.getLimit(symbol, limit)
429
+ return self.filter_by_symbol_since_limit(trades, symbol, since, limit, True)
430
+
431
+ def handle_my_trades(self, client: Client, message):
432
+ #
433
+ # {
434
+ # "method": "user_deals.update",
435
+ # "data": {
436
+ # "deal_id": 3514376759,
437
+ # "created_at": 1689152421692,
438
+ # "market": "BTCUSDT",
439
+ # "side": "buy",
440
+ # "order_id": 8678890,
441
+ # "margin_market": "BTCUSDT",
442
+ # "price": "30718.42",
443
+ # "amount": "0.00000325",
444
+ # "role": "taker",
445
+ # "fee": "0.0299",
446
+ # "fee_ccy": "USDT"
447
+ # },
448
+ # "id": null
449
+ # }
450
+ #
451
+ data = self.safe_dict(message, 'data', {})
452
+ marketId = self.safe_string(data, 'market')
453
+ isSpot = client.url.find('spot') > -1
454
+ defaultType = 'spot' if isSpot else 'swap'
455
+ market = self.safe_market(marketId, None, None, defaultType)
456
+ symbol = market['symbol']
457
+ messageHash = 'myTrades:' + symbol
458
+ messageWithType = 'myTrades:' + market['type']
459
+ stored = self.safe_value(self.trades, symbol)
460
+ if stored is None:
461
+ limit = self.safe_integer(self.options, 'tradesLimit', 1000)
462
+ stored = ArrayCache(limit)
463
+ self.trades[symbol] = stored
464
+ parsed = self.parse_ws_trade(data, market)
465
+ stored.append(parsed)
466
+ self.trades[symbol] = stored
467
+ client.resolve(self.trades[symbol], messageWithType)
468
+ client.resolve(self.trades[symbol], messageHash)
280
469
 
281
470
  def handle_trades(self, client: Client, message):
471
+ #
472
+ # spot
282
473
  #
283
474
  # {
284
475
  # "method": "deals.update",
285
- # "params": [
286
- # "BTCUSD",
287
- # [{
288
- # "type": "sell",
289
- # "time": 1496458040.059284,
290
- # "price ": "46444.74",
291
- # "id": 29433,
292
- # "amount": "0.00120000"
293
- # }]
294
- # ],
476
+ # "data": {
477
+ # "market": "BTCUSDT",
478
+ # "deal_list": [
479
+ # {
480
+ # "deal_id": 3514376759,
481
+ # "created_at": 1689152421692,
482
+ # "side": "buy",
483
+ # "price": "30718.42",
484
+ # "amount": "0.00000325"
485
+ # },
486
+ # ]
487
+ # },
295
488
  # "id": null
296
489
  # }
297
490
  #
298
- params = self.safe_value(message, 'params', [])
299
- marketId = self.safe_string(params, 0)
300
- trades = self.safe_value(params, 1, [])
301
- defaultType = self.safe_string(self.options, 'defaultType')
491
+ # swap
492
+ #
493
+ # {
494
+ # "method": "deals.update",
495
+ # "data": {
496
+ # "market": "BTCUSDT",
497
+ # "deal_list": [
498
+ # {
499
+ # "deal_id": 3514376759,
500
+ # "created_at": 1689152421692,
501
+ # "side": "buy",
502
+ # "price": "30718.42",
503
+ # "amount": "0.00000325"
504
+ # },
505
+ # ]
506
+ # },
507
+ # "id": null
508
+ # }
509
+ #
510
+ data = self.safe_dict(message, 'data', {})
511
+ trades = self.safe_list(data, 'deal_list', [])
512
+ marketId = self.safe_string(data, 'market')
513
+ isSpot = client.url.find('spot') > -1
514
+ defaultType = 'spot' if isSpot else 'swap'
302
515
  market = self.safe_market(marketId, None, None, defaultType)
303
516
  symbol = market['symbol']
304
517
  messageHash = 'trades:' + symbol
@@ -315,289 +528,239 @@ class coinex(ccxt.async_support.coinex):
315
528
  client.resolve(self.trades[symbol], messageHash)
316
529
 
317
530
  def parse_ws_trade(self, trade, market=None):
531
+ #
532
+ # spot watchTrades
318
533
  #
319
534
  # {
320
- # "type": "sell",
321
- # "time": 1496458040.059284,
322
- # "price ": "46444.74",
323
- # "id": 29433,
324
- # "amount": "0.00120000"
535
+ # "deal_id": 3514376759,
536
+ # "created_at": 1689152421692,
537
+ # "side": "buy",
538
+ # "price": "30718.42",
539
+ # "amount": "0.00000325"
325
540
  # }
326
541
  #
327
- timestamp = self.safe_timestamp(trade, 'time')
328
- defaultType = self.safe_string(self.options, 'defaultType')
542
+ # swap watchTrades
543
+ #
544
+ # {
545
+ # "deal_id": 3514376759,
546
+ # "created_at": 1689152421692,
547
+ # "side": "buy",
548
+ # "price": "30718.42",
549
+ # "amount": "0.00000325"
550
+ # }
551
+ #
552
+ # spot and swap watchMyTrades
553
+ #
554
+ # {
555
+ # "deal_id": 3514376759,
556
+ # "created_at": 1689152421692,
557
+ # "market": "BTCUSDT",
558
+ # "side": "buy",
559
+ # "order_id": 8678890,
560
+ # "margin_market": "BTCUSDT",
561
+ # "price": "30718.42",
562
+ # "amount": "0.00000325",
563
+ # "role": "taker",
564
+ # "fee": "0.0299",
565
+ # "fee_ccy": "USDT"
566
+ # }
567
+ #
568
+ timestamp = self.safe_integer(trade, 'created_at')
569
+ isSpot = ('margin_market' in trade)
570
+ defaultType = 'spot' if isSpot else 'swap'
571
+ marketId = self.safe_string(trade, 'market')
572
+ market = self.safe_market(marketId, market, None, defaultType)
573
+ fee: dict = {}
574
+ feeCost = self.omit_zero(self.safe_string(trade, 'fee'))
575
+ if feeCost is not None:
576
+ feeCurrencyId = self.safe_string(trade, 'fee_ccy', market['quote'])
577
+ fee = {
578
+ 'currency': self.safe_currency_code(feeCurrencyId),
579
+ 'cost': feeCost,
580
+ }
329
581
  return self.safe_trade({
330
- 'id': self.safe_string(trade, 'id'),
582
+ 'id': self.safe_string(trade, 'deal_id'),
331
583
  'info': trade,
332
584
  'timestamp': timestamp,
333
585
  'datetime': self.iso8601(timestamp),
334
- 'symbol': self.safe_symbol(None, market, None, defaultType),
335
- 'order': None,
586
+ 'symbol': self.safe_symbol(marketId, market, None, defaultType),
587
+ 'order': self.safe_string(trade, 'order_id'),
336
588
  'type': None,
337
- 'side': self.safe_string(trade, 'type'),
338
- 'takerOrMaker': None,
589
+ 'side': self.safe_string(trade, 'side'),
590
+ 'takerOrMaker': self.safe_string(trade, 'role'),
339
591
  'price': self.safe_string(trade, 'price'),
340
592
  'amount': self.safe_string(trade, 'amount'),
341
593
  'cost': None,
342
- 'fee': None,
594
+ 'fee': fee,
343
595
  }, market)
344
596
 
345
- def handle_ohlcv(self, client: Client, message):
346
- #
347
- # spot
348
- # {
349
- # "error": null,
350
- # "result": [
351
- # [
352
- # 1673846940,
353
- # "21148.74",
354
- # "21148.38",
355
- # "21148.75",
356
- # "21138.66",
357
- # "1.57060173",
358
- # "33214.9138778914"
359
- # ],
360
- # ]
361
- # "id": 1,
362
- # }
363
- # swap
364
- # {
365
- # "method": "kline.update",
366
- # "params": [
367
- # [
368
- # 1654019640, # timestamp
369
- # "32061.99", # open
370
- # "32061.28", # close
371
- # "32061.99", # high
372
- # "32061.28", # low
373
- # "0.1285", # amount base
374
- # "4119.943736" # amount quote
375
- # ]
376
- # ],
377
- # "id": null
378
- # }
379
- #
380
- candles = self.safe_value_2(message, 'params', 'result', [])
381
- messageHash = 'ohlcv'
382
- id = self.safe_string(message, 'id')
383
- ohlcvs = self.parse_ohlcvs(candles)
384
- if id is not None:
385
- # spot subscription response
386
- client.resolve(ohlcvs, messageHash)
387
- return
388
- keys = list(self.ohlcvs.keys())
389
- keysLength = len(keys)
390
- if keysLength == 0:
391
- self.ohlcvs['unknown'] = {}
392
- limit = self.safe_integer(self.options, 'OHLCVLimit', 1000)
393
- stored = ArrayCacheByTimestamp(limit)
394
- self.ohlcvs['unknown']['unknown'] = stored
395
- ohlcv = self.ohlcvs['unknown']['unknown']
396
- for i in range(0, len(ohlcvs)):
397
- candle = ohlcvs[i]
398
- ohlcv.append(candle)
399
- client.resolve(ohlcv, messageHash)
400
-
401
597
  async def watch_ticker(self, symbol: str, params={}) -> Ticker:
402
598
  """
403
- :see: https://viabtc.github.io/coinex_api_en_doc/spot/#docsspot004_websocket007_state_subscribe
404
599
  watches a price ticker, a statistical calculation with the information calculated over the past 24 hours for a specific market
600
+ :see: https://docs.coinex.com/api/v2/spot/market/ws/market
601
+ :see: https://docs.coinex.com/api/v2/futures/market/ws/market-state
405
602
  :param str symbol: unified symbol of the market to fetch the ticker for
406
603
  :param dict [params]: extra parameters specific to the exchange API endpoint
407
604
  :returns dict: a `ticker structure <https://docs.ccxt.com/#/?id=ticker-structure>`
408
605
  """
606
+ await self.load_markets()
607
+ market = self.market(symbol)
409
608
  tickers = await self.watch_tickers([symbol], params)
410
- return self.safe_value(tickers, symbol)
609
+ return tickers[market['symbol']]
411
610
 
412
611
  async def watch_tickers(self, symbols: Strings = None, params={}) -> Tickers:
413
612
  """
414
- :see: https://viabtc.github.io/coinex_api_en_doc/spot/#docsspot004_websocket007_state_subscribe
415
613
  watches a price ticker, a statistical calculation with the information calculated over the past 24 hours for all markets of a specific list
614
+ :see: https://docs.coinex.com/api/v2/spot/market/ws/market
615
+ :see: https://docs.coinex.com/api/v2/futures/market/ws/market-state
416
616
  :param str[] symbols: unified symbol of the market to fetch the ticker for
417
617
  :param dict [params]: extra parameters specific to the exchange API endpoint
418
618
  :returns dict: a dictionary of `ticker structures <https://docs.ccxt.com/#/?id=ticker-structure>`
419
619
  """
420
620
  await self.load_markets()
421
- symbols = self.market_symbols(symbols)
621
+ marketIds = self.market_ids(symbols)
622
+ market = None
623
+ messageHashes = []
624
+ symbolsDefined = (symbols is not None)
625
+ if symbolsDefined:
626
+ for i in range(0, len(symbols)):
627
+ symbol = symbols[i]
628
+ market = self.market(symbol)
629
+ messageHashes.append('tickers::' + market['symbol'])
630
+ else:
631
+ messageHashes.append('tickers')
422
632
  type = None
423
- type, params = self.handle_market_type_and_params('watchTickers', None, params)
633
+ type, params = self.handle_market_type_and_params('watchTickers', market, params)
424
634
  url = self.urls['api']['ws'][type]
425
- messageHash = 'tickers'
426
- if symbols is not None:
427
- messageHash = 'tickers::' + ','.join(symbols)
635
+ subscriptionHashes = ['all@ticker']
428
636
  subscribe: dict = {
429
637
  'method': 'state.subscribe',
638
+ 'params': {'market_list': marketIds},
430
639
  'id': self.request_id(),
431
- 'params': [],
432
640
  }
433
- request = self.deep_extend(subscribe, params)
434
- newTickers = await self.watch(url, messageHash, request, messageHash)
641
+ result = await self.watch_multiple(url, messageHashes, self.deep_extend(subscribe, params), subscriptionHashes)
435
642
  if self.newUpdates:
436
- return newTickers
643
+ return result
437
644
  return self.filter_by_array(self.tickers, 'symbol', symbols)
438
645
 
439
646
  async def watch_trades(self, symbol: str, since: Int = None, limit: Int = None, params={}) -> List[Trade]:
440
647
  """
441
- :see: https://viabtc.github.io/coinex_api_en_doc/spot/#docsspot004_websocket012_deal_subcribe
442
- :see: https://viabtc.github.io/coinex_api_en_doc/futures/#docsfutures002_websocket019_deal_subcribe
443
648
  get the list of most recent trades for a particular symbol
649
+ :see: https://docs.coinex.com/api/v2/spot/market/ws/market-deals
650
+ :see: https://docs.coinex.com/api/v2/futures/market/ws/market-deals
444
651
  :param str symbol: unified symbol of the market to fetch trades for
445
652
  :param int [since]: timestamp in ms of the earliest trade to fetch
446
653
  :param int [limit]: the maximum amount of trades to fetch
447
654
  :param dict [params]: extra parameters specific to the exchange API endpoint
448
655
  :returns dict[]: a list of `trade structures <https://docs.ccxt.com/#/?id=public-trades>`
449
656
  """
657
+ params['callerMethodName'] = 'watchTrades'
658
+ return await self.watch_trades_for_symbols([symbol], since, limit, params)
659
+
660
+ async def watch_trades_for_symbols(self, symbols: List[str], since: Int = None, limit: Int = None, params={}) -> List[Trade]:
661
+ """
662
+ watch the most recent trades for a list of symbols
663
+ :see: https://docs.coinex.com/api/v2/spot/market/ws/market-deals
664
+ :see: https://docs.coinex.com/api/v2/futures/market/ws/market-deals
665
+ :param str[] symbols: unified symbols of the markets to fetch trades for
666
+ :param int [since]: timestamp in ms of the earliest trade to fetch
667
+ :param int [limit]: the maximum amount of trades to fetch
668
+ :param dict [params]: extra parameters specific to the exchange API endpoint
669
+ :returns dict[]: a list of `trade structures <https://docs.ccxt.com/#/?id=public-trades>`
670
+ """
450
671
  await self.load_markets()
451
- market = self.market(symbol)
672
+ subscribedSymbols = []
673
+ messageHashes = []
674
+ market = None
675
+ callerMethodName = None
676
+ callerMethodName, params = self.handle_param_string(params, 'callerMethodName', 'watchTradesForSymbols')
677
+ symbolsDefined = (symbols is not None)
678
+ if symbolsDefined:
679
+ for i in range(0, len(symbols)):
680
+ symbol = symbols[i]
681
+ market = self.market(symbol)
682
+ subscribedSymbols.append(market['id'])
683
+ messageHashes.append('trades:' + market['symbol'])
684
+ else:
685
+ messageHashes.append('trades')
452
686
  type = None
453
- type, params = self.handle_market_type_and_params('watchTrades', market, params)
687
+ type, params = self.handle_market_type_and_params(callerMethodName, market, params)
454
688
  url = self.urls['api']['ws'][type]
455
- messageHash = 'trades:' + symbol
456
- subscriptionHash = 'trades'
457
- subscribedSymbols = self.safe_value(self.options, 'watchTradesSubscriptions', [])
458
- subscribedSymbols.append(market['id'])
459
- message: dict = {
689
+ subscriptionHashes = ['trades']
690
+ subscribe: dict = {
460
691
  'method': 'deals.subscribe',
461
- 'params': subscribedSymbols,
692
+ 'params': {'market_list': subscribedSymbols},
462
693
  'id': self.request_id(),
463
694
  }
464
- self.options['watchTradesSubscriptions'] = subscribedSymbols
465
- request = self.deep_extend(message, params)
466
- trades = await self.watch(url, messageHash, request, subscriptionHash)
695
+ trades = await self.watch_multiple(url, messageHashes, self.deep_extend(subscribe, params), subscriptionHashes)
696
+ if self.newUpdates:
697
+ return trades
467
698
  return self.filter_by_since_limit(trades, since, limit, 'timestamp', True)
468
699
 
469
- async def watch_order_book(self, symbol: str, limit: Int = None, params={}) -> OrderBook:
700
+ async def watch_order_book_for_symbols(self, symbols: List[str], limit: Int = None, params={}) -> OrderBook:
470
701
  """
471
- :see: https://viabtc.github.io/coinex_api_en_doc/spot/#docsspot004_websocket017_depth_subscribe_multi
472
- :see: https://viabtc.github.io/coinex_api_en_doc/futures/#docsfutures002_websocket011_depth_subscribe_multi
473
702
  watches information on open orders with bid(buy) and ask(sell) prices, volumes and other data
474
- :param str symbol: unified symbol of the market to fetch the order book for
703
+ :see: https://docs.coinex.com/api/v2/spot/market/ws/market-depth
704
+ :see: https://docs.coinex.com/api/v2/futures/market/ws/market-depth
705
+ :param str[] symbols: unified array of symbols
475
706
  :param int [limit]: the maximum amount of order book entries to return
476
707
  :param dict [params]: extra parameters specific to the exchange API endpoint
477
708
  :returns dict: A dictionary of `order book structures <https://docs.ccxt.com/#/?id=order-book-structure>` indexed by market symbols
478
709
  """
479
710
  await self.load_markets()
480
- market = self.market(symbol)
481
- symbol = market['symbol']
711
+ watchOrderBookSubscriptions: dict = {}
712
+ messageHashes = []
713
+ market = None
482
714
  type = None
483
- type, params = self.handle_market_type_and_params('watchOrderBook', market, params)
484
- url = self.urls['api']['ws'][type]
485
- name = 'orderbook'
486
- messageHash = name + ':' + symbol
487
- options = self.safe_value(self.options, 'watchOrderBook', {})
488
- limits = self.safe_value(options, 'limits', [])
715
+ callerMethodName = None
716
+ callerMethodName, params = self.handle_param_string(params, 'callerMethodName', 'watchOrderBookForSymbols')
717
+ type, params = self.handle_market_type_and_params(callerMethodName, None, params)
718
+ options = self.safe_dict(self.options, 'watchOrderBook', {})
719
+ limits = self.safe_list(options, 'limits', [])
489
720
  if limit is None:
490
- limit = self.safe_value(options, 'defaultLimit', 50)
721
+ limit = self.safe_integer(options, 'defaultLimit', 50)
491
722
  if not self.in_array(limit, limits):
492
- raise NotSupported(self.id + ' watchOrderBook() limit must be one of ' + ', '.join(limits))
723
+ raise NotSupported(self.id + ' watchOrderBookForSymbols() limit must be one of ' + ', '.join(limits))
493
724
  defaultAggregation = self.safe_string(options, 'defaultAggregation', '0')
494
- aggregations = self.safe_value(options, 'aggregations', [])
725
+ aggregations = self.safe_list(options, 'aggregations', [])
495
726
  aggregation = self.safe_string(params, 'aggregation', defaultAggregation)
496
727
  if not self.in_array(aggregation, aggregations):
497
- raise NotSupported(self.id + ' watchOrderBook() aggregation must be one of ' + ', '.join(aggregations))
728
+ raise NotSupported(self.id + ' watchOrderBookForSymbols() aggregation must be one of ' + ', '.join(aggregations))
498
729
  params = self.omit(params, 'aggregation')
499
- watchOrderBookSubscriptions = self.safe_value(self.options, 'watchOrderBookSubscriptions', {})
500
- watchOrderBookSubscriptions[symbol] = [market['id'], limit, aggregation, True]
730
+ symbolsDefined = (symbols is not None)
731
+ if symbolsDefined:
732
+ for i in range(0, len(symbols)):
733
+ symbol = symbols[i]
734
+ market = self.market(symbol)
735
+ messageHashes.append('orderbook:' + market['symbol'])
736
+ watchOrderBookSubscriptions[symbol] = [market['id'], limit, aggregation, True]
737
+ else:
738
+ messageHashes.append('orderbook')
739
+ marketList = list(watchOrderBookSubscriptions.values())
501
740
  subscribe: dict = {
502
- 'method': 'depth.subscribe_multi',
741
+ 'method': 'depth.subscribe',
742
+ 'params': {'market_list': marketList},
503
743
  'id': self.request_id(),
504
- 'params': list(watchOrderBookSubscriptions.values()),
505
744
  }
506
- self.options['watchOrderBookSubscriptions'] = watchOrderBookSubscriptions
507
- subscriptionHash = self.hash(self.encode(self.json(watchOrderBookSubscriptions)), 'sha256')
508
- request = self.deep_extend(subscribe, params)
509
- orderbook = await self.watch(url, messageHash, request, subscriptionHash, request)
510
- return orderbook.limit()
511
-
512
- async def watch_ohlcv(self, symbol: str, timeframe='1m', since: Int = None, limit: Int = None, params={}) -> List[list]:
513
- """
514
- :see: https://viabtc.github.io/coinex_api_en_doc/futures/#docsfutures002_websocket023_kline_subscribe
515
- watches historical candlestick data containing the open, high, low, and close price, and the volume of a market
516
- :param str symbol: unified symbol of the market to fetch OHLCV data for
517
- :param str timeframe: the length of time each candle represents
518
- :param int [since]: timestamp in ms of the earliest candle to fetch
519
- :param int [limit]: the maximum amount of candles to fetch
520
- :param dict [params]: extra parameters specific to the exchange API endpoint
521
- :returns int[][]: A list of candles ordered, open, high, low, close, volume
522
- """
523
- await self.load_markets()
524
- market = self.market(symbol)
525
- symbol = market['symbol']
526
- type = None
527
- type, params = self.handle_market_type_and_params('watchOHLCV', market, params)
528
- if type != 'swap':
529
- raise NotSupported(self.id + ' watchOHLCV() is only supported for swap markets. Try using fetchOHLCV() instead')
745
+ subscriptionHashes = self.hash(self.encode(self.json(watchOrderBookSubscriptions)), 'sha256')
530
746
  url = self.urls['api']['ws'][type]
531
- messageHash = 'ohlcv'
532
- watchOHLCVWarning = self.safe_bool(self.options, 'watchOHLCVWarning', True)
533
- client = self.safe_value(self.clients, url, {})
534
- clientSub = self.safe_value(client, 'subscriptions', {})
535
- existingSubscription = self.safe_value(clientSub, messageHash)
536
- subSymbol = self.safe_string(existingSubscription, 'symbol')
537
- subTimeframe = self.safe_string(existingSubscription, 'timeframe')
538
- # due to nature of coinex response can only watch one symbol at a time
539
- if watchOHLCVWarning and existingSubscription is not None and (subSymbol != symbol or subTimeframe != timeframe):
540
- raise ExchangeError(self.id + ' watchOHLCV() can only watch one symbol and timeframe at a time. To supress self warning set watchOHLCVWarning to False in options')
541
- timeframes = self.safe_value(self.options, 'timeframes', {})
542
- subscribe: dict = {
543
- 'method': 'kline.subscribe',
544
- 'id': self.request_id(),
545
- 'params': [
546
- market['id'],
547
- self.safe_integer(timeframes, timeframe),
548
- ],
549
- }
550
- subscription: dict = {
551
- 'symbol': symbol,
552
- 'timeframe': timeframe,
553
- }
554
- request = self.deep_extend(subscribe, params)
555
- ohlcvs = await self.watch(url, messageHash, request, messageHash, subscription)
747
+ orderbooks = await self.watch_multiple(url, messageHashes, self.deep_extend(subscribe, params), subscriptionHashes)
556
748
  if self.newUpdates:
557
- limit = ohlcvs.getLimit(symbol, limit)
558
- return self.filter_by_since_limit(ohlcvs, since, limit, 0)
749
+ return orderbooks
750
+ return orderbooks.limit()
559
751
 
560
- async def fetch_ohlcv_ws(self, symbol: str, timeframe='1m', since: Int = None, limit: Int = None, params={}) -> List[list]:
752
+ async def watch_order_book(self, symbol: str, limit: Int = None, params={}) -> OrderBook:
561
753
  """
562
- :see: https://viabtc.github.io/coinex_api_en_doc/spot/#docsspot004_websocket005_kline_query
563
- query historical candlestick data containing the open, high, low, and close price, and the volume of a market
564
- :param str symbol: unified symbol of the market to query OHLCV data for
565
- :param str timeframe: the length of time each candle represents
566
- :param int|None since: timestamp in ms of the earliest candle to fetch
567
- :param int|None limit: the maximum amount of candles to fetch
568
- :param dict params: extra parameters specific to the exchange API endpoint
569
- :param int|None params['end']: the end time for spot markets, self.seconds() is set
570
- :returns int[][]: A list of candles ordered, open, high, low, close, volume
754
+ watches information on open orders with bid(buy) and ask(sell) prices, volumes and other data
755
+ :see: https://docs.coinex.com/api/v2/spot/market/ws/market-depth
756
+ :see: https://docs.coinex.com/api/v2/futures/market/ws/market-depth
757
+ :param str symbol: unified symbol of the market to fetch the order book for
758
+ :param int [limit]: the maximum amount of order book entries to return
759
+ :param dict [params]: extra parameters specific to the exchange API endpoint
760
+ :returns dict: A dictionary of `order book structures <https://docs.ccxt.com/#/?id=order-book-structure>` indexed by market symbols
571
761
  """
572
- await self.load_markets()
573
- market = self.market(symbol)
574
- type, query = self.handle_market_type_and_params('fetchOHLCV', market, params)
575
- url = self.urls['api']['ws'][type]
576
- symbol = market['symbol']
577
- messageHash = 'ohlcv'
578
- timeframes = self.safe_value(self.options, 'timeframes', {})
579
- timeframe = self.safe_string(timeframes, timeframe, timeframe)
580
- if since is None:
581
- since = 1640995200 # January 1, 2022
582
- id = self.request_id()
583
- subscribe: dict = {
584
- 'method': 'kline.query',
585
- 'params': [
586
- market['id'],
587
- self.parse_to_int(since / 1000),
588
- self.safe_integer(params, 'end', self.seconds()),
589
- self.parse_to_int(timeframe),
590
- ],
591
- 'id': id,
592
- }
593
- subscription: dict = {
594
- 'id': id,
595
- 'future': messageHash,
596
- }
597
- subscriptionHash = id
598
- request = self.deep_extend(subscribe, query)
599
- ohlcvs = await self.watch(url, messageHash, request, subscriptionHash, subscription)
600
- return self.filter_by_since_limit(ohlcvs, since, limit, 0)
762
+ params['callerMethodName'] = 'watchOrderBook'
763
+ return await self.watch_order_book_for_symbols([symbol], limit, params)
601
764
 
602
765
  def handle_delta(self, bookside, delta):
603
766
  bidAsk = self.parse_bid_ask(delta, 0, 1)
@@ -611,49 +774,51 @@ class coinex(ccxt.async_support.coinex):
611
774
  #
612
775
  # {
613
776
  # "method": "depth.update",
614
- # "params": [
615
- # False,
616
- # {
777
+ # "data": {
778
+ # "market": "BTCUSDT",
779
+ # "is_full": True,
780
+ # "depth": {
617
781
  # "asks": [
618
- # ["46350.52", "1.07871851"],
619
- # ...
782
+ # [
783
+ # "30740.00",
784
+ # "0.31763545"
785
+ # ],
620
786
  # ],
621
787
  # "bids": [
622
- # ["46349.61", "0.04000000"],
623
- # ...
788
+ # [
789
+ # "30736.00",
790
+ # "0.04857373"
791
+ # ],
624
792
  # ],
625
- # "last": "46349.93",
626
- # "time": 1639987469166,
627
- # "checksum": 1533284725
628
- # },
629
- # "BTCUSDT"
630
- # ],
793
+ # "last": "30746.28",
794
+ # "updated_at": 1689152421692,
795
+ # "checksum": 2578768879
796
+ # }
797
+ # },
631
798
  # "id": null
632
799
  # }
633
800
  #
634
- isSwap = client.url.find('perpetual') >= 0
635
- marketType = 'swap' if isSwap else 'spot'
636
- params = self.safe_value(message, 'params', [])
637
- fullOrderBook = self.safe_value(params, 0)
638
- orderbook = self.safe_value(params, 1)
639
- marketId = self.safe_string(params, 2)
640
- market = self.safe_market(marketId, None, None, marketType)
801
+ defaultType = self.safe_string(self.options, 'defaultType')
802
+ data = self.safe_dict(message, 'data', {})
803
+ depth = self.safe_dict(data, 'depth', {})
804
+ marketId = self.safe_string(data, 'market')
805
+ market = self.safe_market(marketId, None, None, defaultType)
641
806
  symbol = market['symbol']
642
807
  name = 'orderbook'
643
808
  messageHash = name + ':' + symbol
644
- timestamp = self.safe_integer(orderbook, 'time')
809
+ timestamp = self.safe_integer(depth, 'updated_at')
645
810
  currentOrderBook = self.safe_value(self.orderbooks, symbol)
811
+ fullOrderBook = self.safe_bool(data, 'is_full', False)
646
812
  if fullOrderBook:
647
- snapshot = self.parse_order_book(orderbook, symbol, timestamp)
813
+ snapshot = self.parse_order_book(depth, symbol, timestamp)
648
814
  if currentOrderBook is None:
649
- orderbook = self.order_book(snapshot)
650
- self.orderbooks[symbol] = orderbook
815
+ self.orderbooks[symbol] = self.order_book(snapshot)
651
816
  else:
652
817
  orderbook = self.orderbooks[symbol]
653
818
  orderbook.reset(snapshot)
654
819
  else:
655
- asks = self.safe_value(orderbook, 'asks', [])
656
- bids = self.safe_value(orderbook, 'bids', [])
820
+ asks = self.safe_list(depth, 'asks', [])
821
+ bids = self.safe_list(depth, 'bids', [])
657
822
  self.handle_deltas(currentOrderBook['asks'], asks)
658
823
  self.handle_deltas(currentOrderBook['bids'], bids)
659
824
  currentOrderBook['nonce'] = timestamp
@@ -664,24 +829,50 @@ class coinex(ccxt.async_support.coinex):
664
829
  client.resolve(self.orderbooks[symbol], messageHash)
665
830
 
666
831
  async def watch_orders(self, symbol: Str = None, since: Int = None, limit: Int = None, params={}) -> List[Order]:
832
+ """
833
+ watches information on multiple orders made by the user
834
+ :see: https://docs.coinex.com/api/v2/spot/order/ws/user-order
835
+ :see: https://docs.coinex.com/api/v2/futures/order/ws/user-order
836
+ :param str symbol: unified market symbol of the market orders were made in
837
+ :param int [since]: the earliest time in ms to fetch orders for
838
+ :param int [limit]: the maximum number of order structures to retrieve
839
+ :param dict [params]: extra parameters specific to the exchange API endpoint
840
+ :param bool [params.trigger]: if the orders to watch are trigger orders or not
841
+ :returns dict[]: a list of `order structures <https://docs.ccxt.com/#/?id=order-structure>`
842
+ """
667
843
  await self.load_markets()
668
- await self.authenticate(params)
844
+ stop = self.safe_bool_2(params, 'trigger', 'stop')
845
+ params = self.omit(params, ['trigger', 'stop'])
669
846
  messageHash = 'orders'
670
847
  market = None
671
- type, query = self.handle_market_type_and_params('watchOrders', market, params)
672
- message: dict = {
673
- 'method': 'order.subscribe',
674
- 'id': self.request_id(),
675
- }
848
+ marketList = None
676
849
  if symbol is not None:
677
850
  market = self.market(symbol)
678
851
  symbol = market['symbol']
679
- message['params'] = [market['id']]
852
+ type = None
853
+ type, params = self.handle_market_type_and_params('watchOrders', market, params, 'spot')
854
+ await self.authenticate(type)
855
+ if symbol is not None:
856
+ marketList = [market['id']]
680
857
  messageHash += ':' + symbol
681
858
  else:
682
- message['params'] = []
859
+ marketList = []
860
+ if type == 'spot':
861
+ messageHash += ':spot'
862
+ else:
863
+ messageHash += ':swap'
864
+ method = None
865
+ if stop:
866
+ method = 'stop.subscribe'
867
+ else:
868
+ method = 'order.subscribe'
869
+ message: dict = {
870
+ 'method': method,
871
+ 'params': {'market_list': marketList},
872
+ 'id': self.request_id(),
873
+ }
683
874
  url = self.urls['api']['ws'][type]
684
- request = self.deep_extend(message, query)
875
+ request = self.deep_extend(message, params)
685
876
  orders = await self.watch(url, messageHash, request, messageHash, request)
686
877
  if self.newUpdates:
687
878
  limit = orders.getLimit(symbol, limit)
@@ -689,255 +880,260 @@ class coinex(ccxt.async_support.coinex):
689
880
 
690
881
  def handle_orders(self, client: Client, message):
691
882
  #
692
- # spot
883
+ # spot
693
884
  #
694
- # {
695
- # "method": "order.update",
696
- # "params": [
697
- # 1,
698
- # {
699
- # "id": 77782469357,
700
- # "type": 1,
701
- # "side": 2,
702
- # "user": 1849116,
703
- # "account": 0,
704
- # "option": 2,
705
- # "ctime": 1653961043.048967,
706
- # "mtime": 1653961043.048967,
707
- # "market": "BTCUSDT",
708
- # "source": "web",
709
- # "client_id": '',
710
- # "price": "1.00",
711
- # "amount": "1.00000000",
712
- # "taker_fee": "0.0020",
713
- # "maker_fee": "0.0020",
714
- # "left": "1.00000000",
715
- # "deal_stock": "0",
716
- # "deal_money": "0",
717
- # "money_fee": "0",
718
- # "stock_fee": "0",
719
- # "asset_fee": "0",
720
- # "fee_discount": "1",
721
- # "last_deal_amount": "0",
722
- # "last_deal_price": "0",
723
- # "last_deal_time": 0,
724
- # "last_deal_id": 0,
725
- # "last_role": 0,
726
- # "fee_asset": null,
727
- # "stop_id": 0
728
- # }
729
- # ],
730
- # "id": null
731
- # }
885
+ # {
886
+ # "method": "order.update",
887
+ # "data": {
888
+ # "event": "put",
889
+ # "order": {
890
+ # "order_id": 12750,
891
+ # "market": "BTCUSDT",
892
+ # "margin_market": "BTCUSDT",
893
+ # "type": "limit",
894
+ # "side": "buy",
895
+ # "price": "5999.00",
896
+ # "amount": "1.50000000",
897
+ # "unfill_amount": "1.50000000",
898
+ # "fill_value": "1.50000000",
899
+ # "taker_fee_rate": "0.0001",
900
+ # "maker_fee_rate": "0.0001",
901
+ # "base_ccy_fee": "0.0001",
902
+ # "quote_ccy_fee": "0.0001",
903
+ # "discount_ccy_fee": "0.0001",
904
+ # "last_fill_amount": "0",
905
+ # "last_fill_price": "0",
906
+ # "client_id": "buy1_1234",
907
+ # "created_at": 1689152421692,
908
+ # "updated_at": 1689152421692,
909
+ # }
910
+ # },
911
+ # "id": null
912
+ # }
732
913
  #
733
- # swap
914
+ # spot stop
734
915
  #
735
- # {
736
- # "method": "order.update",
737
- # "params": [
738
- # 1,
739
- # {
740
- # "order_id": 23423462821,
741
- # "position_id": 0,
742
- # "stop_id": 0,
743
- # "market": "BTCUSDT",
744
- # "type": 1,
745
- # "side": 2,
746
- # "target": 0,
747
- # "effect_type": 1,
748
- # "user_id": 1849116,
749
- # "create_time": 1653961509.25049,
750
- # "update_time": 1653961509.25049,
751
- # "source": "web",
752
- # "price": "1.00",
753
- # "amount": "1.0000",
754
- # "taker_fee": "0.00050",
755
- # "maker_fee": "0.00030",
756
- # "left": "1.0000",
757
- # "deal_stock": "0.00000000000000000000",
758
- # "deal_fee": "0.00000000000000000000",
759
- # "deal_profit": "0.00000000000000000000",
760
- # "last_deal_amount": "0.00000000000000000000",
761
- # "last_deal_price": "0.00000000000000000000",
762
- # "last_deal_time": 0,
763
- # "last_deal_id": 0,
764
- # "last_deal_type": 0,
765
- # "last_deal_role": 0,
766
- # "client_id": '',
767
- # "fee_asset": '',
768
- # "fee_discount": "0.00000000000000000000",
769
- # "deal_asset_fee": "0.00000000000000000000",
770
- # "leverage": "3",
771
- # "position_type": 2
772
- # }
773
- # ],
774
- # "id": null
775
- # }
776
- #
777
- params = self.safe_value(message, 'params', [])
778
- order = self.safe_value(params, 1, {})
916
+ # {
917
+ # "method": "stop.update",
918
+ # "data": {
919
+ # "event": 1,
920
+ # "stop": {
921
+ # "stop_id": 102067022299,
922
+ # "market": "BTCUSDT",
923
+ # "margin_market": "BTCUSDT",
924
+ # "type": "limit",
925
+ # "side": "buy",
926
+ # "price": "20000.00",
927
+ # "amount": "0.10000000",
928
+ # "trigger_price": "20000.00",
929
+ # "trigger_direction": "lower",
930
+ # "taker_fee_rate": "0.0016",
931
+ # "maker_fee_rate": "0.0016",
932
+ # "status": "active_success",
933
+ # "client_id": "",
934
+ # "created_at": 1689152996689,
935
+ # "updated_at": 1689152996689,
936
+ # }
937
+ # },
938
+ # "id": null
939
+ # }
940
+ #
941
+ # swap
942
+ #
943
+ # {
944
+ # "method": "order.update",
945
+ # "data": {
946
+ # "event": "put",
947
+ # "order": {
948
+ # "order_id": 98388656341,
949
+ # "stop_id": 0,
950
+ # "market": "BTCUSDT",
951
+ # "side": "buy",
952
+ # "type": "limit",
953
+ # "amount": "0.0010",
954
+ # "price": "50000.00",
955
+ # "unfilled_amount": "0.0010",
956
+ # "filled_amount": "0",
957
+ # "filled_value": "0",
958
+ # "fee": "0",
959
+ # "fee_ccy": "USDT",
960
+ # "taker_fee_rate": "0.00046",
961
+ # "maker_fee_rate": "0.00000000000000000000",
962
+ # "client_id": "",
963
+ # "last_filled_amount": "0.0010",
964
+ # "last_filled_price": "30721.35",
965
+ # "created_at": 1689145715129,
966
+ # "updated_at": 1689145715129
967
+ # }
968
+ # },
969
+ # "id": null
970
+ # }
971
+ #
972
+ # swap stop
973
+ #
974
+ # {
975
+ # "method": "stop.update",
976
+ # "data": {
977
+ # "event": "put",
978
+ # "stop": {
979
+ # "stop_id": 98389557871,
980
+ # "market": "BTCUSDT",
981
+ # "side": "sell",
982
+ # "type": "limit",
983
+ # "price": "20000.00",
984
+ # "amount": "0.0100",
985
+ # "trigger_price": "20000.00",
986
+ # "trigger_direction": "higer",
987
+ # "trigger_price_type": "index_price",
988
+ # "taker_fee_rate": "0.00046",
989
+ # "maker_fee_rate": "0.00026",
990
+ # "client_id": "",
991
+ # "created_at": 1689146382674,
992
+ # "updated_at": 1689146382674
993
+ # }
994
+ # },
995
+ # "id": null
996
+ # }
997
+ #
998
+ data = self.safe_dict(message, 'data', {})
999
+ order = self.safe_dict_2(data, 'order', 'stop', {})
779
1000
  parsedOrder = self.parse_ws_order(order)
1001
+ symbol = parsedOrder['symbol']
1002
+ market = self.market(symbol)
780
1003
  if self.orders is None:
781
1004
  limit = self.safe_integer(self.options, 'ordersLimit', 1000)
782
1005
  self.orders = ArrayCacheBySymbolById(limit)
783
1006
  orders = self.orders
784
1007
  orders.append(parsedOrder)
785
1008
  messageHash = 'orders'
786
- client.resolve(self.orders, messageHash)
787
- messageHash += ':' + parsedOrder['symbol']
1009
+ messageWithType = messageHash + ':' + market['type']
1010
+ client.resolve(self.orders, messageWithType)
1011
+ messageHash += ':' + symbol
788
1012
  client.resolve(self.orders, messageHash)
789
1013
 
790
1014
  def parse_ws_order(self, order, market=None):
791
1015
  #
792
- # spot
1016
+ # spot
793
1017
  #
794
- # {
795
- # "id": 77782469357,
796
- # "type": 1,
797
- # "side": 2,
798
- # "user": 1849116,
799
- # "account": 0,
800
- # "option": 2,
801
- # "ctime": 1653961043.048967,
802
- # "mtime": 1653961043.048967,
803
- # "market": "BTCUSDT",
804
- # "source": "web",
805
- # "client_id": '',
806
- # "price": "1.00",
807
- # "amount": "1.00000000",
808
- # "taker_fee": "0.0020",
809
- # "maker_fee": "0.0020",
810
- # "left": "1.00000000",
811
- # "deal_stock": "0",
812
- # "deal_money": "0",
813
- # "money_fee": "0",
814
- # "stock_fee": "0",
815
- # "asset_fee": "0",
816
- # "fee_discount": "1",
817
- # "last_deal_amount": "0",
818
- # "last_deal_price": "0",
819
- # "last_deal_time": 0,
820
- # "last_deal_id": 0,
821
- # "last_role": 0,
822
- # "fee_asset": null,
823
- # "stop_id": 0
824
- # }
1018
+ # {
1019
+ # "order_id": 12750,
1020
+ # "market": "BTCUSDT",
1021
+ # "margin_market": "BTCUSDT",
1022
+ # "type": "limit",
1023
+ # "side": "buy",
1024
+ # "price": "5999.00",
1025
+ # "amount": "1.50000000",
1026
+ # "unfill_amount": "1.50000000",
1027
+ # "fill_value": "1.50000000",
1028
+ # "taker_fee_rate": "0.0001",
1029
+ # "maker_fee_rate": "0.0001",
1030
+ # "base_ccy_fee": "0.0001",
1031
+ # "quote_ccy_fee": "0.0001",
1032
+ # "discount_ccy_fee": "0.0001",
1033
+ # "last_fill_amount": "0",
1034
+ # "last_fill_price": "0",
1035
+ # "client_id": "buy1_1234",
1036
+ # "created_at": 1689152421692,
1037
+ # "updated_at": 1689152421692,
1038
+ # }
825
1039
  #
826
- # swap
1040
+ # spot stop
827
1041
  #
828
- # {
829
- # "order_id": 23423462821,
830
- # "position_id": 0,
831
- # "stop_id": 0,
832
- # "market": "BTCUSDT",
833
- # "type": 1,
834
- # "side": 2,
835
- # "target": 0,
836
- # "effect_type": 1,
837
- # "user_id": 1849116,
838
- # "create_time": 1653961509.25049,
839
- # "update_time": 1653961509.25049,
840
- # "source": "web",
841
- # "price": "1.00",
842
- # "amount": "1.0000",
843
- # "taker_fee": "0.00050",
844
- # "maker_fee": "0.00030",
845
- # "left": "1.0000",
846
- # "deal_stock": "0.00000000000000000000",
847
- # "deal_fee": "0.00000000000000000000",
848
- # "deal_profit": "0.00000000000000000000",
849
- # "last_deal_amount": "0.00000000000000000000",
850
- # "last_deal_price": "0.00000000000000000000",
851
- # "last_deal_time": 0,
852
- # "last_deal_id": 0,
853
- # "last_deal_type": 0,
854
- # "last_deal_role": 0,
855
- # "client_id": '',
856
- # "fee_asset": '',
857
- # "fee_discount": "0.00000000000000000000",
858
- # "deal_asset_fee": "0.00000000000000000000",
859
- # "leverage": "3",
860
- # "position_type": 2
861
- # }
862
- #
863
- # order.update_stop
864
- #
865
- # {
866
- # "id": 78006745870,
867
- # "type": 1,
868
- # "side": 2,
869
- # "user": 1849116,
870
- # "account": 1,
871
- # "option": 70,
872
- # "direction": 1,
873
- # "ctime": 1654171725.131976,
874
- # "mtime": 1654171725.131976,
875
- # "market": "BTCUSDT",
876
- # "source": "web",
877
- # "client_id": '',
878
- # "stop_price": "1.00",
879
- # "price": "1.00",
880
- # "amount": "1.00000000",
881
- # "taker_fee": "0.0020",
882
- # "maker_fee": "0.0020",
883
- # "fee_discount": "1",
884
- # "fee_asset": null,
885
- # "status": 0
886
- # }
887
- #
888
- timestamp = self.safe_timestamp_2(order, 'update_time', 'mtime')
1042
+ # {
1043
+ # "stop_id": 102067022299,
1044
+ # "market": "BTCUSDT",
1045
+ # "margin_market": "BTCUSDT",
1046
+ # "type": "limit",
1047
+ # "side": "buy",
1048
+ # "price": "20000.00",
1049
+ # "amount": "0.10000000",
1050
+ # "trigger_price": "20000.00",
1051
+ # "trigger_direction": "lower",
1052
+ # "taker_fee_rate": "0.0016",
1053
+ # "maker_fee_rate": "0.0016",
1054
+ # "status": "active_success",
1055
+ # "client_id": "",
1056
+ # "created_at": 1689152996689,
1057
+ # "updated_at": 1689152996689,
1058
+ # }
1059
+ #
1060
+ # swap
1061
+ #
1062
+ # {
1063
+ # "order_id": 98388656341,
1064
+ # "stop_id": 0,
1065
+ # "market": "BTCUSDT",
1066
+ # "side": "buy",
1067
+ # "type": "limit",
1068
+ # "amount": "0.0010",
1069
+ # "price": "50000.00",
1070
+ # "unfilled_amount": "0.0010",
1071
+ # "filled_amount": "0",
1072
+ # "filled_value": "0",
1073
+ # "fee": "0",
1074
+ # "fee_ccy": "USDT",
1075
+ # "taker_fee_rate": "0.00046",
1076
+ # "maker_fee_rate": "0.00000000000000000000",
1077
+ # "client_id": "",
1078
+ # "last_filled_amount": "0.0010",
1079
+ # "last_filled_price": "30721.35",
1080
+ # "created_at": 1689145715129,
1081
+ # "updated_at": 1689145715129
1082
+ # }
1083
+ #
1084
+ # swap stop
1085
+ #
1086
+ # {
1087
+ # "stop_id": 98389557871,
1088
+ # "market": "BTCUSDT",
1089
+ # "side": "sell",
1090
+ # "type": "limit",
1091
+ # "price": "20000.00",
1092
+ # "amount": "0.0100",
1093
+ # "trigger_price": "20000.00",
1094
+ # "trigger_direction": "higer",
1095
+ # "trigger_price_type": "index_price",
1096
+ # "taker_fee_rate": "0.00046",
1097
+ # "maker_fee_rate": "0.00026",
1098
+ # "client_id": "",
1099
+ # "created_at": 1689146382674,
1100
+ # "updated_at": 1689146382674
1101
+ # }
1102
+ #
1103
+ timestamp = self.safe_integer(order, 'created_at')
889
1104
  marketId = self.safe_string(order, 'market')
890
- typeCode = self.safe_string(order, 'type')
891
- type = self.safe_string({
892
- '1': 'limit',
893
- '2': 'market',
894
- }, typeCode)
895
- sideCode = self.safe_string(order, 'side')
896
- side = self.safe_string({
897
- '1': 'sell',
898
- '2': 'buy',
899
- }, sideCode)
900
- remaining = self.safe_string(order, 'left')
901
- amount = self.safe_string(order, 'amount')
902
1105
  status = self.safe_string(order, 'status')
903
- defaultType = self.safe_string(self.options, 'defaultType')
1106
+ isSpot = ('margin_market' in order)
1107
+ defaultType = 'spot' if isSpot else 'swap'
904
1108
  market = self.safe_market(marketId, market, None, defaultType)
905
- cost = self.safe_string(order, 'deal_money')
906
- filled = self.safe_string(order, 'deal_stock')
907
- average = None
908
- if market['swap']:
909
- leverage = self.safe_string(order, 'leverage')
910
- cost = Precise.string_div(filled, leverage)
911
- average = Precise.string_div(filled, amount)
912
- filled = None
913
1109
  fee = None
914
- feeCost = self.omit_zero(self.safe_string(order, 'money_fee'))
1110
+ feeCost = self.omit_zero(self.safe_string_2(order, 'fee', 'quote_ccy_fee'))
915
1111
  if feeCost is not None:
916
- feeCurrencyId = self.safe_string(order, 'fee_asset', market['quote'])
1112
+ feeCurrencyId = self.safe_string(order, 'fee_ccy', market['quote'])
917
1113
  fee = {
918
1114
  'currency': self.safe_currency_code(feeCurrencyId),
919
1115
  'cost': feeCost,
920
1116
  }
921
1117
  return self.safe_order({
922
1118
  'info': order,
923
- 'id': self.safe_string_2(order, 'order_id', 'id'),
1119
+ 'id': self.safe_string_2(order, 'order_id', 'stop_id'),
924
1120
  'clientOrderId': self.safe_string(order, 'client_id'),
925
1121
  'datetime': self.iso8601(timestamp),
926
1122
  'timestamp': timestamp,
927
- 'lastTradeTimestamp': self.safe_timestamp(order, 'last_deal_time'),
1123
+ 'lastTradeTimestamp': self.safe_integer(order, 'updated_at'),
928
1124
  'symbol': market['symbol'],
929
- 'type': type,
1125
+ 'type': self.safe_string(order, 'type'),
930
1126
  'timeInForce': None,
931
1127
  'postOnly': None,
932
- 'side': side,
1128
+ 'side': self.safe_string(order, 'side'),
933
1129
  'price': self.safe_string(order, 'price'),
934
- 'stopPrice': self.safe_string(order, 'stop_price'),
935
- 'triggerPrice': self.safe_string(order, 'stop_price'),
936
- 'amount': amount,
937
- 'filled': filled,
938
- 'remaining': remaining,
939
- 'cost': cost,
940
- 'average': average,
1130
+ 'stopPrice': self.safe_string(order, 'trigger_price'),
1131
+ 'triggerPrice': self.safe_string(order, 'trigger_price'),
1132
+ 'amount': self.safe_string(order, 'amount'),
1133
+ 'filled': self.safe_string_2(order, 'filled_amount', 'fill_value'),
1134
+ 'remaining': self.safe_string_2(order, 'unfilled_amount', 'unfill_amount'),
1135
+ 'cost': None,
1136
+ 'average': None,
941
1137
  'status': self.parse_ws_order_status(status),
942
1138
  'fee': fee,
943
1139
  'trades': None,
@@ -945,24 +1141,109 @@ class coinex(ccxt.async_support.coinex):
945
1141
 
946
1142
  def parse_ws_order_status(self, status):
947
1143
  statuses: dict = {
948
- '0': 'pending',
949
- '1': 'ok',
1144
+ 'active_success': 'open',
1145
+ 'active_fail': 'canceled',
1146
+ 'cancel': 'canceled',
950
1147
  }
951
1148
  return self.safe_string(statuses, status, status)
952
1149
 
1150
+ async def watch_bids_asks(self, symbols: Strings = None, params={}) -> Tickers:
1151
+ """
1152
+ watches best bid & ask for symbols
1153
+ :see: https://docs.coinex.com/api/v2/spot/market/ws/market-bbo
1154
+ :see: https://docs.coinex.com/api/v2/futures/market/ws/market-bbo
1155
+ :param str[] [symbols]: unified symbol of the market to fetch the ticker for
1156
+ :param dict [params]: extra parameters specific to the exchange API endpoint
1157
+ :returns dict: a `ticker structure <https://docs.ccxt.com/#/?id=ticker-structure>`
1158
+ """
1159
+ await self.load_markets()
1160
+ marketIds = self.market_ids(symbols)
1161
+ messageHashes = []
1162
+ market = None
1163
+ symbolsDefined = (symbols is not None)
1164
+ if symbolsDefined:
1165
+ for i in range(0, len(symbols)):
1166
+ symbol = symbols[i]
1167
+ market = self.market(symbol)
1168
+ messageHashes.append('bidsasks:' + market['symbol'])
1169
+ else:
1170
+ messageHashes.append('bidsasks')
1171
+ type = None
1172
+ type, params = self.handle_market_type_and_params('watchBidsAsks', market, params)
1173
+ url = self.urls['api']['ws'][type]
1174
+ subscriptionHashes = ['all@bidsasks']
1175
+ subscribe: dict = {
1176
+ 'method': 'bbo.subscribe',
1177
+ 'params': {'market_list': marketIds},
1178
+ 'id': self.request_id(),
1179
+ }
1180
+ result = await self.watch_multiple(url, messageHashes, self.deep_extend(subscribe, params), subscriptionHashes)
1181
+ if self.newUpdates:
1182
+ return result
1183
+ return self.filter_by_array(self.bidsasks, 'symbol', symbols)
1184
+
1185
+ def handle_bid_ask(self, client: Client, message):
1186
+ #
1187
+ # {
1188
+ # "method": "bbo.update",
1189
+ # "data": {
1190
+ # "market": "BTCUSDT",
1191
+ # "updated_at": 1656660154,
1192
+ # "best_bid_price": "20000",
1193
+ # "best_bid_size": "0.1",
1194
+ # "best_ask_price": "20001",
1195
+ # "best_ask_size": "0.15"
1196
+ # },
1197
+ # "id": null
1198
+ # }
1199
+ #
1200
+ data = self.safe_dict(message, 'data', {})
1201
+ parsedTicker = self.parse_ws_bid_ask(data)
1202
+ symbol = parsedTicker['symbol']
1203
+ self.bidsasks[symbol] = parsedTicker
1204
+ messageHash = 'bidsasks:' + symbol
1205
+ client.resolve(parsedTicker, messageHash)
1206
+
1207
+ def parse_ws_bid_ask(self, ticker, market=None):
1208
+ #
1209
+ # {
1210
+ # "market": "BTCUSDT",
1211
+ # "updated_at": 1656660154,
1212
+ # "best_bid_price": "20000",
1213
+ # "best_bid_size": "0.1",
1214
+ # "best_ask_price": "20001",
1215
+ # "best_ask_size": "0.15"
1216
+ # }
1217
+ #
1218
+ defaultType = self.safe_string(self.options, 'defaultType')
1219
+ marketId = self.safe_string(ticker, 'market')
1220
+ market = self.safe_market(marketId, market, None, defaultType)
1221
+ timestamp = self.safe_timestamp(ticker, 'updated_at')
1222
+ return self.safe_ticker({
1223
+ 'symbol': self.safe_symbol(marketId, market, None, defaultType),
1224
+ 'timestamp': timestamp,
1225
+ 'datetime': self.iso8601(timestamp),
1226
+ 'ask': self.safe_number(ticker, 'best_ask_price'),
1227
+ 'askVolume': self.safe_number(ticker, 'best_ask_size'),
1228
+ 'bid': self.safe_number(ticker, 'best_bid_price'),
1229
+ 'bidVolume': self.safe_number(ticker, 'best_bid_size'),
1230
+ 'info': ticker,
1231
+ }, market)
1232
+
953
1233
  def handle_message(self, client: Client, message):
954
- error = self.safe_value(message, 'error')
955
- if error is not None:
956
- raise ExchangeError(self.id + ' ' + self.json(error))
957
1234
  method = self.safe_string(message, 'method')
1235
+ error = self.safe_string(message, 'message')
1236
+ if error is not None:
1237
+ self.handle_errors(None, None, client.url, method, None, self.json(error), message, None, None)
958
1238
  handlers: dict = {
959
1239
  'state.update': self.handle_ticker,
960
- 'asset.update': self.handle_balance,
1240
+ 'balance.update': self.handle_balance,
961
1241
  'deals.update': self.handle_trades,
1242
+ 'user_deals.update': self.handle_my_trades,
962
1243
  'depth.update': self.handle_order_book,
963
1244
  'order.update': self.handle_orders,
964
- 'kline.update': self.handle_ohlcv,
965
- 'order.update_stop': self.handle_orders,
1245
+ 'stop.update': self.handle_orders,
1246
+ 'bbo.update': self.handle_bid_ask,
966
1247
  }
967
1248
  handler = self.safe_value(handlers, method)
968
1249
  if handler is not None:
@@ -970,92 +1251,90 @@ class coinex(ccxt.async_support.coinex):
970
1251
  return
971
1252
  self.handle_subscription_status(client, message)
972
1253
 
1254
+ def handle_errors(self, code: int, reason: str, url: str, method: str, headers: dict, body: str, response, requestHeaders, requestBody):
1255
+ if response is None:
1256
+ return None
1257
+ #
1258
+ # {"id": 1, "code": 20001, "message": "invalid argument"}
1259
+ # {"id": 2, "code": 21001, "message": "require auth"}
1260
+ # {"id": 1, "code": 21002, "message": "Signature Incorrect"}
1261
+ #
1262
+ message = self.safe_string_lower(response, 'message')
1263
+ isErrorMessage = (message is not None) and (message != 'ok')
1264
+ errorCode = self.safe_string(response, 'code')
1265
+ isErrorCode = (errorCode is not None) and (errorCode != '0')
1266
+ if isErrorCode or isErrorMessage:
1267
+ feedback = self.id + ' ' + body
1268
+ self.throw_exactly_matched_exception(self.exceptions['exact'], errorCode, feedback)
1269
+ self.throw_broadly_matched_exception(self.exceptions['broad'], message, feedback)
1270
+ raise ExchangeError(feedback)
1271
+ return None
1272
+
973
1273
  def handle_authentication_message(self, client: Client, message):
1274
+ #
1275
+ # success
974
1276
  #
975
1277
  # {
976
- # "error": null,
977
- # "result": {
978
- # "status": "success"
979
- # },
980
- # "id": 1
1278
+ # "id": 1,
1279
+ # "code": 0,
1280
+ # "message": "OK"
981
1281
  # }
982
1282
  #
983
- messageHashSpot = 'authenticated:spot'
984
- messageHashSwap = 'authenticated:swap'
985
- # client.resolve(message, messageHashSpot)
986
- # client.resolve(message, messageHashSwap)
987
- spotFuture = self.safe_value(client.futures, messageHashSpot)
988
- spotFuture.resolve(True)
989
- swapFutures = self.safe_value(client.futures, messageHashSwap)
990
- swapFutures.resolve(True)
991
- return message
1283
+ # fail
1284
+ #
1285
+ # {
1286
+ # "id": 1,
1287
+ # "code": 21002,
1288
+ # "message": ""
1289
+ # }
1290
+ #
1291
+ status = self.safe_string_lower(message, 'message')
1292
+ errorCode = self.safe_string(message, 'code')
1293
+ messageHash = 'authenticated'
1294
+ if (status == 'ok') or (errorCode == '0'):
1295
+ future = self.safe_value(client.futures, messageHash)
1296
+ future.resolve(True)
1297
+ else:
1298
+ error = AuthenticationError(self.json(message))
1299
+ client.reject(error, messageHash)
1300
+ if messageHash in client.subscriptions:
1301
+ del client.subscriptions[messageHash]
992
1302
 
993
1303
  def handle_subscription_status(self, client: Client, message):
994
1304
  id = self.safe_integer(message, 'id')
995
1305
  subscription = self.safe_value(client.subscriptions, id)
996
1306
  if subscription is not None:
997
1307
  futureIndex = self.safe_string(subscription, 'future')
998
- if futureIndex == 'ohlcv':
999
- self.handle_ohlcv(client, message)
1000
- return
1001
1308
  future = self.safe_value(client.futures, futureIndex)
1002
1309
  if future is not None:
1003
1310
  future.resolve(True)
1004
1311
  del client.subscriptions[id]
1005
1312
 
1006
- async def authenticate(self, params={}):
1007
- type = None
1008
- type, params = self.handle_market_type_and_params('authenticate', None, params)
1313
+ async def authenticate(self, type: str):
1009
1314
  url = self.urls['api']['ws'][type]
1010
1315
  client = self.client(url)
1011
1316
  time = self.milliseconds()
1012
- isSpot = (type == 'spot')
1013
- spotMessageHash = 'authenticated:spot'
1014
- swapMessageHash = 'authenticated:swap'
1015
- messageHash = spotMessageHash if isSpot else swapMessageHash
1317
+ timestamp = str(time)
1318
+ messageHash = 'authenticated'
1016
1319
  future = client.future(messageHash)
1017
1320
  authenticated = self.safe_value(client.subscriptions, messageHash)
1018
- if type == 'spot':
1019
- if authenticated is not None:
1020
- return await future
1021
- requestId = self.request_id()
1022
- subscribe: dict = {
1023
- 'id': requestId,
1024
- 'future': spotMessageHash,
1025
- }
1026
- signData = 'access_id=' + self.apiKey + '&tonce=' + self.number_to_string(time) + '&secret_key=' + self.secret
1027
- hash = self.hash(self.encode(signData), 'md5')
1028
- request: dict = {
1029
- 'method': 'server.sign',
1030
- 'params': [
1031
- self.apiKey,
1032
- hash.upper(),
1033
- time,
1034
- ],
1035
- 'id': requestId,
1036
- }
1037
- self.watch(url, messageHash, request, requestId, subscribe)
1038
- client.subscriptions[messageHash] = True
1039
- return await future
1040
- else:
1041
- if authenticated is not None:
1042
- return await future
1043
- requestId = self.request_id()
1044
- subscribe: dict = {
1045
- 'id': requestId,
1046
- 'future': swapMessageHash,
1047
- }
1048
- signData = 'access_id=' + self.apiKey + '&timestamp=' + self.number_to_string(time) + '&secret_key=' + self.secret
1049
- hash = self.hash(self.encode(signData), 'sha256', 'hex')
1050
- request: dict = {
1051
- 'method': 'server.sign',
1052
- 'params': [
1053
- self.apiKey,
1054
- hash.lower(),
1055
- time,
1056
- ],
1057
- 'id': requestId,
1058
- }
1059
- self.watch(url, messageHash, request, requestId, subscribe)
1060
- client.subscriptions[messageHash] = True
1321
+ if authenticated is not None:
1061
1322
  return await future
1323
+ requestId = self.request_id()
1324
+ subscribe: dict = {
1325
+ 'id': requestId,
1326
+ 'future': messageHash,
1327
+ }
1328
+ hmac = self.hmac(self.encode(timestamp), self.encode(self.secret), hashlib.sha256, 'hex')
1329
+ request: dict = {
1330
+ 'id': requestId,
1331
+ 'method': 'server.sign',
1332
+ 'params': {
1333
+ 'access_id': self.apiKey,
1334
+ 'signed_str': hmac.lower(),
1335
+ 'timestamp': time,
1336
+ },
1337
+ }
1338
+ self.watch(url, messageHash, request, requestId, subscribe)
1339
+ client.subscriptions[messageHash] = True
1340
+ return await future