ccxt 4.2.89__py2.py3-none-any.whl → 4.2.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 (71) 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 +43 -21
  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 +11 -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/kraken.py +11 -8
  28. ccxt/async_support/kucoin.py +1 -0
  29. ccxt/async_support/kucoinfutures.py +32 -4
  30. ccxt/async_support/mexc.py +1 -0
  31. ccxt/async_support/okx.py +173 -38
  32. ccxt/async_support/phemex.py +1 -0
  33. ccxt/async_support/woo.py +1 -0
  34. ccxt/base/exchange.py +53 -14
  35. ccxt/base/types.py +1 -0
  36. ccxt/binance.py +81 -9
  37. ccxt/bingx.py +94 -1
  38. ccxt/bitfinex2.py +1 -0
  39. ccxt/bitget.py +2 -0
  40. ccxt/bitmex.py +1 -0
  41. ccxt/bitrue.py +1 -0
  42. ccxt/bybit.py +50 -0
  43. ccxt/coinbase.py +43 -21
  44. ccxt/coinbaseinternational.py +1 -0
  45. ccxt/coinex.py +96 -8
  46. ccxt/cryptocom.py +1 -0
  47. ccxt/delta.py +1 -0
  48. ccxt/digifinex.py +1 -0
  49. ccxt/exmo.py +1 -0
  50. ccxt/gate.py +2 -0
  51. ccxt/gemini.py +11 -9
  52. ccxt/hitbtc.py +1 -0
  53. ccxt/htx.py +1 -0
  54. ccxt/hyperliquid.py +1 -0
  55. ccxt/kraken.py +11 -8
  56. ccxt/kucoin.py +1 -0
  57. ccxt/kucoinfutures.py +32 -4
  58. ccxt/mexc.py +1 -0
  59. ccxt/okx.py +173 -38
  60. ccxt/phemex.py +1 -0
  61. ccxt/pro/__init__.py +1 -1
  62. ccxt/pro/bitmex.py +35 -17
  63. ccxt/pro/kucoin.py +85 -0
  64. ccxt/pro/kucoinfutures.py +141 -76
  65. ccxt/test/test_async.py +15 -1
  66. ccxt/test/test_sync.py +15 -1
  67. ccxt/woo.py +1 -0
  68. {ccxt-4.2.89.dist-info → ccxt-4.2.91.dist-info}/METADATA +4 -4
  69. {ccxt-4.2.89.dist-info → ccxt-4.2.91.dist-info}/RECORD +71 -71
  70. {ccxt-4.2.89.dist-info → ccxt-4.2.91.dist-info}/WHEEL +0 -0
  71. {ccxt-4.2.89.dist-info → ccxt-4.2.91.dist-info}/top_level.txt +0 -0
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
@@ -337,6 +337,7 @@ class coinbase(Exchange, ImplicitAPI):
337
337
  'CGLD': 'CELO',
338
338
  },
