ccxt 4.2.89__py2.py3-none-any.whl → 4.2.90__py2.py3-none-any.whl

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

Potentially problematic release.


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

Files changed (64) hide show
  1. ccxt/__init__.py +1 -1
  2. ccxt/abstract/bingx.py +2 -0
  3. ccxt/abstract/bybit.py +2 -0
  4. ccxt/ascendex.py +1 -0
  5. ccxt/async_support/__init__.py +1 -1
  6. ccxt/async_support/ascendex.py +1 -0
  7. ccxt/async_support/base/exchange.py +13 -1
  8. ccxt/async_support/binance.py +81 -9
  9. ccxt/async_support/bingx.py +94 -1
  10. ccxt/async_support/bitfinex2.py +1 -0
  11. ccxt/async_support/bitget.py +2 -0
  12. ccxt/async_support/bitmex.py +1 -0
  13. ccxt/async_support/bitrue.py +1 -0
  14. ccxt/async_support/bybit.py +50 -0
  15. ccxt/async_support/coinbase.py +40 -20
  16. ccxt/async_support/coinbaseinternational.py +1 -0
  17. ccxt/async_support/coinex.py +96 -8
  18. ccxt/async_support/cryptocom.py +1 -0
  19. ccxt/async_support/delta.py +1 -0
  20. ccxt/async_support/digifinex.py +1 -0
  21. ccxt/async_support/exmo.py +1 -0
  22. ccxt/async_support/gate.py +2 -0
  23. ccxt/async_support/gemini.py +10 -9
  24. ccxt/async_support/hitbtc.py +1 -0
  25. ccxt/async_support/htx.py +1 -0
  26. ccxt/async_support/hyperliquid.py +1 -0
  27. ccxt/async_support/kucoin.py +1 -0
  28. ccxt/async_support/kucoinfutures.py +32 -4
  29. ccxt/async_support/mexc.py +1 -0
  30. ccxt/async_support/okx.py +143 -8
  31. ccxt/async_support/phemex.py +1 -0
  32. ccxt/async_support/woo.py +1 -0
  33. ccxt/base/exchange.py +52 -6
  34. ccxt/base/types.py +1 -0
  35. ccxt/binance.py +81 -9
  36. ccxt/bingx.py +94 -1
  37. ccxt/bitfinex2.py +1 -0
  38. ccxt/bitget.py +2 -0
  39. ccxt/bitmex.py +1 -0
  40. ccxt/bitrue.py +1 -0
  41. ccxt/bybit.py +50 -0
  42. ccxt/coinbase.py +40 -20
  43. ccxt/coinbaseinternational.py +1 -0
  44. ccxt/coinex.py +96 -8
  45. ccxt/cryptocom.py +1 -0
  46. ccxt/delta.py +1 -0
  47. ccxt/digifinex.py +1 -0
  48. ccxt/exmo.py +1 -0
  49. ccxt/gate.py +2 -0
  50. ccxt/gemini.py +10 -9
  51. ccxt/hitbtc.py +1 -0
  52. ccxt/htx.py +1 -0
  53. ccxt/hyperliquid.py +1 -0
  54. ccxt/kucoin.py +1 -0
  55. ccxt/kucoinfutures.py +32 -4
  56. ccxt/mexc.py +1 -0
  57. ccxt/okx.py +143 -8
  58. ccxt/phemex.py +1 -0
  59. ccxt/pro/__init__.py +1 -1
  60. ccxt/woo.py +1 -0
  61. {ccxt-4.2.89.dist-info → ccxt-4.2.90.dist-info}/METADATA +4 -4
  62. {ccxt-4.2.89.dist-info → ccxt-4.2.90.dist-info}/RECORD +64 -64
  63. {ccxt-4.2.89.dist-info → ccxt-4.2.90.dist-info}/WHEEL +0 -0
  64. {ccxt-4.2.89.dist-info → ccxt-4.2.90.dist-info}/top_level.txt +0 -0
ccxt/base/exchange.py CHANGED
@@ -4,7 +4,7 @@
4
4
 
5
5
  # -----------------------------------------------------------------------------
6
6
 
7
- __version__ = '4.2.89'
7
+ __version__ = '4.2.90'
8
8
 
9
9
  # -----------------------------------------------------------------------------
10
10
 
@@ -39,6 +39,7 @@ from ccxt.base.types import BalanceAccount, Currency, IndexType, OrderSide, Orde
39
39
  from cryptography.hazmat import backends
40
40
  from cryptography.hazmat.primitives import hashes
41
41
  from cryptography.hazmat.primitives.asymmetric import padding
