ccxt 2.9.10__py2.py3-none-any.whl → 2.9.12__py2.py3-none-any.whl

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

Potentially problematic release.


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

ccxt/pro/bybit.py CHANGED
@@ -32,47 +32,43 @@ class bybit(Exchange, ccxt.async_support.bybit):
32
32
  'api': {
33
33
  'ws': {
34
34
  'public': {
35
- 'spot': 'wss://stream.{hostname}/spot/public/v3',
36
- 'inverse': 'wss://stream.{hostname}/contract/inverse/public/v3',
37
- 'usdt': 'wss://stream.{hostname}/contract/usdt/public/v3',
38
- 'usdc': {
39
- 'option': 'wss://stream.{hostname}/option/usdc/public/v3',
40
- 'swap': 'wss://stream.{hostname}/contract/usdc/public/v3',
41
- },
35
+ 'spot': 'wss://stream.{hostname}/v5/public/spot',
36
+ 'inverse': 'wss://stream.{hostname}/v5/public/inverse',
37
+ 'option': 'wss://stream.{hostname}/v5/public/option',
38
+ 'linear': 'wss://stream.{hostname}/v5/public/linear',
42
39
  },
43
40
  'private': {
44
- 'spot': 'wss://stream.{hostname}/spot/private/v3',
45
- 'contract': {
46
- 'unified': 'wss://stream.{hostname}/unified/private/v3',
47
- 'nonUnified': 'wss://stream.{hostname}/contract/private/v3',
41
+ 'spot': {
42
+ 'unified': 'wss://stream.{hostname}/v5/private',
43
+ 'nonUnified': 'wss://stream.{hostname}/spot/private/v3',
48
44
  },
45
+ 'contract': 'wss://stream.{hostname}/v5/private',
46
+ 'usdc': 'wss://stream.{hostname}/trade/option/usdc/private/v1',
49
47
  },
50
48
  },
51
49
  },
52
50
  'test': {
53
51
  'ws': {
54
52
  'public': {
55
- 'spot': 'wss://stream-testnet.{hostname}/spot/public/v3',
56
- 'inverse': 'wss://stream-testnet.{hostname}/contract/inverse/public/v3',
57
- 'usdt': 'wss://stream-testnet.{hostname}/contract/usdt/public/v3',
58
- 'usdc': {
59
- 'option': 'wss://stream-testnet.{hostname}/option/usdc/public/v3',
60
- 'swap': 'wss://stream-testnet.{hostname}/contract/usdc/public/v3',
61
- },
53
+ 'spot': 'wss://stream-testnet.{hostname}/v5/public/spot',
54
+ 'inverse': 'wss://stream-testnet.{hostname}/v5/public/inverse',
55
+ 'linear': 'wss://stream-testnet.{hostname}/v5/public/linear',
56
+ 'option': 'wss://stream-testnet.{hostname}/v5/public/option',
62
57
  },
63
58
  'private': {
64
- 'spot': 'wss://stream-testnet.{hostname}/spot/private/v3',
65
- 'contract': {
66
- 'unified': 'wss://stream-testnet.{hostname}/unified/private/v3',
67
- 'nonUnified': 'wss://stream-testnet.{hostname}/contract/private/v3',
59
+ 'spot': {
60
+ 'unified': 'wss://stream-testnet.{hostname}/v5/private',
61
+ 'nonUnified': 'wss://stream-testnet.{hostname}/spot/private/v3',
68
62
  },
63
+ 'contract': 'wss://stream-testnet.{hostname}/v5/private',
64
+ 'usdc': 'wss://stream-testnet.{hostname}/trade/option/usdc/private/v1',
69
65
  },
70
66
  },
71
67
  },
72
68
  },
73
69
  'options': {
74
70
  'watchTicker': {
75
- 'name': 'tickers', # 'tickers' for 24hr statistical ticker or 'bookticker' for Best bid price and best ask price
71
+ 'name': 'tickers', # 'tickers' for 24hr statistical ticker or 'tickers_lt' for leverage token ticker
76
72
  },
77
73
  'spot': {
78
74
  'timeframes': {
@@ -126,43 +122,35 @@ class bybit(Exchange, ccxt.async_support.bybit):
126
122
  self.options['requestId'] = requestId
127
123
  return requestId
128
124
 
129
- def get_url_by_market_type(self, symbol=None, isPrivate=False, isUnifiedMargin=False, method=None, params={}):
125
+ def get_url_by_market_type(self, symbol=None, isPrivate=False, method=None, params={}):
130
126
  accessibility = 'private' if isPrivate else 'public'
131
127
  isUsdcSettled = None
132
128
  isSpot = None
133
129
  type = None
134
- isUsdtSettled = None
135
130
  market = None
136
131
  url = self.urls['api']['ws']
137
132
  if symbol is not None:
138
133
  market = self.market(symbol)
139
134
  isUsdcSettled = market['settle'] == 'USDC'
140
- isUsdtSettled = market['settle'] == 'USDT'
141
- isSpot = market['spot']
142
135
  type = market['type']
143
136
  else:
144
137
  type, params = self.handle_market_type_and_params(method, None, params)
145
138
  defaultSettle = self.safe_string(self.options, 'defaultSettle')
146
139
  defaultSettle = self.safe_string_2(params, 'settle', 'defaultSettle', defaultSettle)
147
140
  isUsdcSettled = (defaultSettle == 'USDC')
148
- isUsdtSettled = (defaultSettle == 'USDT')
149
- isSpot = (type == 'spot')
141
+ isSpot = (type == 'spot')
150
142
  if isPrivate:
151
- if isSpot:
152
- url = url[accessibility]['spot']
153
- else:
154
- margin = 'unified' if isUnifiedMargin else 'nonUnified'
155
- url = url[accessibility]['contract'][margin]
143
+ url = url[accessibility]['usdc'] if (isUsdcSettled) else url[accessibility]['contract']
156
144
  else:
157
145
  if isSpot:
158
146
  url = url[accessibility]['spot']
159
- elif isUsdcSettled:
160
- url = url[accessibility]['usdc'][type]
161
- elif isUsdtSettled:
162
- url = url[accessibility]['usdt']
147
+ elif type == 'swap':
148
+ subType = None
149
+ subType, params = self.handle_sub_type_and_params(method, market, params, 'linear')
150
+ url = url[accessibility][subType]
163
151
  else:
164
- # inverse
165
- url = url[accessibility]['inverse']
152
+ # option
153
+ url = url[accessibility]['option']
166
154
  url = self.implode_hostname(url)
167
155
  return url
168
156
 
@@ -173,6 +161,8 @@ class bybit(Exchange, ccxt.async_support.bybit):
173
161
  async def watch_ticker(self, symbol, params={}):
174
162
  """
175
163
  watches a price ticker, a statistical calculation with the information calculated over the past 24 hours for a specific market
164
+ see https://bybit-exchange.github.io/docs/v5/websocket/public/ticker
165
+ see https://bybit-exchange.github.io/docs/v5/websocket/public/etp-ticker
176
166
  :param str symbol: unified symbol of the market to fetch the ticker for
177
167
  :param dict params: extra parameters specific to the bybit api endpoint
178
168
  :returns dict: a `ticker structure <https://docs.ccxt.com/en/latest/manual.html#ticker-structure>`
@@ -180,7 +170,7 @@ class bybit(Exchange, ccxt.async_support.bybit):
180
170
  await self.load_markets()
181
171
  market = self.market(symbol)
182
172
  messageHash = 'ticker:' + market['symbol']
183
- url = self.get_url_by_market_type(symbol, False, False, params)
173
+ url = self.get_url_by_market_type(symbol, False, params)
184
174
  params = self.clean_params(params)
185
175
  options = self.safe_value(self.options, 'watchTicker', {})
186
176
  topic = self.safe_string(options, 'name', 'tickers')
@@ -192,67 +182,104 @@ class bybit(Exchange, ccxt.async_support.bybit):
192
182
 
193
183
  def handle_ticker(self, client, message):
194
184
  #
195
- # spot - tickers
196
- # {
197
- # "data": {
198
- # "t": 1661742216005,
199
- # "s": "BTCUSDT",
200
- # "o": "19820",
201
- # "h": "20071.93",
202
- # "l": "19365.85",
203
- # "c": "19694.27",
204
- # "v": "9997.246174",
205
- # "qv": "197357775.97621786",
206
- # "m": "-0.0063"
207
- # },
208
- # "type": "delta",
209
- # "topic": "tickers.BTCUSDT",
210
- # "ts": 1661742216011
211
- # }
212
- # spot - bookticker
213
- # {
214
- # "data": {
215
- # "s": "BTCUSDT",
216
- # "bp": "19693.04",
217
- # "bq": "0.913957",
218
- # "ap": "19694.27",
219
- # "aq": "0.705447",
220
- # "t": 1661742216108
221
- # },
222
- # "type": "delta",
223
- # "topic": "bookticker.BTCUSDT",
224
- # "ts": 1661742216109
225
- # }
226
- # swap
227
- # {
228
- # "topic":"tickers.BTCUSDT",
229
- # "type":"snapshot",
230
- # "data":{
231
- # "symbol":"BTCUSDT",
232
- # "tickDirection":"ZeroMinusTick",
233
- # "price24hPcnt":"0.032786",
234
- # "lastPrice":"22019.00",
235
- # "prevPrice24h":"21320.00",
236
- # "highPrice24h":"22522.00",
237
- # "lowPrice24h":"20745.00",
238
- # "prevPrice1h":"22186.50",
239
- # "markPrice":"22010.11",
240
- # "indexPrice":"22009.01",
241
- # "openInterest":"44334.438",
242
- # "turnover24h":"4609010554.786498",
243
- # "volume24h":"213532.606",
244
- # "fundingRate":"0.0001",
245
- # "nextFundingTime":"2022-07-18T16:00:00Z",
246
- # "bid1Price":"22019.00",
247
- # "bid1Size":"41.530",
248
- # "ask1Price":"22019.50",
249
- # "ask1Size":"7.041",
250
- # "basisRate":"0",
251
- # "deliveryFeeRate":"0"
252
- # },
253
- # "cs":14236992078,
254
- # "ts":1663203915102
255
- # }
185
+ # linear
186
+ # {
187
+ # "topic": "tickers.BTCUSDT",
188
+ # "type": "snapshot",
189
+ # "data": {
190
+ # "symbol": "BTCUSDT",
191
+ # "tickDirection": "PlusTick",
192
+ # "price24hPcnt": "0.017103",
193
+ # "lastPrice": "17216.00",
194
+ # "prevPrice24h": "16926.50",
195
+ # "highPrice24h": "17281.50",
196
+ # "lowPrice24h": "16915.00",
197
+ # "prevPrice1h": "17238.00",
198
+ # "markPrice": "17217.33",
199
+ # "indexPrice": "17227.36",
200
+ # "openInterest": "68744.761",
201
+ # "openInterestValue": "1183601235.91",
202
+ # "turnover24h": "1570383121.943499",
203
+ # "volume24h": "91705.276",
204
+ # "nextFundingTime": "1673280000000",
205
+ # "fundingRate": "-0.000212",
206
+ # "bid1Price": "17215.50",
207
+ # "bid1Size": "84.489",
208
+ # "ask1Price": "17216.00",
209
+ # "ask1Size": "83.020"
210
+ # },
211
+ # "cs": 24987956059,
212
+ # "ts": 1673272861686
213
+ # }
214
+ #
215
+ # option
216
+ # {
217
+ # "id": "tickers.BTC-6JAN23-17500-C-2480334983-1672917511074",
218
+ # "topic": "tickers.BTC-6JAN23-17500-C",
219
+ # "ts": 1672917511074,
220
+ # "data": {
221
+ # "symbol": "BTC-6JAN23-17500-C",
222
+ # "bidPrice": "0",
223
+ # "bidSize": "0",
224
+ # "bidIv": "0",
225
+ # "askPrice": "10",
226
+ # "askSize": "5.1",
227
+ # "askIv": "0.514",
228
+ # "lastPrice": "10",
229
+ # "highPrice24h": "25",
230
+ # "lowPrice24h": "5",
231
+ # "markPrice": "7.86976724",
232
+ # "indexPrice": "16823.73",
233
+ # "markPriceIv": "0.4896",
234
+ # "underlyingPrice": "16815.1",
235
+ # "openInterest": "49.85",
236
+ # "turnover24h": "446802.8473",
237
+ # "volume24h": "26.55",
238
+ # "totalVolume": "86",
239
+ # "totalTurnover": "1437431",
240
+ # "delta": "0.047831",
241
+ # "gamma": "0.00021453",
242
+ # "vega": "0.81351067",
243
+ # "theta": "-19.9115368",
244
+ # "predictedDeliveryPrice": "0",
245
+ # "change24h": "-0.33333334"
246
+ # },
247
+ # "type": "snapshot"
248
+ # }
249
+ #
250
+ # spot
251
+ # {
252
+ # "topic": "tickers.BTCUSDT",
253
+ # "ts": 1673853746003,
254
+ # "type": "snapshot",
255
+ # "cs": 2588407389,
256
+ # "data": {
257
+ # "symbol": "BTCUSDT",
258
+ # "lastPrice": "21109.77",
259
+ # "highPrice24h": "21426.99",
260
+ # "lowPrice24h": "20575",
261
+ # "prevPrice24h": "20704.93",
262
+ # "volume24h": "6780.866843",
263
+ # "turnover24h": "141946527.22907118",
264
+ # "price24hPcnt": "0.0196",
265
+ # "usdIndexPrice": "21120.2400136"
266
+ # }
267
+ # }
268
+ #
269
+ # lt ticker
270
+ # {
271
+ # "topic": "tickers_lt.EOS3LUSDT",
272
+ # "ts": 1672325446847,
273
+ # "type": "snapshot",
274
+ # "data": {
275
+ # "symbol": "EOS3LUSDT",
276
+ # "lastPrice": "0.41477848043290448",
277
+ # "highPrice24h": "0.435285472510871305",
278
+ # "lowPrice24h": "0.394601507960931382",
279
+ # "prevPrice24h": "0.431502290172376349",
280
+ # "price24hPcnt": "-0.0388"
281
+ # }
282
+ # }
256
283
  #
257
284
  topic = self.safe_string(message, 'topic', '')
258
285
  updateType = self.safe_string(message, 'type', '')
@@ -284,6 +311,8 @@ class bybit(Exchange, ccxt.async_support.bybit):
284
311
  async def watch_ohlcv(self, symbol, timeframe='1m', since=None, limit=None, params={}):
285
312
  """
286
313
  watches historical candlestick data containing the open, high, low, and close price, and the volume of a market
314
+ see https://bybit-exchange.github.io/docs/v5/websocket/public/kline
315
+ see https://bybit-exchange.github.io/docs/v5/websocket/public/etp-kline
287
316
  :param str symbol: unified symbol of the market to fetch OHLCV data for
288
317
  :param str timeframe: the length of time each candle represents
289
318
  :param int|None since: timestamp in ms of the earliest candle to fetch
@@ -294,13 +323,10 @@ class bybit(Exchange, ccxt.async_support.bybit):
294
323
  await self.load_markets()
295
324
  market = self.market(symbol)
296
325
  symbol = market['symbol']
297
- url = self.get_url_by_market_type(symbol, False, False, params)
326
+ url = self.get_url_by_market_type(symbol, False, params)
298
327
  params = self.clean_params(params)
299
328
  ohlcv = None
300
- marketType = 'spot' if market['spot'] else 'contract'
301
- marketOptions = self.safe_value(self.options, marketType, {})
302
- timeframes = self.safe_value(marketOptions, 'timeframes', {})
303
- timeframeId = self.safe_string(timeframes, timeframe, timeframe)
329
+ timeframeId = self.safe_string(self.timeframes, timeframe, timeframe)
304
330
  topics = ['kline.' + timeframeId + '.' + market['id']]
305
331
  messageHash = 'kline' + ':' + timeframeId + ':' + symbol
306
332
  ohlcv = await self.watch_topics(url, messageHash, topics, params)
@@ -310,43 +336,26 @@ class bybit(Exchange, ccxt.async_support.bybit):
310
336
 
311
337
  def handle_ohlcv(self, client, message):
312
338
  #
313
- # swap
314
- # {
315
- # "topic":"kline.1.BTCUSDT",
316
- # "data":[
317
- # {
318
- # "start":1658150220000,
319
- # "end":1658150279999,
320
- # "interval":"1",
321
- # "open":"22212",
322
- # "close":"22214",
323
- # "high":"22214.5",
324
- # "low":"22212",
325
- # "volume":"5.456",
326
- # "turnover":"121193.36",
327
- # "confirm":false,
328
- # "timestamp":1658150224542
329
- # }
330
- # ],
331
- # "ts":1658150224542,
332
- # "type":"snapshot"
333
- # }
334
- #
335
- # spot
336
- # {
337
- # "data": {
338
- # "t": 1661742000000,
339
- # "s": "BTCUSDT",
340
- # "c": "19685.55",
341
- # "h": "19756.95",
342
- # "l": "19674.61",
343
- # "o": "19705.38",
344
- # "v": "0.232154"
345
- # },
346
- # "type": "delta",
347
- # "topic": "kline.1h.BTCUSDT",
348
- # "ts": 1661745259605
349
- # }
339
+ # {
340
+ # "topic": "kline.5.BTCUSDT",
341
+ # "data": [
342
+ # {
343
+ # "start": 1672324800000,
344
+ # "end": 1672325099999,
345
+ # "interval": "5",
346
+ # "open": "16649.5",
347
+ # "close": "16677",
348
+ # "high": "16677",
349
+ # "low": "16608",
350
+ # "volume": "2.081",
351
+ # "turnover": "34666.4005",
352
+ # "confirm": False,
353
+ # "timestamp": 1672324988882
354
+ # }
355
+ # ],
356
+ # "ts": 1672324988882,
357
+ # "type": "snapshot"
358
+ # }
350
359
  #
351
360
  data = self.safe_value(message, 'data', {})
352
361
  topic = self.safe_string(message, 'topic')
@@ -366,17 +375,13 @@ class bybit(Exchange, ccxt.async_support.bybit):
366
375
  limit = self.safe_integer(self.options, 'OHLCVLimit', 1000)
367
376
  stored = ArrayCacheByTimestamp(limit)
368
377
  self.ohlcvs[symbol][timeframeId] = stored
369
- if isinstance(data, list):
370
- for i in range(0, len(data)):
371
- parsed = self.parse_ws_contract_ohlcv(data[i])
372
- stored.append(parsed)
373
- else:
374
- parsed = self.parse_ohlcv(data, market)
378
+ for i in range(0, len(data)):
379
+ parsed = self.parse_ws_ohlcv(data[i])
375
380
  stored.append(parsed)
376
381
  messageHash = 'kline' + ':' + timeframeId + ':' + symbol
377
382
  client.resolve(stored, messageHash)
378
383
 
379
- def parse_ws_contract_ohlcv(self, ohlcv):
384
+ def parse_ws_ohlcv(self, ohlcv):
380
385
  #
381
386
  # {
382
387
  # "start": 1670363160000,
@@ -404,6 +409,7 @@ class bybit(Exchange, ccxt.async_support.bybit):
404
409
  async def watch_order_book(self, symbol, limit=None, params={}):
405
410
  """
406
411
  watches information on open orders with bid(buy) and ask(sell) prices, volumes and other data
412
+ see https://bybit-exchange.github.io/docs/v5/websocket/public/orderbook
407
413
  :param str symbol: unified symbol of the market to fetch the order book for
408
414
  :param int|None limit: the maximum amount of order book entries to return.
409
415
  :param dict params: extra parameters specific to the bybit api endpoint
@@ -412,81 +418,60 @@ class bybit(Exchange, ccxt.async_support.bybit):
412
418
  await self.load_markets()
413
419
  market = self.market(symbol)
414
420
  symbol = market['symbol']
415
- url = self.get_url_by_market_type(symbol, False, False, params)
421
+ url = self.get_url_by_market_type(symbol, False, params)
416
422
  params = self.clean_params(params)
417
423
  messageHash = 'orderbook' + ':' + symbol
418
424
  if limit is None:
419
425
  if market['spot']:
420
- limit = 40
426
+ limit = 50
421
427
  else:
422
- limit = 200
428
+ limit = 500
423
429
  else:
424
430
  if not market['spot']:
425
- # bybit only support limit 1, 50 , 200 for contract
426
- if (limit != 1) and (limit != 50) and (limit != 200):
427
- raise BadRequest(self.id + ' watchOrderBook() can only use limit 1, 50 and 200.')
431
+ # bybit only support limit 1, 50, 200, 500 for contract
432
+ if (limit != 1) and (limit != 50) and (limit != 200) and (limit != 500):
433
+ raise BadRequest(self.id + ' watchOrderBook() can only use limit 1, 50, 200 and 500.')
428
434
  topics = ['orderbook.' + str(limit) + '.' + market['id']]
429
435
  orderbook = await self.watch_topics(url, messageHash, topics, params)
430
436
  return orderbook.limit()
431
437
 
432
438
  def handle_order_book(self, client, message):
433
439
  #
434
- # spot snapshot
435
440
  # {
441
+ # "topic": "orderbook.50.BTCUSDT",
442
+ # "type": "snapshot",
443
+ # "ts": 1672304484978,
436
444
  # "data": {
437
445
  # "s": "BTCUSDT",
438
- # "t": 1661743689733,
439
446
  # "b": [
447
+ # ...,
440
448
  # [
441
- # "19721.9",
442
- # "0.784806"
449
+ # "16493.50",
450
+ # "0.006"
443
451
  # ],
444
- # ...
452
+ # [
453
+ # "16493.00",
454
+ # "0.100"
455
+ # ]
445
456
  # ],
446
457
  # "a": [
447
458
  # [
448
- # "19721.91",
449
- # "0.192687"
459
+ # "16611.00",
460
+ # "0.029"
450
461
  # ],
451
- # ...
452
- # ]
453
- # },
454
- # "type": "delta", # docs say to ignore, always snapshot
455
- # "topic": "orderbook.40.BTCUSDT",
456
- # "ts": 1661743689735
462
+ # [
463
+ # "16612.00",
464
+ # "0.213"
465
+ # ],
466
+ # ],
467
+ # "u": 18521288,
468
+ # "seq": 7961638724
469
+ # }
457
470
  # }
458
471
  #
459
- # contract
460
- # {
461
- # "topic": "orderbook.50.BTCUSDT",
462
- # "type": "snapshot",
463
- # "ts": 1668748553479,
464
- # "data": {
465
- # "s": "BTCUSDT",
466
- # "b": [
467
- # [
468
- # "17053.00", #price
469
- # "0.021" #size
470
- # ],
471
- # ....
472
- # ],
473
- # "a": [
474
- # [
475
- # "17054.00",
476
- # "6.288"
477
- # ],
478
- # ....
479
- # ],
480
- # "u": 3083181,
481
- # "seq": 7545268447
482
- # }
483
- # }
484
- #
485
472
  isSpot = client.url.find('spot') >= 0
486
473
  type = self.safe_string(message, 'type')
487
474
  isSnapshot = (type == 'snapshot')
488
- if isSpot:
489
- isSnapshot = True
490
475
  data = self.safe_value(message, 'data', {})
491
476
  marketId = self.safe_string(data, 's')
492
477
  marketType = 'spot' if isSpot else 'contract'
@@ -521,6 +506,7 @@ class bybit(Exchange, ccxt.async_support.bybit):
521
506
  async def watch_trades(self, symbol, since=None, limit=None, params={}):
522
507
  """
523
508
  watches information on multiple trades made in a market
509
+ see https://bybit-exchange.github.io/docs/v5/websocket/public/trade
524
510
  :param str symbol: unified market symbol of the market orders were made in
525
511
  :param int|None since: the earliest time in ms to fetch orders for
526
512
  :param int|None limit: the maximum number of orde structures to retrieve
@@ -530,18 +516,10 @@ class bybit(Exchange, ccxt.async_support.bybit):
530
516
  await self.load_markets()
531
517
  market = self.market(symbol)
532
518
  symbol = market['symbol']
533
- url = self.get_url_by_market_type(symbol, False, False, params)
519
+ url = self.get_url_by_market_type(symbol, False, params)
534
520
  params = self.clean_params(params)
535
521
  messageHash = 'trade:' + symbol
536
- topic = None
537
- if market['spot']:
538
- topic = 'trade.' + market['id']
539
- else:
540
- topic = 'publicTrade.'
541
- if market['option']:
542
- topic += market['baseId']
543
- else:
544
- topic += market['id']
522
+ topic = 'publicTrade.' + market['id']
545
523
  trades = await self.watch_topics(url, messageHash, [topic], params)
546
524
  if self.newUpdates:
547
525
  limit = trades.getLimit(symbol, limit)
@@ -549,53 +527,32 @@ class bybit(Exchange, ccxt.async_support.bybit):
549
527
 
550
528
  def handle_trades(self, client, message):
551
529
  #
552
- # swap
553
- # {
554
- # "topic": "publicTrade.BTCUSDT",
555
- # "type": "snapshot",
556
- # "ts": 1662694953823,
557
- # "data": [
558
- # {
559
- # "T": 1662694953819,
560
- # "s": "BTCUSDT",
561
- # "S": "Buy",
562
- # "v": "0.010",
563
- # "p": "19792.50",
564
- # "L": "PlusTick",
565
- # "i": "5c9ab13e-6010-522c-aecd-02c4d9c8db3d",
566
- # "BT": False
567
- # }
568
- # ]
569
- # }
570
- #
571
- # spot
572
- # {
573
- # "data": {
574
- # "v": "2100000000001992601",
575
- # "t": 1661742109857,
576
- # "p": "19706.87",
577
- # "q": "0.000158",
578
- # "m": True
579
- # },
580
- # "type": "delta",
581
- # "topic": "trade.BTCUSDT",
582
- # "ts": 1661742109863
583
- # }
530
+ # {
531
+ # "topic": "publicTrade.BTCUSDT",
532
+ # "type": "snapshot",
533
+ # "ts": 1672304486868,
534
+ # "data": [
535
+ # {
536
+ # "T": 1672304486865,
537
+ # "s": "BTCUSDT",
538
+ # "S": "Buy",
539
+ # "v": "0.001",
540
+ # "p": "16578.50",
541
+ # "L": "PlusTick",
542
+ # "i": "20f43950-d8dd-5b31-9112-a178eb6023af",
543
+ # "BT": False
544
+ # }
545
+ # ]
546
+ # }
584
547
  #
585
548
  data = self.safe_value(message, 'data', {})
586
549
  topic = self.safe_string(message, 'topic')
587
- trades = None
550
+ trades = data
588
551
  parts = topic.split('.')
589
- tradeType = self.safe_string(parts, 0)
590
- marketType = 'spot' if (tradeType == 'trade') else 'contract'
552
+ isSpot = client.url.find('spot') >= 0
553
+ marketType = 'spot' if (isSpot) else 'contract'
591
554
  marketId = self.safe_string(parts, 1)
592
555
  market = self.safe_market(marketId, None, None, marketType)
593
- if isinstance(data, list):
594
- # contract markets
595
- trades = data
596
- else:
597
- # spot markets
598
- trades = [data]
599
556
  symbol = market['symbol']
600
557
  stored = self.safe_value(self.trades, symbol)
601
558
  if stored is None:
@@ -610,51 +567,16 @@ class bybit(Exchange, ccxt.async_support.bybit):
610
567
 
611
568
  def parse_ws_trade(self, trade, market=None):
612
569
  #
613
- # contract public
614
- # {
615
- # T: 1670198879981,
616
- # s: 'BTCUSDT',
617
- # S: 'Buy',
618
- # v: '0.001',
619
- # p: '17130.00',
620
- # L: 'ZeroPlusTick',
621
- # i: 'a807f4ee-2e8b-5f21-a02a-3e258ddbfdc9',
622
- # BT: False
623
- # }
624
- # contract private
625
- #
626
- # parsed by rest implementation
627
- # {
628
- # "symbol": "BITUSDT",
629
- # "execFee": "0.02022",
630
- # "execId": "beba036f-9fb4-59a7-84b7-2620e5d13e1c",
631
- # "execPrice": "0.674",
632
- # "execQty": "50",
633
- # "execType": "Trade",
634
- # "execValue": "33.7",
635
- # "feeRate": "0.0006",
636
- # "lastLiquidityInd": "RemovedLiquidity",
637
- # "leavesQty": "0",
638
- # "orderId": "ddbea432-2bd7-45dd-ab42-52d920b8136d",
639
- # "orderLinkId": "b001",
640
- # "orderPrice": "0.707",
641
- # "orderQty": "50",
642
- # "orderType": "Market",
643
- # "stopOrderType": "UNKNOWN",
644
- # "side": "Buy",
645
- # "execTime": "1659057535081",
646
- # "closedSize": "0"
647
- # }
648
- #
649
- # spot public
650
- #
570
+ # public
651
571
  # {
652
- # 'symbol': 'BTCUSDT', # artificially added
653
- # v: '2290000000003002848', # trade id
654
- # t: 1652967602261,
655
- # p: '29698.82',
656
- # q: '0.189531',
657
- # m: True
572
+ # "T": 1672304486865,
573
+ # "s": "BTCUSDT",
574
+ # "S": "Buy",
575
+ # "v": "0.001",
576
+ # "p": "16578.50",
577
+ # "L": "PlusTick",
578
+ # "i": "20f43950-d8dd-5b31-9112-a178eb6023af",
579
+ # "BT": False
658
580
  # }
659
581
  #
660
582
  # spot private
@@ -678,6 +600,8 @@ class bybit(Exchange, ccxt.async_support.bybit):
678
600
  id = self.safe_string_n(trade, ['i', 'T', 'v'])
679
601
  isContract = ('BT' in trade)
680
602
  marketType = 'contract' if isContract else 'spot'
603
+ if market is not None:
604
+ marketType = market['type']
681
605
  marketId = self.safe_string(trade, 's')
682
606
  market = self.safe_market(marketId, market, None, marketType)
683
607
  symbol = market['symbol']
@@ -712,14 +636,15 @@ class bybit(Exchange, ccxt.async_support.bybit):
712
636
  def get_private_type(self, url):
713
637
  if url.find('spot') >= 0:
714
638
  return 'spot'
715
- elif url.find('unified') >= 0:
639
+ elif url.find('v5/private') >= 0:
716
640
  return 'unified'
717
- elif url.find('contract') >= 0:
718
- return 'contract'
641
+ else:
642
+ return 'usdc'
719
643
 
720
644
  async def watch_my_trades(self, symbol=None, since=None, limit=None, params={}):
721
645
  """
722
646
  watches information on multiple trades made by the user
647
+ see https://bybit-exchange.github.io/docs/v5/websocket/private/execution
723
648
  :param str symbol: unified market symbol of the market orders were made in
724
649
  :param int|None since: the earliest time in ms to fetch orders for
725
650
  :param int|None limit: the maximum number of orde structures to retrieve
@@ -733,14 +658,12 @@ class bybit(Exchange, ccxt.async_support.bybit):
733
658
  if symbol is not None:
734
659
  symbol = self.symbol(symbol)
735
660
  messageHash += ':' + symbol
736
- unified = await self.isUnifiedEnabled()
737
- isUnifiedMargin = self.safe_value(unified, 0, False)
738
- url = self.get_url_by_market_type(symbol, True, isUnifiedMargin, method, params)
661
+ url = self.get_url_by_market_type(symbol, True, method, params)
739
662
  await self.authenticate(url)
740
663
  topicByMarket = {
741
664
  'spot': 'ticketInfo',
742
- 'contract': 'user.execution.contractAccount',
743
- 'unified': 'user.execution.unifiedAccount',
665
+ 'unified': 'execution',
666
+ 'usdc': 'user.openapi.perp.trade',
744
667
  }
745
668
  topic = self.safe_value(topicByMarket, self.get_private_type(url))
746
669
  trades = await self.watch_topics(url, messageHash, [topic], params)
@@ -750,6 +673,7 @@ class bybit(Exchange, ccxt.async_support.bybit):
750
673
 
751
674
  def handle_my_trades(self, client, message):
752
675
  #
676
+ # spot
753
677
  # {
754
678
  # "type": "snapshot",
755
679
  # "topic": "ticketInfo",
@@ -773,39 +697,48 @@ class bybit(Exchange, ccxt.async_support.bybit):
773
697
  # }
774
698
  # ]
775
699
  # }
776
- # contract
700
+ # unified
777
701
  # {
778
- # topic: 'user.execution.contractAccount',
779
- # data: [
780
- # {
781
- # symbol: 'BTCUSD',
782
- # execFee: '0.00000004',
783
- # execId: '7d0f66da-8312-52a9-959b-9fba58a90af0',
784
- # execPrice: '17228.00',
785
- # execQty: '1',
786
- # execType: 'Trade',
787
- # execValue: '0.00005804',
788
- # feeRate: '0.0006',
789
- # lastLiquidityInd: 'RemovedLiquidity',
790
- # leavesQty: '0',
791
- # orderId: '6111f83d-2c8c-463a-b9a8-77885eae2f57',
792
- # orderLinkId: '',
793
- # orderPrice: '17744.50',
794
- # orderQty: '1',
795
- # orderType: 'Market',
796
- # stopOrderType: 'UNKNOWN',
797
- # side: 'Buy',
798
- # execTime: '1670210101997',
799
- # closedSize: '0'
800
- # }
702
+ # "id": "592324803b2785-26fa-4214-9963-bdd4727f07be",
703
+ # "topic": "execution",
704
+ # "creationTime": 1672364174455,
705
+ # "data": [
706
+ # {
707
+ # "category": "linear",
708
+ # "symbol": "XRPUSDT",
709
+ # "execFee": "0.005061",
710
+ # "execId": "7e2ae69c-4edf-5800-a352-893d52b446aa",
711
+ # "execPrice": "0.3374",
712
+ # "execQty": "25",
713
+ # "execType": "Trade",
714
+ # "execValue": "8.435",
715
+ # "isMaker": False,
716
+ # "feeRate": "0.0006",
717
+ # "tradeIv": "",
718
+ # "markIv": "",
719
+ # "blockTradeId": "",
720
+ # "markPrice": "0.3391",
721
+ # "indexPrice": "",
722
+ # "underlyingPrice": "",
723
+ # "leavesQty": "0",
724
+ # "orderId": "f6e324ff-99c2-4e89-9739-3086e47f9381",
725
+ # "orderLinkId": "",
726
+ # "orderPrice": "0.3207",
727
+ # "orderQty": "25",
728
+ # "orderType": "Market",
729
+ # "stopOrderType": "UNKNOWN",
730
+ # "side": "Sell",
731
+ # "execTime": "1672364174443",
732
+ # "isLeverage": "0"
733
+ # }
801
734
  # ]
802
735
  # }
803
736
  #
804
737
  topic = self.safe_string(message, 'topic')
805
738
  spot = topic == 'ticketInfo'
806
739
  data = self.safe_value(message, 'data', [])
807
- # unified margin
808
- data = self.safe_value(data, 'result', data)
740
+ if not isinstance(data, list):
741
+ data = self.safe_value(data, 'result', [])
809
742
  if self.myTrades is None:
810
743
  limit = self.safe_integer(self.options, 'tradesLimit', 1000)
811
744
  self.myTrades = ArrayCacheBySymbolById(limit)
@@ -829,6 +762,7 @@ class bybit(Exchange, ccxt.async_support.bybit):
829
762
  async def watch_orders(self, symbol=None, since=None, limit=None, params={}):
830
763
  """
831
764
  watches information on multiple orders made by the user
765
+ see https://bybit-exchange.github.io/docs/v5/websocket/private/order
832
766
  :param str|None symbol: unified market symbol of the market orders were made in
833
767
  :param int|None since: the earliest time in ms to fetch orders for
834
768
  :param int|None limit: the maximum number of orde structures to retrieve
@@ -841,14 +775,12 @@ class bybit(Exchange, ccxt.async_support.bybit):
841
775
  if symbol is not None:
842
776
  symbol = self.symbol(symbol)
843
777
  messageHash += ':' + symbol
844
- unified = await self.isUnifiedEnabled()
845
- isUnifiedMargin = self.safe_value(unified, 0, False)
846
- url = self.get_url_by_market_type(None, True, isUnifiedMargin, method, params)
778
+ url = self.get_url_by_market_type(symbol, True, method, params)
847
779
  await self.authenticate(url)
848
780
  topicsByMarket = {
849
781
  'spot': ['order', 'stopOrder'],
850
- 'contract': ['user.order.contractAccount'],
851
- 'unified': ['user.order.unifiedAccount'],
782
+ 'unified': ['order'],
783
+ 'usdc': ['user.openapi.perp.order'],
852
784
  }
853
785
  topics = self.safe_value(topicsByMarket, self.get_private_type(url))
854
786
  orders = await self.watch_topics(url, messageHash, topics, params)
@@ -895,91 +827,59 @@ class bybit(Exchange, ccxt.async_support.bybit):
895
827
  # }
896
828
  # ]
897
829
  # }
898
- # contract
830
+ # unified
899
831
  # {
900
- # "topic": "user.order.contractAccount",
832
+ # "id": "5923240c6880ab-c59f-420b-9adb-3639adc9dd90",
833
+ # "topic": "order",
834
+ # "creationTime": 1672364262474,
901
835
  # "data": [
902
836
  # {
903
- # "symbol": "BTCUSD",
904
- # "orderId": "ee013d82-fafc-4504-97b1-d92aca21eedd",
905
- # "side": "Buy",
837
+ # "symbol": "ETH-30DEC22-1400-C",
838
+ # "orderId": "5cf98598-39a7-459e-97bf-76ca765ee020",
839
+ # "side": "Sell",
906
840
  # "orderType": "Market",
907
- # "stopOrderType": "UNKNOWN",
908
- # "price": "21920.00",
909
- # "qty": "200",
910
- # "timeInForce": "ImmediateOrCancel",
841
+ # "cancelType": "UNKNOWN",
842
+ # "price": "72.5",
843
+ # "qty": "1",
844
+ # "orderIv": "",
845
+ # "timeInForce": "IOC",
911
846
  # "orderStatus": "Filled",
912
- # "triggerPrice": "0.00",
913
- # "orderLinkId": "inv001",
914
- # "createdTime": "1661338622771",
915
- # "updatedTime": "1661338622775",
916
- # "takeProfit": "0.00",
917
- # "stopLoss": "0.00",
918
- # "tpTriggerBy": "UNKNOWN",
919
- # "slTriggerBy": "UNKNOWN",
920
- # "triggerBy": "UNKNOWN",
847
+ # "orderLinkId": "",
848
+ # "lastPriceOnCreated": "",
921
849
  # "reduceOnly": False,
922
- # "closeOnTrigger": False,
850
+ # "leavesQty": "",
851
+ # "leavesValue": "",
852
+ # "cumExecQty": "1",
853
+ # "cumExecValue": "75",
854
+ # "avgPrice": "75",
855
+ # "blockTradeId": "",
856
+ # "positionIdx": 0,
857
+ # "cumExecFee": "0.358635",
858
+ # "createdTime": "1672364262444",
859
+ # "updatedTime": "1672364262457",
860
+ # "rejectReason": "EC_NoError",
861
+ # "stopOrderType": "",
862
+ # "triggerPrice": "",
863
+ # "takeProfit": "",
864
+ # "stopLoss": "",
865
+ # "tpTriggerBy": "",
866
+ # "slTriggerBy": "",
923
867
  # "triggerDirection": 0,
924
- # "leavesQty": "0",
925
- # "lastExecQty": "200",
926
- # "lastExecPrice": "21282.00",
927
- # "cumExecQty": "200",
928
- # "cumExecValue": "0.00939761"
868
+ # "triggerBy": "",
869
+ # "closeOnTrigger": False,
870
+ # "category": "option"
929
871
  # }
930
872
  # ]
931
873
  # }
932
- # unified
933
- # {
934
- # "id": "f91080af-5187-4261-a802-7604419771aa",
935
- # "topic": "user.order.unifiedAccount",
936
- # "ts": 1661932033707,
937
- # "data": {
938
- # "result": [
939
- # {
940
- # "orderId": "415f8961-4073-4d74-bc3e-df2830e52843",
941
- # "orderLinkId": "",
942
- # "symbol": "BTCUSDT",
943
- # "side": "Buy",
944
- # "orderType": "Limit",
945
- # "price": "17000.00000000",
946
- # "qty": "0.0100",
947
- # "timeInForce": "GoodTillCancel",
948
- # "orderStatus": "New",
949
- # "cumExecQty": "0.0000",
950
- # "cumExecValue": "0.00000000",
951
- # "cumExecFee": "0.00000000",
952
- # "stopOrderType": "UNKNOWN",
953
- # "triggerBy": "UNKNOWN",
954
- # "triggerPrice": "",
955
- # "reduceOnly": True,
956
- # "closeOnTrigger": True,
957
- # "createdTime": 1661932033636,
958
- # "updatedTime": 1661932033644,
959
- # "iv": "",
960
- # "orderIM": "",
961
- # "takeProfit": "",
962
- # "stopLoss": "",
963
- # "tpTriggerBy": "UNKNOWN",
964
- # "slTriggerBy": "UNKNOWN",
965
- # "basePrice": "",
966
- # "blockTradeId": "",
967
- # "leavesQty": "0.0100"
968
- # }
969
- # ],
970
- # "version": 284
971
- # },
972
- # "type": "snapshot"
973
- # }
974
874
  #
975
- topic = self.safe_string(message, 'topic', '')
875
+ type = self.safe_string(message, 'type', '')
976
876
  if self.orders is None:
977
877
  limit = self.safe_integer(self.options, 'ordersLimit', 1000)
978
878
  self.orders = ArrayCacheBySymbolById(limit)
979
879
  orders = self.orders
980
880
  rawOrders = []
981
881
  parser = None
982
- if topic == 'order':
882
+ if type == 'snapshot':
983
883
  rawOrders = self.safe_value(message, 'data', [])
984
884
  parser = 'parseWsSpotOrder'
985
885
  else:
@@ -1088,20 +988,47 @@ class bybit(Exchange, ccxt.async_support.bybit):
1088
988
  async def watch_balance(self, params={}):
1089
989
  """
1090
990
  query for balance and get the amount of funds available for trading or funds locked in orders
991
+ see https://bybit-exchange.github.io/docs/v5/websocket/private/wallet
1091
992
  :param dict params: extra parameters specific to the bybit api endpoint
1092
993
  :returns dict: a `balance structure <https://docs.ccxt.com/en/latest/manual.html?#balance-structure>`
1093
994
  """
995
+ await self.load_markets()
1094
996
  method = 'watchBalance'
1095
997
  messageHash = 'balances'
998
+ type = None
999
+ type, params = self.handle_market_type_and_params('watchBalance', None, params)
1000
+ subType = None
1001
+ subType, params = self.handle_sub_type_and_params('watchBalance', None, params)
1096
1002
  unified = await self.isUnifiedEnabled()
1097
1003
  isUnifiedMargin = self.safe_value(unified, 0, False)
1098
- url = self.get_url_by_market_type(None, True, isUnifiedMargin, method, params)
1004
+ isUnifiedAccount = self.safe_value(unified, 1, False)
1005
+ url = self.get_url_by_market_type(None, True, method, params)
1099
1006
  await self.authenticate(url)
1100
1007
  topicByMarket = {
1101
1008
  'spot': 'outboundAccountInfo',
1102
- 'contract': 'user.wallet.contractAccount',
1103
- 'unified': 'user.wallet.unifiedAccount',
1009
+ 'unified': 'wallet',
1104
1010
  }
1011
+ if isUnifiedAccount:
1012
+ # unified account
1013
+ if subType == 'inverse':
1014
+ messageHash += ':contract'
1015
+ else:
1016
+ messageHash += ':unified'
1017
+ if not isUnifiedMargin and not isUnifiedAccount:
1018
+ # normal account using v5
1019
+ if type == 'spot':
1020
+ messageHash += ':spot'
1021
+ else:
1022
+ messageHash += ':contract'
1023
+ if isUnifiedMargin:
1024
+ # unified margin account using v5
1025
+ if type == 'spot':
1026
+ messageHash += ':spot'
1027
+ else:
1028
+ if subType == 'linear':
1029
+ messageHash += ':unified'
1030
+ else:
1031
+ messageHash += ':contract'
1105
1032
  topics = [self.safe_value(topicByMarket, self.get_private_type(url))]
1106
1033
  return await self.watch_topics(url, messageHash, topics, params)
1107
1034
 
@@ -1129,59 +1056,124 @@ class bybit(Exchange, ccxt.async_support.bybit):
1129
1056
  # }
1130
1057
  # ]
1131
1058
  # }
1132
- # contract
1133
- # {
1134
- # "topic": "user.wallet.contractAccount",
1135
- # "data": [
1136
- # {
1137
- # "coin": "USDT",
1138
- # "equity": "610.3984319",
1139
- # "walletBalance": "609.7384319",
1140
- # "positionMargin": "4.7582882",
1141
- # "availableBalance": "604.9801437",
1142
- # "orderMargin": "0",
1143
- # "unrealisedPnl": "0.66",
1144
- # "cumRealisedPnl": "-0.2615681"
1145
- # }
1146
- # ]
1147
- # }
1148
1059
  # unified
1149
- # {
1150
- # "id": "46bd0430-1d03-48f7-a503-c6c020d07536",
1151
- # "topic": "user.wallet.unifiedAccount",
1152
- # "ts": 1649150852199,
1153
- # "data": {
1154
- # "result": {
1155
- # "accountIMRate": "0.0002",
1156
- # "accountMMRate": "0.0000",
1157
- # "totalEquity": "510444.50000000",
1158
- # "totalWalletBalance": "510444.50000000",
1159
- # "totalMarginBalance": "510444.50000000",
1160
- # "totalAvailableBalance": "510333.52491801",
1161
- # "totalPerpUPL": "0.00000000",
1162
- # "totalInitialMargin": "110.97508199",
1163
- # "totalMaintenanceMargin": "9.13733489",
1164
- # "coin": [{
1165
- # "currencyCoin": "USDC",
1166
- # "equity": "0.00000000",
1167
- # "usdValue": "0.00000000",
1168
- # "walletBalance": "0.00000000",
1169
- # "marginBalance": "510444.50000000",
1170
- # "availableBalance": "510333.52491801",
1171
- # "marginBalanceWithoutConvert": "0.00000000",
1172
- # "availableBalanceWithoutConvert": "0.00000000",
1173
- # "borrowSize": "0.00000000",
1174
- # "availableToBorrow": "200000.00000000",
1175
- # "accruedInterest": "0.00000000",
1176
- # "totalOrderIM": "0.00000000",
1177
- # "totalPositionIM": "0.00000000",
1178
- # "totalPositionMM": "0.00000000"
1179
- # }]
1180
- # },
1181
- # "version": 19
1182
- # },
1183
- # "type": "snapshot"
1184
- # }
1060
+ # {
1061
+ # "id": "5923242c464be9-25ca-483d-a743-c60101fc656f",
1062
+ # "topic": "wallet",
1063
+ # "creationTime": 1672364262482,
1064
+ # "data": [
1065
+ # {
1066
+ # "accountIMRate": "0.016",
1067
+ # "accountMMRate": "0.003",
1068
+ # "totalEquity": "12837.78330098",
1069
+ # "totalWalletBalance": "12840.4045924",
1070
+ # "totalMarginBalance": "12837.78330188",
1071
+ # "totalAvailableBalance": "12632.05767702",
1072
+ # "totalPerpUPL": "-2.62129051",
1073
+ # "totalInitialMargin": "205.72562486",
1074
+ # "totalMaintenanceMargin": "39.42876721",
1075
+ # "coin": [
1076
+ # {
1077
+ # "coin": "USDC",
1078
+ # "equity": "200.62572554",
1079
+ # "usdValue": "200.62572554",
1080
+ # "walletBalance": "201.34882644",
1081
+ # "availableToWithdraw": "0",
1082
+ # "availableToBorrow": "1500000",
1083
+ # "borrowAmount": "0",
1084
+ # "accruedInterest": "0",
1085
+ # "totalOrderIM": "0",
1086
+ # "totalPositionIM": "202.99874213",
1087
+ # "totalPositionMM": "39.14289747",
1088
+ # "unrealisedPnl": "74.2768991",
1089
+ # "cumRealisedPnl": "-209.1544627",
1090
+ # "bonus": "0"
1091
+ # },
1092
+ # {
1093
+ # "coin": "BTC",
1094
+ # "equity": "0.06488393",
1095
+ # "usdValue": "1023.08402268",
1096
+ # "walletBalance": "0.06488393",
1097
+ # "availableToWithdraw": "0.06488393",
1098
+ # "availableToBorrow": "2.5",
1099
+ # "borrowAmount": "0",
1100
+ # "accruedInterest": "0",
1101
+ # "totalOrderIM": "0",
1102
+ # "totalPositionIM": "0",
1103
+ # "totalPositionMM": "0",
1104
+ # "unrealisedPnl": "0",
1105
+ # "cumRealisedPnl": "0",
1106
+ # "bonus": "0"
1107
+ # },
1108
+ # {
1109
+ # "coin": "ETH",
1110
+ # "equity": "0",
1111
+ # "usdValue": "0",
1112
+ # "walletBalance": "0",
1113
+ # "availableToWithdraw": "0",
1114
+ # "availableToBorrow": "26",
1115
+ # "borrowAmount": "0",
1116
+ # "accruedInterest": "0",
1117
+ # "totalOrderIM": "0",
1118
+ # "totalPositionIM": "0",
1119
+ # "totalPositionMM": "0",
1120
+ # "unrealisedPnl": "0",
1121
+ # "cumRealisedPnl": "0",
1122
+ # "bonus": "0"
1123
+ # },
1124
+ # {
1125
+ # "coin": "USDT",
1126
+ # "equity": "11726.64664904",
1127
+ # "usdValue": "11613.58597018",
1128
+ # "walletBalance": "11728.54414904",
1129
+ # "availableToWithdraw": "11723.92075829",
1130
+ # "availableToBorrow": "2500000",
1131
+ # "borrowAmount": "0",
1132
+ # "accruedInterest": "0",
1133
+ # "totalOrderIM": "0",
1134
+ # "totalPositionIM": "2.72589075",
1135
+ # "totalPositionMM": "0.28576575",
1136
+ # "unrealisedPnl": "-1.8975",
1137
+ # "cumRealisedPnl": "0.64782276",
1138
+ # "bonus": "0"
1139
+ # },
1140
+ # {
1141
+ # "coin": "EOS3L",
1142
+ # "equity": "215.0570412",
1143
+ # "usdValue": "0",
1144
+ # "walletBalance": "215.0570412",
1145
+ # "availableToWithdraw": "215.0570412",
1146
+ # "availableToBorrow": "0",
1147
+ # "borrowAmount": "0",
1148
+ # "accruedInterest": "",
1149
+ # "totalOrderIM": "0",
1150
+ # "totalPositionIM": "0",
1151
+ # "totalPositionMM": "0",
1152
+ # "unrealisedPnl": "0",
1153
+ # "cumRealisedPnl": "0",
1154
+ # "bonus": "0"
1155
+ # },
1156
+ # {
1157
+ # "coin": "BIT",
1158
+ # "equity": "1.82",
1159
+ # "usdValue": "0.48758257",
1160
+ # "walletBalance": "1.82",
1161
+ # "availableToWithdraw": "1.82",
1162
+ # "availableToBorrow": "0",
1163
+ # "borrowAmount": "0",
1164
+ # "accruedInterest": "",
1165
+ # "totalOrderIM": "0",
1166
+ # "totalPositionIM": "0",
1167
+ # "totalPositionMM": "0",
1168
+ # "unrealisedPnl": "0",
1169
+ # "cumRealisedPnl": "0",
1170
+ # "bonus": "0"
1171
+ # }
1172
+ # ],
1173
+ # "accountType": "UNIFIED"
1174
+ # }
1175
+ # ]
1176
+ # }
1185
1177
  #
1186
1178
  if self.balance is None:
1187
1179
  self.balance = {}
@@ -1189,31 +1181,43 @@ class bybit(Exchange, ccxt.async_support.bybit):
1189
1181
  topic = self.safe_value(message, 'topic')
1190
1182
  info = None
1191
1183
  rawBalances = []
1184
+ account = None
1192
1185
  if topic == 'outboundAccountInfo':
1186
+ account = 'spot'
1193
1187
  data = self.safe_value(message, 'data', [])
1194
1188
  for i in range(0, len(data)):
1195
1189
  B = self.safe_value(data[i], 'B', [])
1196
1190
  rawBalances = self.array_concat(rawBalances, B)
1197
1191
  info = rawBalances
1198
- if topic == 'user.wallet.contractAccount':
1199
- rawBalances = self.safe_value(message, 'data', [])
1200
- info = rawBalances
1201
- if topic == 'user.wallet.unifiedAccount':
1192
+ if topic == 'wallet':
1202
1193
  data = self.safe_value(message, 'data', {})
1203
- result = self.safe_value(data, 'result', {})
1204
- rawBalances = self.safe_value(result, 'coin', [])
1205
- info = result
1194
+ for i in range(0, len(data)):
1195
+ result = self.safe_value(data, 0, {})
1196
+ account = self.safe_string_lower(result, 'accountType')
1197
+ rawBalances = self.array_concat(rawBalances, self.safe_value(result, 'coin', []))
1198
+ info = data
1206
1199
  for i in range(0, len(rawBalances)):
1207
- self.parse_ws_balance(rawBalances[i])
1208
- self.balance['info'] = info
1209
- timestamp = self.safe_integer(message, 'ts')
1210
- self.balance['timestamp'] = timestamp
1211
- self.balance['datetime'] = self.iso8601(timestamp)
1212
- self.balance = self.safe_balance(self.balance)
1213
- messageHash = 'balances'
1214
- client.resolve(self.balance, messageHash)
1200
+ self.parse_ws_balance(rawBalances[i], account)
1201
+ if account is not None:
1202
+ if self.safe_value(self.balance, account) is None:
1203
+ self.balance[account] = {}
1204
+ self.balance[account]['info'] = info
1205
+ timestamp = self.safe_integer(message, 'ts')
1206
+ self.balance[account]['timestamp'] = timestamp
1207
+ self.balance[account]['datetime'] = self.iso8601(timestamp)
1208
+ self.balance[account] = self.safe_balance(self.balance[account])
1209
+ messageHash = 'balances:' + account
1210
+ client.resolve(self.balance[account], messageHash)
1211
+ else:
1212
+ self.balance['info'] = info
1213
+ timestamp = self.safe_integer(message, 'ts')
1214
+ self.balance['timestamp'] = timestamp
1215
+ self.balance['datetime'] = self.iso8601(timestamp)
1216
+ self.balance = self.safe_balance(self.balance)
1217
+ messageHash = 'balances'
1218
+ client.resolve(self.balance, messageHash)
1215
1219
 
1216
- def parse_ws_balance(self, balance):
1220
+ def parse_ws_balance(self, balance, accountType=None):
1217
1221
  #
1218
1222
  # spot
1219
1223
  # {
@@ -1221,42 +1225,36 @@ class bybit(Exchange, ccxt.async_support.bybit):
1221
1225
  # "f": "176.81254174",
1222
1226
  # "l": "201.575"
1223
1227
  # }
1224
- # contract
1225
- # {
1226
- # "coin": "USDT",
1227
- # "equity": "610.3984319",
1228
- # "walletBalance": "609.7384319",
1229
- # "positionMargin": "4.7582882",
1230
- # "availableBalance": "604.9801437",
1231
- # "orderMargin": "0",
1232
- # "unrealisedPnl": "0.66",
1233
- # "cumRealisedPnl": "-0.2615681"
1234
- # }
1235
1228
  # unified
1236
- # {
1237
- # "currencyCoin": "USDC",
1238
- # "equity": "0.00000000",
1239
- # "usdValue": "0.00000000",
1240
- # "walletBalance": "0.00000000",
1241
- # "marginBalance": "510444.50000000",
1242
- # "availableBalance": "510333.52491801",
1243
- # "marginBalanceWithoutConvert": "0.00000000",
1244
- # "availableBalanceWithoutConvert": "0.00000000",
1245
- # "borrowSize": "0.00000000",
1246
- # "availableToBorrow": "200000.00000000",
1247
- # "accruedInterest": "0.00000000",
1248
- # "totalOrderIM": "0.00000000",
1249
- # "totalPositionIM": "0.00000000",
1250
- # "totalPositionMM": "0.00000000"
1251
- # }
1229
+ # {
1230
+ # "coin": "BTC",
1231
+ # "equity": "0.06488393",
1232
+ # "usdValue": "1023.08402268",
1233
+ # "walletBalance": "0.06488393",
1234
+ # "availableToWithdraw": "0.06488393",
1235
+ # "availableToBorrow": "2.5",
1236
+ # "borrowAmount": "0",
1237
+ # "accruedInterest": "0",
1238
+ # "totalOrderIM": "0",
1239
+ # "totalPositionIM": "0",
1240
+ # "totalPositionMM": "0",
1241
+ # "unrealisedPnl": "0",
1242
+ # "cumRealisedPnl": "0",
1243
+ # "bonus": "0"
1244
+ # }
1252
1245
  #
1253
1246
  account = self.account()
1254
- currencyId = self.safe_string_n(balance, ['a', 'currencyCoin', 'coin'])
1247
+ currencyId = self.safe_string_2(balance, 'a', 'coin')
1255
1248
  code = self.safe_currency_code(currencyId)
1256
- account['free'] = self.safe_string_n(balance, ['availableBalanceWithoutConvert', 'availableBalance', 'f'])
1257
- account['used'] = self.safe_string(balance, 'l')
1249
+ account['free'] = self.safe_string_n(balance, ['availableToWithdraw', 'f', 'free', 'availableToWithdraw'])
1250
+ account['used'] = self.safe_string_2(balance, 'l', 'locked')
1258
1251
  account['total'] = self.safe_string(balance, 'walletBalance')
1259
- self.balance[code] = account
1252
+ if accountType is not None:
1253
+ if self.safe_value(self.balance, accountType) is None:
1254
+ self.balance[accountType] = {}
1255
+ self.balance[accountType][code] = account
1256
+ else:
1257
+ self.balance[code] = account
1260
1258
 
1261
1259
  async def watch_topics(self, url, messageHash, topics=[], params={}):
1262
1260
  request = {
@@ -1376,7 +1374,12 @@ class bybit(Exchange, ccxt.async_support.bybit):
1376
1374
  'outboundAccountInfo': self.handle_balance,
1377
1375
  'execution': self.handle_my_trades,
1378
1376
  'ticketInfo': self.handle_my_trades,
1377
+ 'user.openapi.perp.trade': self.handle_my_trades,
1379
1378
  }
1379
+ exacMethod = self.safe_value(methods, topic)
1380
+ if exacMethod is not None:
1381
+ exacMethod(client, message)
1382
+ return
1380
1383
  keys = list(methods.keys())
1381
1384
  for i in range(0, len(keys)):
1382
1385
  key = keys[i]