339
339
  'options': {
340
+ 'brokerId': 'ccxt',
340
341
  'stablePairs': ['BUSD-USD', 'CBETH-ETH', 'DAI-USD', 'GUSD-USD', 'GYEN-USD', 'PAX-USD', 'PAX-USDT', 'USDC-EUR', 'USDC-GBP', 'USDT-EUR', 'USDT-GBP', 'USDT-USD', 'USDT-USDC', 'WBTC-BTC'],
341
342
  'fetchCurrencies': {
342
343
  'expires': 5000,
@@ -2251,8 +2252,9 @@ class coinbase(Exchange, ImplicitAPI):
2251
2252
  """
2252
2253
  self.load_markets()
2253
2254
  market = self.market(symbol)
2255
+ id = self.safe_string(self.options, 'brokerId', 'ccxt')
2254
2256
  request = {
2255
- 'client_order_id': self.uuid(),
2257
+ 'client_order_id': id + '-' + self.uuid(),
2256
2258
  'product_id': market['id'],
2257
2259
  'side': side.upper(),
2258
2260
  }
@@ -3573,25 +3575,14 @@ class coinbase(Exchange, ImplicitAPI):
3573
3575
  url = self.urls['api']['rest'] + fullPath
3574
3576
  if signed:
3575
3577
  authorization = self.safe_string(self.headers, 'Authorization')
3578
+ authorizationString = None
3576
3579
  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)
3580
+ authorizationString = authorization
3584
3581
  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)
3582
+ authorizationString = 'Bearer ' + self.token
3592
3583
  else:
3593
3584
  self.check_required_credentials()
3594
- timestampString = str(self.seconds())
3585
+ seconds = self.seconds()
3595
3586
  payload = ''
3596
3587
  if method != 'GET':
3597
3588
  if query:
@@ -3605,14 +3596,45 @@ class coinbase(Exchange, ImplicitAPI):
3605
3596
  # https://docs.cloud.coinbase.com/advanced-trade-api/docs/auth#example-request
3606
3597
  # v2: 'GET' require payload in the signature
3607
3598
  # 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)
3599
+ isCloudAPiKey = (self.apiKey.find('organizations/') >= 0) or (self.secret.startswith('-----BEGIN'))
3600
+ if isCloudAPiKey:
3601
+ if self.apiKey.startswith('-----BEGIN'):
3602
+ raise ArgumentsRequired(self.id + ' apiKey should contain the name(eg: organizations/3b910e93....) and not the public key')
3603
+ # it may not work for v2
3604
+ uri = method + ' ' + url.replace('https://', '')
3605
+ quesPos = uri.find('?')
3606
+ if quesPos >= 0:
3607
+ uri = uri[0:quesPos]
3608
+ nonce = self.random_bytes(16)
3609
+ request = {
3610
+ 'aud': ['retail_rest_api_proxy'],
3611
+ 'iss': 'coinbase-cloud',
3612
+ 'nbf': seconds,
3613
+ 'exp': seconds + 120,
3614
+ 'sub': self.apiKey,
3615
+ 'uri': uri,
3616
+ 'iat': seconds,
3617
+ }
3618
+ token = self.jwt(request, self.encode(self.secret), 'sha256', False, {'kid': self.apiKey, 'nonce': nonce, 'alg': 'ES256'})
3619
+ authorizationString = 'Bearer ' + token
3620
+ else:
3621
+ timestampString = str(self.seconds())
3622
+ auth = timestampString + method + savedPath + payload
3623
+ signature = self.hmac(self.encode(auth), self.encode(self.secret), hashlib.sha256)
3624
+ headers = {
3625
+ 'CB-ACCESS-KEY': self.apiKey,
3626
+ 'CB-ACCESS-SIGN': signature,
3627
+ 'CB-ACCESS-TIMESTAMP': timestampString,
3628
+ 'Content-Type': 'application/json',
3629
+ }
3630
+ if authorizationString is not None:
3610
3631
  headers = {
3611
- 'CB-ACCESS-KEY': self.apiKey,
3612
- 'CB-ACCESS-SIGN': signature,
3613
- 'CB-ACCESS-TIMESTAMP': timestampString,
3632
+ 'Authorization': authorizationString,
3614
3633
  'Content-Type': 'application/json',
3615
3634
  }
3635
+ if method != 'GET':
3636
+ if query:
3637
+ body = self.json(query)
3616
3638
  return {'url': url, 'method': method, 'body': body, 'headers': headers}
3617
3639
 
3618
3640
  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,
ccxt/coinex.py CHANGED
@@ -92,6 +92,7 @@ class coinex(Exchange, ImplicitAPI):
92
92
  'fetchLeverage': 'emulated',
93
93
  'fetchLeverages': True,
94
94
  'fetchLeverageTiers': True,
95
+ 'fetchMarginAdjustmentHistory': True,
95
96
  'fetchMarketLeverageTiers': 'emulated',
96
97
  'fetchMarkets': True,
97
98
  'fetchMarkOHLCV': False,
@@ -3966,11 +3967,10 @@ class coinex(Exchange, ImplicitAPI):
3966
3967
  # "message":"OK"
3967
3968
  # }
3968
3969
  #
3970
+ data = self.safe_dict(response, 'data')
3969
3971
  status = self.safe_string(response, 'message')
3970
- type = 'add' if (addOrReduce == 1) else 'reduce'
3971
- return self.extend(self.parse_margin_modification(response, market), {
3972
+ return self.extend(self.parse_margin_modification(data, market), {
3972
3973
  'amount': self.parse_number(amount),
3973
- 'type': type,
3974
3974
  'status': status,
3975
3975
  })
3976
3976
 
@@ -4030,13 +4030,34 @@ class coinex(Exchange, ImplicitAPI):
4030
4030
  # "user_id": 3620173
4031
4031
  # }
4032
4032
  #
4033
- timestamp = self.safe_integer_product(data, 'update_time', 1000)
4033
+ # fetchMarginAdjustmentHistory
4034
+ #
4035
+ # {
4036
+ # bkr_price: '0',
4037
+ # leverage: '3',
4038
+ # liq_price: '0',
4039
+ # margin_amount: '5.33236666666666666666',
4040
+ # margin_change: '3',
4041
+ # market: 'XRPUSDT',
4042
+ # position_amount: '11',
4043
+ # position_id: '297155652',
4044
+ # position_type: '2',
4045
+ # settle_price: '0.6361',
4046
+ # time: '1711050906.382891',
4047
+ # type: '1',
4048
+ # user_id: '3685860'
4049
+ # }
4050
+ #
4051
+ marketId = self.safe_string(data, 'market')
4052
+ type = self.safe_string(data, 'type')
4053
+ timestamp = self.safe_integer_product_2(data, 'time', 'update_time', 1000)
4034
4054
  return {
4035
4055
  'info': data,
4036
- 'symbol': self.safe_symbol(None, market),
4037
- 'type': None,
4038
- 'amount': self.safe_number(data, 'margin_amount'),
4039
- 'total': None,
4056
+ 'symbol': self.safe_symbol(marketId, market, None, 'swap'),
4057
+ 'type': 'add' if (type == '1') else 'reduce',
4058
+ 'marginMode': 'isolated',
4059
+ 'amount': self.safe_number(data, 'margin_change'),
4060
+ 'total': self.safe_number(data, 'position_amount'),
4040
4061
  'code': market['quote'],
4041
4062
  'status': None,
4042
4063
  'timestamp': timestamp,
@@ -4644,6 +4665,7 @@ class coinex(Exchange, ImplicitAPI):
4644
4665
  currencyId = self.safe_string(transfer, 'asset')
4645
4666
  currencyCode = self.safe_currency_code(currencyId, currency)
4646
4667
  return {
4668
+ 'info': transfer,
4647
4669
  'id': self.safe_integer(transfer, 'id'),
4648
4670
  'timestamp': timestamp,
4649
4671
  'datetime': self.iso8601(timestamp),
@@ -5410,3 +5432,69 @@ class coinex(Exchange, ImplicitAPI):
5410
5432
  self.throw_exactly_matched_exception(self.exceptions['exact'], code, feedback)
5411
5433
  raise ExchangeError(feedback)
5412
5434
  return None
5435
+
5436
+ def fetch_margin_adjustment_history(self, symbol: Str = None, type: Str = None, since: Num = None, limit: Num = None, params={}) -> List[MarginModification]:
5437
+ """
5438
+ fetches the history of margin added or reduced from contract isolated positions
5439
+ :see: https://viabtc.github.io/coinex_api_en_doc/futures/#docsfutures001_http046_position_margin_history
5440
+ :param str [symbol]: unified market symbol
5441
+ :param str [type]: not used by coinex fetchMarginAdjustmentHistory
5442
+ :param int [since]: timestamp in ms of the earliest change to fetch
5443
+ :param int [limit]: the maximum amount of changes to fetch, default=100, max=100
5444
+ :param dict params: extra parameters specific to the exchange api endpoint
5445
+ :param int [params.until]: timestamp in ms of the latest change to fetch
5446
+ *
5447
+ * EXCHANGE SPECIFIC PARAMETERS
5448
+ :param int [params.offset]: offset
5449
+ :returns dict[]: a list of `margin structures <https://docs.ccxt.com/#/?id=margin-loan-structure>`
5450
+ """
5451
+ self.load_markets()
5452
+ until = self.safe_integer(params, 'until')
5453
+ params = self.omit(params, 'until')
5454
+ if limit is None:
5455
+ limit = 100
5456
+ request = {
5457
+ 'market': '',
5458
+ 'position_id': 0,
5459
+ 'offset': 0,
5460
+ 'limit': limit,
5461
+ }
5462
+ if symbol is not None:
5463
+ market = self.market(symbol)
5464
+ request['market'] = market['id']
5465
+ if since is not None:
5466
+ request['start_time'] = since
5467
+ if until is not None:
5468
+ request['end_time'] = until
5469
+ response = self.v1PerpetualPrivateGetPositionMarginHistory(self.extend(request, params))
5470
+ #
5471
+ # {
5472
+ # code: '0',
5473
+ # data: {
5474
+ # limit: '100',
5475
+ # offset: '0',
5476
+ # records: [
5477
+ # {
5478
+ # bkr_price: '0',
5479
+ # leverage: '3',
5480
+ # liq_price: '0',
5481
+ # margin_amount: '5.33236666666666666666',
5482
+ # margin_change: '3',
5483
+ # market: 'XRPUSDT',
5484
+ # position_amount: '11',
5485
+ # position_id: '297155652',
5486
+ # position_type: '2',
5487
+ # settle_price: '0.6361',
5488
+ # time: '1711050906.382891',
5489
+ # type: '1',
5490
+ # user_id: '3685860'
5491
+ # }
5492
+ # ]
5493
+ # },
5494
+ # message: 'OK'
5495
+ # }
5496
+ #
5497
+ data = self.safe_dict(response, 'data', {})
5498
+ records = self.safe_list(data, 'records', [])
5499
+ modifications = self.parse_margin_modifications(records, None, 'market', 'swap')
5500
+ return self.filter_by_symbol_since_limit(modifications, symbol, since, limit)