42
+ # from cryptography.hazmat.primitives.asymmetric.utils import decode_dss_signature
42
43
  from cryptography.hazmat.primitives.serialization import load_pem_private_key
43
44
 
44
45
  # -----------------------------------------------------------------------------
@@ -1314,22 +1315,33 @@ class Exchange(object):
1314
1315
  return Exchange.decode(base64.b64decode(s))
1315
1316
 
1316
1317
  @staticmethod
1317
- def jwt(request, secret, algorithm='sha256', is_rsa=False):
1318
+ def jwt(request, secret, algorithm='sha256', is_rsa=False, opts={}):
1318
1319
  algos = {
1319
1320
  'sha256': hashlib.sha256,
1320
1321
  'sha384': hashlib.sha384,
1321
1322
  'sha512': hashlib.sha512,
1322
1323
  }
1323
1324
  alg = ('RS' if is_rsa else 'HS') + algorithm[3:]
1324
- header = Exchange.encode(Exchange.json({
1325
+ if 'alg' in opts and opts['alg'] is not None:
1326
+ alg = opts['alg']
1327
+ header_opts = {
1325
1328
  'alg': alg,
1326
1329
  'typ': 'JWT',
1327
- }))
1330
+ }
1331
+ if 'kid' in opts and opts['kid'] is not None:
1332
+ header_opts['kid'] = opts['kid']
1333
+ if 'nonce' in opts and opts['nonce'] is not None:
1334
+ header_opts['nonce'] = opts['nonce']
1335
+ header = Exchange.encode(Exchange.json(header_opts))
1328
1336
  encoded_header = Exchange.base64urlencode(header)
1329
1337
  encoded_data = Exchange.base64urlencode(Exchange.encode(Exchange.json(request)))
1330
1338
  token = encoded_header + '.' + encoded_data
1331
- if is_rsa:
1339
+ algoType = alg[0:2]
1340
+ if is_rsa or algoType == 'RS':
1332
1341
  signature = Exchange.base64_to_binary(Exchange.rsa(token, Exchange.decode(secret), algorithm))
1342
+ elif algoType == 'ES':
1343
+ rawSignature = Exchange.ecdsa(token, secret, 'p256', algorithm)
1344
+ signature = Exchange.base16_to_binary(rawSignature['r'] + rawSignature['s'])
1333
1345
  else:
1334
1346
  signature = Exchange.hmac(Exchange.encode(token), secret, algos[algorithm], 'binary')
1335
1347
  return token + '.' + Exchange.base64urlencode(signature)
@@ -1362,6 +1374,10 @@ class Exchange(object):
1362
1374
  def int_to_base16(num):
1363
1375
  return "%0.2X" % num
1364
1376
 
1377
+ @staticmethod
1378
+ def random_bytes(length):
1379
+ return format(random.getrandbits(length * 8), 'x')
1380
+
1365
1381
  @staticmethod
1366
1382
  def ecdsa(request, secret, algorithm='p256', hash=None, fixed_length=False):
1367
1383
  # your welcome - frosty00
@@ -1382,7 +1398,12 @@ class Exchange(object):
1382
1398
  digest = Exchange.hash(encoded_request, hash, 'binary')
1383
1399
  else:
1384
1400
  digest = base64.b16decode(encoded_request, casefold=True)
1385
- key = ecdsa.SigningKey.from_string(base64.b16decode(Exchange.encode(secret),
1401
+ if isinstance(secret, str):
1402
+ secret = Exchange.encode(secret)
1403
+ if secret.find(b'-----BEGIN EC PRIVATE KEY-----') > -1:
1404
+ key = ecdsa.SigningKey.from_pem(secret, hash_function)
1405
+ else:
1406
+ key = ecdsa.SigningKey.from_string(base64.b16decode(secret,
1386
1407
  casefold=True), curve=curve_info[0])
1387
1408
  r_binary, s_binary, v = key.sign_digest_deterministic(digest, hashfunc=hash_function,
1388
1409
  sigencode=ecdsa.util.sigencode_strings_canonize)
@@ -2249,6 +2270,18 @@ class Exchange(object):
2249
2270
  def set_margin(self, symbol: str, amount: float, params={}):
2250
2271
  raise NotSupported(self.id + ' setMargin() is not supported yet')
2251
2272
 
2273
+ def fetch_margin_adjustment_history(self, symbol: Str = None, type: Str = None, since: Num = None, limit: Num = None, params={}):
2274
+ """
2275
+ fetches the history of margin added or reduced from contract isolated positions
2276
+ :param str [symbol]: unified market symbol
2277
+ :param str [type]: "add" or "reduce"
2278
+ :param int [since]: timestamp in ms of the earliest change to fetch
2279
+ :param int [limit]: the maximum amount of changes to fetch
2280
+ :param dict params: extra parameters specific to the exchange api endpoint
2281
+ :returns dict[]: a list of `margin structures <https://docs.ccxt.com/#/?id=margin-loan-structure>`
2282
+ """
2283
+ raise NotSupported(self.id + ' fetchMarginAdjustmentHistory() is not supported yet')
2284
+
2252
2285
  def set_margin_mode(self, marginMode: str, symbol: Str = None, params={}):
2253
2286
  raise NotSupported(self.id + ' setMarginMode() is not supported yet')
2254
2287
 
@@ -5447,3 +5480,16 @@ class Exchange(object):
5447
5480
  day = date[5:7]
5448
5481
  reconstructedDate = day + month + year
5449
5482
  return reconstructedDate
5483
+
5484
+ def parse_margin_modification(self, data, market: Market = None):
5485
+ raise NotSupported(self.id + ' parseMarginModification() is not supported yet')
5486
+
5487
+ def parse_margin_modifications(self, response: List[object], symbols: List[str] = None, symbolKey: Str = None, marketType: MarketType = None):
5488
+ marginModifications = []
5489
+ for i in range(0, len(response)):
5490
+ info = response[i]
5491
+ marketId = self.safe_string(info, symbolKey)
5492
+ market = self.safe_market(marketId, None, None, marketType)
5493
+ if (symbols is None) or self.in_array(market['symbol'], symbols):
5494
+ marginModifications.append(self.parse_margin_modification(info, market))
5495
+ return marginModifications
ccxt/base/types.py CHANGED
@@ -410,6 +410,7 @@ class MarginModification(TypedDict):
410
410
  info: Dict[str, any]
411
411
  symbol: str
412
412
  type: Optional[Literal['add', 'reduce', 'set']]
413
+ marginMode: Optional[Literal['isolated', 'cross']]
413
414
  amount: Optional[float]
414
415
  code: Str
415
416
  status: Str
ccxt/binance.py CHANGED
@@ -120,6 +120,7 @@ class binance(Exchange, ImplicitAPI):
120
120
  'fetchLeverages': True,
121
121
  'fetchLeverageTiers': True,
122
122
  'fetchLiquidations': False,
123
+ 'fetchMarginAdjustmentHistory': True,
123
124
  'fetchMarginMode': 'emulated',
124
125
  'fetchMarginModes': True,
125
126
  'fetchMarketLeverageTiers': 'emulated',
@@ -10329,21 +10330,37 @@ class binance(Exchange, ImplicitAPI):
10329
10330
  # "type": 1
10330
10331
  # }
10331
10332
  #
10333
+ # fetchMarginAdjustmentHistory
10334
+ #
10335
+ # {
10336
+ # symbol: "XRPUSDT",
10337
+ # type: "1",
10338
+ # deltaType: "TRADE",
10339
+ # amount: "2.57148240",
10340
+ # asset: "USDT",
10341
+ # time: "1711046271555",
10342
+ # positionSide: "BOTH",
10343
+ # clientTranId: ""
10344
+ # }
10345
+ #
10332
10346
  rawType = self.safe_integer(data, 'type')
10333
- resultType = 'add' if (rawType == 1) else 'reduce'
10334
- resultAmount = self.safe_number(data, 'amount')
10335
10347
  errorCode = self.safe_string(data, 'code')
10336
- status = 'ok' if (errorCode == '200') else 'failed'
10348
+ marketId = self.safe_string(data, 'symbol')
10349
+ timestamp = self.safe_integer(data, 'time')
10350
+ market = self.safe_market(marketId, market, None, 'swap')
10351
+ noErrorCode = errorCode is None
10352
+ success = errorCode == '200'
10337
10353
  return {
10338
10354
  'info': data,
10339
10355
  'symbol': market['symbol'],
10340
- 'type': resultType,
10341
- 'amount': resultAmount,
10356
+ 'type': 'add' if (rawType == 1) else 'reduce',
10357
+ 'marginMode': 'isolated',
10358
+ 'amount': self.safe_number(data, 'amount'),
10359
+ 'code': self.safe_string(data, 'asset'),
10342
10360
  'total': None,
10343
- 'code': None,
10344
- 'status': status,
10345
- 'timestamp': None,
10346
- 'datetime': None,
10361
+ 'status': 'ok' if (success or noErrorCode) else 'failed',
10362
+ 'timestamp': timestamp,
10363
+ 'datetime': self.iso8601(timestamp),
10347
10364
  }
10348
10365
 
10349
10366
  def reduce_margin(self, symbol: str, amount, params={}) -> MarginModification:
@@ -11505,3 +11522,58 @@ class binance(Exchange, ImplicitAPI):
11505
11522
  'baseVolume': self.safe_number(chain, 'volume'),
11506
11523
  'quoteVolume': None,
11507
11524
  }
11525
+
11526
+ def fetch_margin_adjustment_history(self, symbol: Str = None, type: Str = None, since: Num = None, limit: Num = None, params={}) -> List[MarginModification]:
11527
+ """
11528
+ fetches the history of margin added or reduced from contract isolated positions
11529
+ :see: https://binance-docs.github.io/apidocs/futures/en/#get-position-margin-change-history-trade
11530
+ :see: https://binance-docs.github.io/apidocs/delivery/en/#get-position-margin-change-history-trade
11531
+ :param str symbol: unified market symbol
11532
+ :param str [type]: "add" or "reduce"
11533
+ :param int [since]: timestamp in ms of the earliest change to fetch
11534
+ :param int [limit]: the maximum amount of changes to fetch
11535
+ :param dict params: extra parameters specific to the exchange api endpoint
11536
+ :param int [params.until]: timestamp in ms of the latest change to fetch
11537
+ :returns dict[]: a list of `margin structures <https://docs.ccxt.com/#/?id=margin-loan-structure>`
11538
+ """
11539
+ self.load_markets()
11540
+ if symbol is None:
11541
+ raise ArgumentsRequired(self.id + ' fetchMarginAdjustmentHistory() requires a symbol argument')
11542
+ market = self.market(symbol)
11543
+ until = self.safe_integer(params, 'until')
11544
+ params = self.omit(params, 'until')
11545
+ request = {
11546
+ 'symbol': market['id'],
11547
+ }
11548
+ if type is not None:
11549
+ request['type'] = 1 if (type == 'add') else 2
11550
+ if since is not None:
11551
+ request['startTime'] = since
11552
+ if limit is not None:
11553
+ request['limit'] = limit
11554
+ if until is not None:
11555
+ request['endTime'] = until
11556
+ response = None
11557
+ if market['linear']:
11558
+ response = self.fapiPrivateGetPositionMarginHistory(self.extend(request, params))
11559
+ elif market['inverse']:
11560
+ response = self.dapiPrivateGetPositionMarginHistory(self.extend(request, params))
11561
+ else:
11562
+ raise BadRequest(self.id + 'fetchMarginAdjustmentHistory() is not supported for markets of type ' + market['type'])
11563
+ #
11564
+ # [
11565
+ # {
11566
+ # symbol: "XRPUSDT",
11567
+ # type: "1",
11568
+ # deltaType: "TRADE",
11569
+ # amount: "2.57148240",
11570
+ # asset: "USDT",
11571
+ # time: "1711046271555",
11572
+ # positionSide: "BOTH",
11573
+ # clientTranId: ""
11574
+ # }
11575
+ # ...
11576
+ # ]
11577
+ #
11578
+ modifications = self.parse_margin_modifications(response)
11579
+ return self.filter_by_symbol_since_limit(modifications, symbol, since, limit)
ccxt/bingx.py CHANGED
@@ -73,6 +73,7 @@ class bingx(Exchange, ImplicitAPI):
73
73
  'fetchFundingRates': True,
74
74
  'fetchLeverage': True,
75
75
  'fetchLiquidations': False,
76
+ 'fetchMarginAdjustmentHistory': False,
76
77
  'fetchMarginMode': True,
77
78
  'fetchMarkets': True,
78
79
  'fetchMarkOHLCV': True,
@@ -82,6 +83,7 @@ class bingx(Exchange, ImplicitAPI):
82
83
  'fetchOpenOrders': True,
83
84
  'fetchOrder': True,
84
85
  'fetchOrderBook': True,
86
+ 'fetchOrders': True,
85
87
  'fetchPositionMode': True,
86
88
  'fetchPositions': True,
87
89
  'fetchTicker': True,
@@ -193,6 +195,7 @@ class bingx(Exchange, ImplicitAPI):
193
195
  'positionSide/dual': 1,
194
196
  'market/markPriceKlines': 1,
195
197
  'trade/batchCancelReplace': 1,
198
+ 'trade/fullOrder': 1,
196
199
  },
197
200
  'post': {
198
201
  'trade/cancelReplace': 1,
@@ -340,6 +343,7 @@ class bingx(Exchange, ImplicitAPI):
340
343
  'post': {
341
344
  'swap/trace/closeTrackOrder': 1,
342
345
  'swap/trace/setTPSL': 1,
346
+ 'spot/trader/sellOrder': 1,
343
347
  },
344
348
  },
345
349
  },
@@ -2586,7 +2590,7 @@ class bingx(Exchange, ImplicitAPI):
2586
2590
  :returns dict: An `order structure <https://docs.ccxt.com/#/?id=order-structure>`
2587
2591
  """
2588
2592
  if symbol is None:
2589
- raise ArgumentsRequired(self.id + ' fetchOrders() requires a symbol argument')
2593
+ raise ArgumentsRequired(self.id + ' fetchOrder() requires a symbol argument')
2590
2594
  self.load_markets()
2591
2595
  market = self.market(symbol)
2592
2596
  request: dict = {
@@ -2654,6 +2658,94 @@ class bingx(Exchange, ImplicitAPI):
2654
2658
  first = self.safe_dict(data, 'order', data)
2655
2659
  return self.parse_order(first, market)
2656
2660
 
2661
+ def fetch_orders(self, symbol: Str = None, since: Int = None, limit: Int = None, params={}) -> List[Order]:
2662
+ """
2663
+ fetches information on multiple orders made by the user
2664
+ :see: https://bingx-api.github.io/docs/#/en-us/swapV2/trade-api.html#User's%20All%20Orders
2665
+ :param str symbol: unified market symbol of the market orders were made in
2666
+ :param int [since]: the earliest time in ms to fetch orders for
2667
+ :param int [limit]: the maximum number of order structures to retrieve
2668
+ :param dict [params]: extra parameters specific to the exchange API endpoint
2669
+ :param int [params.until]: the latest time in ms to fetch entries for
2670
+ :param int [params.orderId]: Only return subsequent orders, and return the latest order by default
2671
+ :returns Order[]: a list of `order structures <https://docs.ccxt.com/#/?id=order-structure>`
2672
+ """
2673
+ self.load_markets()
2674
+ request = {}
2675
+ market = None
2676
+ if symbol is not None:
2677
+ market = self.market(symbol)
2678
+ request['symbol'] = market['id']
2679
+ type = None
2680
+ type, params = self.handle_market_type_and_params('fetchOrders', market, params)
2681
+ if type != 'swap':
2682
+ raise NotSupported(self.id + ' fetchOrders() is only supported for swap markets')
2683
+ if limit is not None:
2684
+ request['limit'] = limit
2685
+ if since is not None:
2686
+ request['startTime'] = since
2687
+ until = self.safe_integer_2(params, 'until', 'till') # unified in milliseconds
2688
+ endTime = self.safe_integer(params, 'endTime', until) # exchange-specific in milliseconds
2689
+ params = self.omit(params, ['endTime', 'till', 'until'])
2690
+ if endTime is not None:
2691
+ request['endTime'] = endTime
2692
+ response = self.swapV1PrivateGetTradeFullOrder(self.extend(request, params))
2693
+ #
2694
+ # {
2695
+ # "code": 0,
2696
+ # "msg": "",
2697
+ # "data": {
2698
+ # "orders": [
2699
+ # {
2700
+ # "symbol": "PYTH-USDT",
2701
+ # "orderId": 1736007506620112100,
2702
+ # "side": "SELL",
2703
+ # "positionSide": "SHORT",
2704
+ # "type": "LIMIT",
2705
+ # "origQty": "33",
2706
+ # "price": "0.3916",
2707
+ # "executedQty": "33",
2708
+ # "avgPrice": "0.3916",
2709
+ # "cumQuote": "13",
2710
+ # "stopPrice": "",
2711
+ # "profit": "0.0000",
2712
+ # "commission": "-0.002585",
2713
+ # "status": "FILLED",
2714
+ # "time": 1702731418000,
2715
+ # "updateTime": 1702731470000,
2716
+ # "clientOrderId": "",
2717
+ # "leverage": "15X",
2718
+ # "takeProfit": {
2719
+ # "type": "TAKE_PROFIT",
2720
+ # "quantity": 0,
2721
+ # "stopPrice": 0,
2722
+ # "price": 0,
2723
+ # "workingType": ""
2724
+ # },
2725
+ # "stopLoss": {
2726
+ # "type": "STOP",
2727
+ # "quantity": 0,
2728
+ # "stopPrice": 0,
2729
+ # "price": 0,
2730
+ # "workingType": ""
2731
+ # },
2732
+ # "advanceAttr": 0,
2733
+ # "positionID": 0,
2734
+ # "takeProfitEntrustPrice": 0,
2735
+ # "stopLossEntrustPrice": 0,
2736
+ # "orderType": "",
2737
+ # "workingType": "MARK_PRICE",
2738
+ # "stopGuaranteed": False,
2739
+ # "triggerOrderId": 1736012449498123500
2740
+ # }
2741
+ # ]
2742
+ # }
2743
+ # }
2744
+ #
2745
+ data = self.safe_dict(response, 'data', {})
2746
+ orders = self.safe_list(data, 'orders', [])
2747
+ return self.parse_orders(orders, market, since, limit)
2748
+
2657
2749
  def fetch_open_orders(self, symbol: Str = None, since: Int = None, limit: Int = None, params={}) -> List[Order]:
2658
2750
  """
2659
2751
  :see: https://bingx-api.github.io/docs/#/spot/trade-api.html#Query%20Open%20Orders
@@ -3306,6 +3398,7 @@ class bingx(Exchange, ImplicitAPI):
3306
3398
  'info': data,
3307
3399
  'symbol': self.safe_string(market, 'symbol'),
3308
3400
  'type': 'add' if (type == '1') else 'reduce',
3401
+ 'marginMode': 'isolated',
3309
3402
  'amount': self.safe_number(data, 'amount'),
3310
3403
  'total': self.safe_number(data, 'margin'),
3311
3404
  'code': self.safe_string(market, 'settle'),
ccxt/bitfinex2.py CHANGED
@@ -3325,6 +3325,7 @@ class bitfinex2(Exchange, ImplicitAPI):
3325
3325
  'info': data,
3326
3326
  'symbol': market['symbol'],
3327
3327
  'type': None,
3328
+ 'marginMode': 'isolated',
3328
3329
  'amount': None,
3329
3330
  'total': None,
3330
3331
  'code': None,
ccxt/bitget.py CHANGED
@@ -105,6 +105,7 @@ class bitget(Exchange, ImplicitAPI):
105
105
  'fetchLeverage': True,
106
106
  'fetchLeverageTiers': False,
107
107
  'fetchLiquidations': False,
108
+ 'fetchMarginAdjustmentHistory': False,
108
109
  'fetchMarginMode': True,
109
110
  'fetchMarketLeverageTiers': True,
110
111
  'fetchMarkets': True,
@@ -6485,6 +6486,7 @@ class bitget(Exchange, ImplicitAPI):
6485
6486
  'info': data,
6486
6487
  'symbol': market['symbol'],
6487
6488
  'type': None,
6489
+ 'marginMode': 'isolated',
6488
6490
  'amount': None,
6489
6491
  'total': None,
6490
6492
  'code': market['settle'],
ccxt/bitmex.py CHANGED
@@ -75,6 +75,7 @@ class bitmex(Exchange, ImplicitAPI):
75
75
  'fetchLeverages': True,
76
76
  'fetchLeverageTiers': False,
77
77
  'fetchLiquidations': True,
78
+ 'fetchMarginAdjustmentHistory': False,
78
79
  'fetchMarketLeverageTiers': False,
79
80
  'fetchMarkets': True,
80
81
  'fetchMarkOHLCV': False,
ccxt/bitrue.py CHANGED
@@ -2852,6 +2852,7 @@ class bitrue(Exchange, ImplicitAPI):
2852
2852
  'info': data,
2853
2853
  'symbol': market['symbol'],
2854
2854
  'type': None,
2855
+ 'marginMode': 'isolated',
2855
2856
  'amount': None,
2856
2857
  'total': None,
2857
2858
  'code': None,
ccxt/bybit.py CHANGED
@@ -95,6 +95,7 @@ class bybit(Exchange, ImplicitAPI):
95
95
  'fetchLedger': True,
96
96
  'fetchLeverage': True,
97
97
  'fetchLeverageTiers': True,
98
+ 'fetchMarginAdjustmentHistory': False,
98
99
  'fetchMarketLeverageTiers': True,
99
100
  'fetchMarkets': True,
100
101
  'fetchMarkOHLCV': True,
@@ -165,6 +166,13 @@ class bybit(Exchange, ImplicitAPI):
165
166
  'public': 'https://api.{hostname}',
166
167
  'private': 'https://api.{hostname}',
167
168
  },
169
+ 'demotrading': {
170
+ 'spot': 'https://api-demo.{hostname}',
171
+ 'futures': 'https://api-demo.{hostname}',
172
+ 'v2': 'https://api-demo.{hostname}',
173
+ 'public': 'https://api-demo.{hostname}',
174
+ 'private': 'https://api-demo.{hostname}',
175
+ },
168
176
  'www': 'https://www.bybit.com',
169
177
  'doc': [
170
178
  'https://bybit-exchange.github.io/docs/inverse/',
@@ -353,6 +361,7 @@ class bybit(Exchange, ImplicitAPI):
353
361
  'v5/user/get-member-type': 5,
354
362
  'v5/user/aff-customer-info': 5,
355
363
  'v5/user/del-submember': 5,
364
+ 'v5/user/submembers': 5,
356
365
  # spot leverage token
357
366
  'v5/spot-lever-token/order-record': 1, # 50/s => cost = 50 / 50 = 1
358
367
  # spot margin trade
@@ -513,6 +522,8 @@ class bybit(Exchange, ImplicitAPI):
513
522
  'v5/lending/redeem-cancel': 5,
514
523
  'v5/account/set-collateral-switch': 5,
515
524
  'v5/account/set-collateral-switch-batch': 5,
525
+ # demo trading
526
+ 'v5/account/demo-apply-money': 5,
516
527
  },
517
528
  },
518
529
  },
@@ -979,6 +990,8 @@ class bybit(Exchange, ImplicitAPI):
979
990
  },
980
991
  'precisionMode': TICK_SIZE,
981
992
  'options': {
993
+ 'sandboxMode': False,
994
+ 'enableDemoTrading': False,
982
995
  'fetchMarkets': ['spot', 'linear', 'inverse', 'option'],
983
996
  'createOrder': {
984
997
  'method': 'privatePostV5OrderCreate', # 'privatePostV5PositionTradingStop'
@@ -1062,6 +1075,32 @@ class bybit(Exchange, ImplicitAPI):
1062
1075
  },
1063
1076
  })
1064
1077
 
1078
+ def set_sandbox_mode(self, enable: bool):
1079
+ """
1080
+ enables or disables sandbox mode
1081
+ :param boolean [enable]: True if demo trading should be enabled, False otherwise
1082
+ """
1083
+ super(bybit, self).set_sandbox_mode(enable)
1084
+ self.options['sandboxMode'] = enable
1085
+
1086
+ def enable_demo_trading(self, enable: bool):
1087
+ """
1088
+ enables or disables demo trading mode
1089
+ :see: https://bybit-exchange.github.io/docs/v5/demo
1090
+ :param boolean [enable]: True if demo trading should be enabled, False otherwise
1091
+ """
1092
+ if self.options['sandboxMode']:
1093
+ raise NotSupported(self.id + ' demo trading does not support in sandbox environment')
1094
+ # enable demo trading in bybit, see: https://bybit-exchange.github.io/docs/v5/demo
1095
+ if enable:
1096
+ self.urls['apiBackupDemoTrading'] = self.urls['api']
1097
+ self.urls['api'] = self.urls['demotrading']
1098
+ elif 'apiBackupDemoTrading' in self.urls:
1099
+ self.urls['api'] = self.urls['apiBackupDemoTrading']
1100
+ newUrls = self.omit(self.urls, 'apiBackupDemoTrading')
1101
+ self.urls = newUrls
1102
+ self.options['enableDemoTrading'] = enable
1103
+
1065
1104
  def nonce(self):
1066
1105
  return self.milliseconds() - self.options['timeDifference']
1067
1106
 
@@ -1077,12 +1116,21 @@ class bybit(Exchange, ImplicitAPI):
1077
1116
  return data
1078
1117
 
1079
1118
  def is_unified_enabled(self, params={}):
1119
+ """
1120
+ returns [enableUnifiedMargin, enableUnifiedAccount] so the user can check if unified account is enabled
1121
+ """
1080
1122
  # The API key of user id must own one of permissions will be allowed to call following API endpoints.
1081
1123
  # SUB UID: "Account Transfer"
1082
1124
  # MASTER UID: "Account Transfer", "Subaccount Transfer", "Withdrawal"
1083
1125
  enableUnifiedMargin = self.safe_value(self.options, 'enableUnifiedMargin')
1084
1126
  enableUnifiedAccount = self.safe_value(self.options, 'enableUnifiedAccount')
1085
1127
  if enableUnifiedMargin is None or enableUnifiedAccount is None:
1128
+ if self.options['enableDemoTrading']:
1129
+ # info endpoint is not available in demo trading
1130
+ # so we're assuming UTA is enabled
1131
+ self.options['enableUnifiedMargin'] = False
1132
+ self.options['enableUnifiedAccount'] = True
1133
+ return [self.options['enableUnifiedMargin'], self.options['enableUnifiedAccount']]
1086
1134
  response = self.privateGetV5UserQueryApi(params)
1087
1135
  #
1088
1136
  # {
@@ -1242,6 +1290,8 @@ class bybit(Exchange, ImplicitAPI):
1242
1290
  """
1243
1291
  if not self.check_required_credentials(False):
1244
1292
  return None
1293
+ if self.options['enableDemoTrading']:
1294
+ return None
1245
1295
  response = self.privateGetV5AssetCoinQueryInfo(params)
1246
1296
  #
1247
1297
  # {
ccxt/coinbase.py CHANGED
@@ -3573,25 +3573,14 @@ class coinbase(Exchange, ImplicitAPI):
3573
3573
  url = self.urls['api']['rest'] + fullPath
3574
3574
  if signed:
3575
3575
  authorization = self.safe_string(self.headers, 'Authorization')
3576
+ authorizationString = None
3576
3577
  if authorization is not None:
3577
- headers = {
3578
- 'Authorization': authorization,
3579
- 'Content-Type': 'application/json',
3580
- }
3581
- if method != 'GET':
3582
- if query:
3583
- body = self.json(query)
3578
+ authorizationString = authorization
3584
3579
  elif self.token and not self.check_required_credentials(False):
3585
- headers = {
3586
- 'Authorization': 'Bearer ' + self.token,
3587
- 'Content-Type': 'application/json',
3588
- }
3589
- if method != 'GET':
3590
- if query:
3591
- body = self.json(query)
3580
+ authorizationString = 'Bearer ' + self.token
3592
3581
  else:
3593
3582
  self.check_required_credentials()
3594
- timestampString = str(self.seconds())
3583
+ seconds = self.seconds()
3595
3584
  payload = ''
3596
3585
  if method != 'GET':
3597
3586
  if query:
@@ -3605,14 +3594,45 @@ class coinbase(Exchange, ImplicitAPI):
3605
3594
  # https://docs.cloud.coinbase.com/advanced-trade-api/docs/auth#example-request
3606
3595
  # v2: 'GET' require payload in the signature
3607
3596
  # https://docs.cloud.coinbase.com/sign-in-with-coinbase/docs/api-key-authentication
3608
- auth = timestampString + method + savedPath + payload
3609
- signature = self.hmac(self.encode(auth), self.encode(self.secret), hashlib.sha256)
3597
+ isCloudAPiKey = (self.apiKey.find('organizations/') >= 0) or (self.secret.startswith('-----BEGIN'))
3598
+ if isCloudAPiKey:
3599
+ if self.apiKey.startswith('-----BEGIN'):
3600
+ raise ArgumentsRequired(self.id + ' apiKey should contain the name(eg: organizations/3b910e93....) and not the public key')
3601
+ # it may not work for v2
3602
+ uri = method + ' ' + url.replace('https://', '')
3603
+ quesPos = uri.find('?')
3604
+ if quesPos >= 0:
3605
+ uri = uri[0:quesPos]
3606
+ nonce = self.random_bytes(16)
3607
+ request = {
3608
+ 'aud': ['retail_rest_api_proxy'],
3609
+ 'iss': 'coinbase-cloud',
3610
+ 'nbf': seconds,
3611
+ 'exp': seconds + 120,
3612
+ 'sub': self.apiKey,
3613
+ 'uri': uri,
3614
+ 'iat': seconds,
3615
+ }
3616
+ token = self.jwt(request, self.encode(self.secret), 'sha256', False, {'kid': self.apiKey, 'nonce': nonce, 'alg': 'ES256'})
3617
+ authorizationString = 'Bearer ' + token
3618
+ else:
3619
+ timestampString = str(self.seconds())
3620
+ auth = timestampString + method + savedPath + payload
3621
+ signature = self.hmac(self.encode(auth), self.encode(self.secret), hashlib.sha256)
3622
+ headers = {
3623
+ 'CB-ACCESS-KEY': self.apiKey,
3624
+ 'CB-ACCESS-SIGN': signature,
3625
+ 'CB-ACCESS-TIMESTAMP': timestampString,
3626
+ 'Content-Type': 'application/json',
3627
+ }
3628
+ if authorizationString is not None:
3610
3629
  headers = {
3611
- 'CB-ACCESS-KEY': self.apiKey,
3612
- 'CB-ACCESS-SIGN': signature,
3613
- 'CB-ACCESS-TIMESTAMP': timestampString,
3630
+ 'Authorization': authorizationString,
3614
3631
  'Content-Type': 'application/json',
3615
3632
  }
3633
+ if method != 'GET':
3634
+ if query:
3635
+ body = self.json(query)
3616
3636
  return {'url': url, 'method': method, 'body': body, 'headers': headers}
3617
3637
 
3618
3638
  def handle_errors(self, code, reason, url, method, headers, body, response, requestHeaders, requestBody):
@@ -85,6 +85,7 @@ class coinbaseinternational(Exchange, ImplicitAPI):
85
85
  'fetchLedger': False,
86
86
  'fetchLeverage': False,
87
87
  'fetchLeverageTiers': False,
88
+ 'fetchMarginAdjustmentHistory': False,
88
89
  'fetchMarginMode': False,
89
90
  'fetchMarkets': True,
90
91
  'fetchMarkOHLCV': False,