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/async_support/okx.py CHANGED
@@ -107,6 +107,7 @@ class okx(Exchange, ImplicitAPI):
107
107
  'fetchLedgerEntry': None,
108
108
  'fetchLeverage': True,
109
109
  'fetchLeverageTiers': False,
110
+ 'fetchMarginAdjustmentHistory': True,
110
111
  'fetchMarketLeverageTiers': True,
111
112
  'fetchMarkets': True,
112
113
  'fetchMarkOHLCV': True,
@@ -1235,7 +1236,7 @@ class okx(Exchange, ImplicitAPI):
1235
1236
  # ]
1236
1237
  # }
1237
1238
  #
1238
- data = self.safe_value(response, 'data', [])
1239
+ data = self.safe_list(response, 'data', [])
1239
1240
  dataLength = len(data)
1240
1241
  update = {
1241
1242
  'updated': None,
@@ -1276,8 +1277,8 @@ class okx(Exchange, ImplicitAPI):
1276
1277
  # "msg": ""
1277
1278
  # }
1278
1279
  #
1279
- data = self.safe_value(response, 'data', [])
1280
- first = self.safe_value(data, 0, {})
1280
+ data = self.safe_list(response, 'data', [])
1281
+ first = self.safe_dict(data, 0, {})
1281
1282
  return self.safe_integer(first, 'ts')
1282
1283
 
1283
1284
  async def fetch_accounts(self, params={}) -> List[Account]:
@@ -1307,7 +1308,7 @@ class okx(Exchange, ImplicitAPI):
1307
1308
  # "msg": ""
1308
1309
  # }
1309
1310
  #
1310
- data = self.safe_value(response, 'data', [])
1311
+ data = self.safe_list(response, 'data', [])
1311
1312
  result = []
1312
1313
  for i in range(0, len(data)):
1313
1314
  account = data[i]
@@ -1329,7 +1330,7 @@ class okx(Exchange, ImplicitAPI):
1329
1330
  :param dict [params]: extra parameters specific to the exchange API endpoint
1330
1331
  :returns dict[]: an array of objects representing market data
1331
1332
  """
1332
- types = self.safe_value(self.options, 'fetchMarkets')
1333
+ types = self.safe_list(self.options, 'fetchMarkets', [])
1333
1334
  promises = []
1334
1335
  result = []
1335
1336
  for i in range(0, len(types)):
@@ -1426,7 +1427,7 @@ class okx(Exchange, ImplicitAPI):
1426
1427
  symbol = symbol + '-' + ymd + '-' + strikePrice + '-' + optionType
1427
1428
  optionType = 'put' if (optionType == 'P') else 'call'
1428
1429
  tickSize = self.safe_string(market, 'tickSz')
1429
- fees = self.safe_value_2(self.fees, type, 'trading', {})
1430
+ fees = self.safe_dict_2(self.fees, type, 'trading', {})
1430
1431
  maxLeverage = self.safe_string(market, 'lever', '1')
1431
1432
  maxLeverage = Precise.string_max(maxLeverage, '1')
1432
1433
  maxSpotCost = self.safe_number(market, 'maxMktSz')
@@ -1485,7 +1486,7 @@ class okx(Exchange, ImplicitAPI):
1485
1486
  'instType': self.convert_to_instrument_type(type),
1486
1487
  }
1487
1488
  if type == 'option':
1488
- optionsUnderlying = self.safe_value(self.options, 'defaultUnderlying', ['BTC-USD', 'ETH-USD'])
1489
+ optionsUnderlying = self.safe_list(self.options, 'defaultUnderlying', ['BTC-USD', 'ETH-USD'])
1489
1490
  promises = []
1490
1491
  for i in range(0, len(optionsUnderlying)):
1491
1492
  underlying = optionsUnderlying[i]
@@ -1494,8 +1495,8 @@ class okx(Exchange, ImplicitAPI):
1494
1495
  promisesResult = await asyncio.gather(*promises)
1495
1496
  markets = []
1496
1497
  for i in range(0, len(promisesResult)):
1497
- res = self.safe_value(promisesResult, i, {})
1498
- options = self.safe_value(res, 'data', [])
1498
+ res = self.safe_dict(promisesResult, i, {})
1499
+ options = self.safe_list(res, 'data', [])
1499
1500
  markets = self.array_concat(markets, options)
1500
1501
  return self.parse_markets(markets)
1501
1502
  response = await self.publicGetPublicInstruments(self.extend(request, params))
@@ -1532,7 +1533,7 @@ class okx(Exchange, ImplicitAPI):
1532
1533
  # "msg": ""
1533
1534
  # }
1534
1535
  #
1535
- dataResponse = self.safe_value(response, 'data', [])
1536
+ dataResponse = self.safe_list(response, 'data', [])
1536
1537
  return self.parse_markets(dataResponse)
1537
1538
 
1538
1539
  def safe_network(self, networkId):
@@ -1606,7 +1607,7 @@ class okx(Exchange, ImplicitAPI):
1606
1607
  # "msg": ""
1607
1608
  # }
1608
1609
  #
1609
- data = self.safe_value(response, 'data', [])
1610
+ data = self.safe_list(response, 'data', [])
1610
1611
  result = {}
1611
1612
  dataByCurrencyId = self.group_by(data, 'ccy')
1612
1613
  currencyIds = list(dataByCurrencyId.keys())
@@ -1622,11 +1623,11 @@ class okx(Exchange, ImplicitAPI):
1622
1623
  maxPrecision = None
1623
1624
  for j in range(0, len(chains)):
1624
1625
  chain = chains[j]
1625
- canDeposit = self.safe_value(chain, 'canDep')
1626
+ canDeposit = self.safe_bool(chain, 'canDep')
1626
1627
  depositEnabled = canDeposit if (canDeposit) else depositEnabled
1627
- canWithdraw = self.safe_value(chain, 'canWd')
1628
+ canWithdraw = self.safe_bool(chain, 'canWd')
1628
1629
  withdrawEnabled = canWithdraw if (canWithdraw) else withdrawEnabled
1629
- canInternal = self.safe_value(chain, 'canInternal')
1630
+ canInternal = self.safe_bool(chain, 'canInternal')
1630
1631
  active = True if (canDeposit and canWithdraw and canInternal) else False
1631
1632
  currencyActive = active if (active) else currencyActive
1632
1633
  networkId = self.safe_string(chain, 'chain')
@@ -1655,7 +1656,7 @@ class okx(Exchange, ImplicitAPI):
1655
1656
  },
1656
1657
  'info': chain,
1657
1658
  }
1658
- firstChain = self.safe_value(chains, 0)
1659
+ firstChain = self.safe_dict(chains, 0, {})
1659
1660
  result[code] = {
1660
1661
  'info': None,
1661
1662
  'code': code,
@@ -1724,8 +1725,8 @@ class okx(Exchange, ImplicitAPI):
1724
1725
  # ]
1725
1726
  # }
1726
1727
  #
1727
- data = self.safe_value(response, 'data', [])
1728
- first = self.safe_value(data, 0, {})
1728
+ data = self.safe_list(response, 'data', [])
1729
+ first = self.safe_dict(data, 0, {})
1729
1730
  timestamp = self.safe_integer(first, 'ts')
1730
1731
  return self.parse_order_book(first, symbol, timestamp)
1731
1732
 
@@ -1824,7 +1825,7 @@ class okx(Exchange, ImplicitAPI):
1824
1825
  # ]
1825
1826
  # }
1826
1827
  #
1827
- data = self.safe_value(response, 'data', [])
1828
+ data = self.safe_list(response, 'data', [])
1828
1829
  first = self.safe_dict(data, 0, {})
1829
1830
  return self.parse_ticker(first, market)
1830
1831
 
@@ -1845,7 +1846,7 @@ class okx(Exchange, ImplicitAPI):
1845
1846
  'instType': self.convert_to_instrument_type(marketType),
1846
1847
  }
1847
1848
  if marketType == 'option':
1848
- defaultUnderlying = self.safe_value(self.options, 'defaultUnderlying', 'BTC-USD')
1849
+ defaultUnderlying = self.safe_string(self.options, 'defaultUnderlying', 'BTC-USD')
1849
1850
  currencyId = self.safe_string_2(params, 'uly', 'marketId', defaultUnderlying)
1850
1851
  if currencyId is None:
1851
1852
  raise ArgumentsRequired(self.id + ' fetchTickers() requires an underlying uly or marketId parameter for options markets')
@@ -2094,7 +2095,7 @@ class okx(Exchange, ImplicitAPI):
2094
2095
  return await self.fetch_paginated_call_deterministic('fetchOHLCV', symbol, since, limit, timeframe, params, 200)
2095
2096
  price = self.safe_string(params, 'price')
2096
2097
  params = self.omit(params, 'price')
2097
- options = self.safe_value(self.options, 'fetchOHLCV', {})
2098
+ options = self.safe_dict(self.options, 'fetchOHLCV', {})
2098
2099
  timezone = self.safe_string(options, 'timezone', 'UTC')
2099
2100
  if limit is None:
2100
2101
  limit = 100 # default 100, max 100
@@ -2207,7 +2208,7 @@ class okx(Exchange, ImplicitAPI):
2207
2208
  # }
2208
2209
  #
2209
2210
  rates = []
2210
- data = self.safe_value(response, 'data', [])
2211
+ data = self.safe_list(response, 'data', [])
2211
2212
  for i in range(0, len(data)):
2212
2213
  rate = data[i]
2213
2214
  timestamp = self.safe_integer(rate, 'fundingTime')
@@ -2229,10 +2230,10 @@ class okx(Exchange, ImplicitAPI):
2229
2230
 
2230
2231
  def parse_trading_balance(self, response):
2231
2232
  result = {'info': response}
2232
- data = self.safe_value(response, 'data', [])
2233
- first = self.safe_value(data, 0, {})
2233
+ data = self.safe_list(response, 'data', [])
2234
+ first = self.safe_dict(data, 0, {})
2234
2235
  timestamp = self.safe_integer(first, 'uTime')
2235
- details = self.safe_value(first, 'details', [])
2236
+ details = self.safe_list(first, 'details', [])
2236
2237
  for i in range(0, len(details)):
2237
2238
  balance = details[i]
2238
2239
  currencyId = self.safe_string(balance, 'ccy')
@@ -2254,7 +2255,7 @@ class okx(Exchange, ImplicitAPI):
2254
2255
 
2255
2256
  def parse_funding_balance(self, response):
2256
2257
  result = {'info': response}
2257
- data = self.safe_value(response, 'data', [])
2258
+ data = self.safe_list(response, 'data', [])
2258
2259
  for i in range(0, len(data)):
2259
2260
  balance = data[i]
2260
2261
  currencyId = self.safe_string(balance, 'ccy')
@@ -2332,8 +2333,8 @@ class okx(Exchange, ImplicitAPI):
2332
2333
  # "msg": ""
2333
2334
  # }
2334
2335
  #
2335
- data = self.safe_value(response, 'data', [])
2336
- first = self.safe_value(data, 0, {})
2336
+ data = self.safe_list(response, 'data', [])
2337
+ first = self.safe_dict(data, 0, {})
2337
2338
  return self.parse_trading_fee(first, market)
2338
2339
 
2339
2340
  async def fetch_balance(self, params={}) -> Balances:
@@ -2747,8 +2748,8 @@ class okx(Exchange, ImplicitAPI):
2747
2748
  response = await self.privatePostTradeOrderAlgo(request)
2748
2749
  else:
2749
2750
  response = await self.privatePostTradeBatchOrders(request)
2750
- data = self.safe_value(response, 'data', [])
2751
- first = self.safe_value(data, 0)
2751
+ data = self.safe_list(response, 'data', [])
2752
+ first = self.safe_dict(data, 0, {})
2752
2753
  order = self.parse_order(first, market)
2753
2754
  order['type'] = type
2754
2755
  order['side'] = side
@@ -4127,7 +4128,7 @@ class okx(Exchange, ImplicitAPI):
4127
4128
  # ]
4128
4129
  # }
4129
4130
  #
4130
- data = self.safe_value(response, 'data', [])
4131
+ data = self.safe_list(response, 'data', [])
4131
4132
  return self.parse_ledger(data, currency, since, limit)
4132
4133
 
4133
4134
  def parse_ledger_entry_type(self, type):
@@ -6036,9 +6037,9 @@ class okx(Exchange, ImplicitAPI):
6036
6037
  # }
6037
6038
  #
6038
6039
  data = self.safe_list(response, 'data', [])
6040
+ entry = self.safe_dict(data, 0, {})
6039
6041
  errorCode = self.safe_string(response, 'code')
6040
- item = self.safe_dict(data, 0, {})
6041
- return self.extend(self.parse_margin_modification(item, market), {
6042
+ return self.extend(self.parse_margin_modification(entry, market), {
6042
6043
  'status': 'ok' if (errorCode == '0') else 'failed',
6043
6044
  })
6044
6045
 
@@ -6053,22 +6054,66 @@ class okx(Exchange, ImplicitAPI):
6053
6054
  # "type": "reduce"
6054
6055
  # }
6055
6056
  #
6056
- amountRaw = self.safe_number(data, 'amt')
6057
+ # fetchMarginAdjustmentHistory
6058
+ #
6059
+ # {
6060
+ # bal: '67621.4325135010619812',
6061
+ # balChg: '-10.0000000000000000',
6062
+ # billId: '691293628710342659',
6063
+ # ccy: 'USDT',
6064
+ # clOrdId: '',
6065
+ # execType: '',
6066
+ # fee: '0',
6067
+ # fillFwdPx: '',
6068
+ # fillIdxPx: '',
6069
+ # fillMarkPx: '',
6070
+ # fillMarkVol: '',
6071
+ # fillPxUsd: '',
6072
+ # fillPxVol: '',
6073
+ # fillTime: '1711089244850',
6074
+ # from: '',
6075
+ # instId: 'XRP-USDT-SWAP',
6076
+ # instType: 'SWAP',
6077
+ # interest: '0',
6078
+ # mgnMode: 'isolated',
6079
+ # notes: '',
6080
+ # ordId: '',
6081
+ # pnl: '0',
6082
+ # posBal: '73.12',
6083
+ # posBalChg: '10.00',
6084
+ # px: '',
6085
+ # subType: '160',
6086
+ # sz: '10',
6087
+ # tag: '',
6088
+ # to: '',
6089
+ # tradeId: '0',
6090
+ # ts: '1711089244699',
6091
+ # type: '6'
6092
+ # }
6093
+ #
6094
+ amountRaw = self.safe_string_2(data, 'amt', 'posBalChg')
6057
6095
  typeRaw = self.safe_string(data, 'type')
6058
- type = 'reduce' if (typeRaw == 'reduce') else 'add'
6096
+ type = None
6097
+ if typeRaw == '6':
6098
+ type = 'add' if Precise.string_gt(amountRaw, '0') else 'reduce'
6099
+ else:
6100
+ type = typeRaw
6101
+ amount = Precise.string_abs(amountRaw)
6059
6102
  marketId = self.safe_string(data, 'instId')
6060
6103
  responseMarket = self.safe_market(marketId, market)
6061
6104
  code = responseMarket['base'] if responseMarket['inverse'] else responseMarket['quote']
6105
+ timestamp = self.safe_integer(data, 'ts')
6062
6106
  return {
6063
6107
  'info': data,
6064
6108
  'symbol': responseMarket['symbol'],
6065
6109
  'type': type,
6066
- 'amount': amountRaw,
6067
- 'total': None,
6110
+ 'marginMode': 'isolated',
6111
+ 'amount': self.parse_number(amount),
6068
6112
  'code': code,
6113
+ 'total': None,
6069
6114
  'status': None,
6070
- 'timestamp': None,
6071
- 'datetime': None,
6115
+ 'timestamp': timestamp,
6116
+ 'datetime': self.iso8601(timestamp),
6072
6117
  }
6073
6118
 
6074
6119
  async def reduce_margin(self, symbol: str, amount, params={}) -> MarginModification:
@@ -7089,3 +7134,93 @@ class okx(Exchange, ImplicitAPI):
7089
7134
  self.throw_exactly_matched_exception(self.exceptions['exact'], code, feedback)
7090
7135
  raise ExchangeError(feedback) # unknown message
7091
7136
  return None
7137
+
7138
+ async def fetch_margin_adjustment_history(self, symbol: Str = None, type: Str = None, since: Num = None, limit: Num = None, params={}) -> List[MarginModification]:
7139
+ """
7140
+ fetches the history of margin added or reduced from contract isolated positions
7141
+ :see: https://www.okx.com/docs-v5/en/#trading-account-rest-api-get-bills-details-last-7-days
7142
+ :see: https://www.okx.com/docs-v5/en/#trading-account-rest-api-get-bills-details-last-3-months
7143
+ :param str [symbol]: not used by okx fetchMarginAdjustmentHistory
7144
+ :param str [type]: "add" or "reduce"
7145
+ :param dict params: extra parameters specific to the exchange api endpoint
7146
+ :param boolean [params.auto]: True if fetching auto margin increases
7147
+ :returns dict[]: a list of `margin structures <https://docs.ccxt.com/#/?id=margin-loan-structure>`
7148
+ """
7149
+ await self.load_markets()
7150
+ auto = self.safe_bool(params, 'auto')
7151
+ if type is None:
7152
+ raise ArgumentsRequired(self.id + ' fetchMarginAdjustmentHistory() requires a type argument')
7153
+ isAdd = type == 'add'
7154
+ subType = '160' if isAdd else '161'
7155
+ if auto:
7156
+ if isAdd:
7157
+ subType = '162'
7158
+ else:
7159
+ raise BadRequest(self.id + ' cannot fetch margin adjustments for type ' + type)
7160
+ request = {
7161
+ 'subType': subType,
7162
+ 'mgnMode': 'isolated',
7163
+ }
7164
+ until = self.safe_integer(params, 'until')
7165
+ params = self.omit(params, 'until')
7166
+ if since is not None:
7167
+ request['startTime'] = since
7168
+ if limit is not None:
7169
+ request['limit'] = limit
7170
+ if until is not None:
7171
+ request['endTime'] = until
7172
+ response = None
7173
+ now = self.milliseconds()
7174
+ oneWeekAgo = now - 604800000
7175
+ threeMonthsAgo = now - 7776000000
7176
+ if (since is None) or (since > oneWeekAgo):
7177
+ response = await self.privateGetAccountBills(self.extend(request, params))
7178
+ elif since > threeMonthsAgo:
7179
+ response = await self.privateGetAccountBillsArchive(self.extend(request, params))
7180
+ else:
7181
+ raise BadRequest(self.id + ' fetchMarginAdjustmentHistory() cannot fetch margin adjustments older than 3 months')
7182
+ #
7183
+ # {
7184
+ # code: '0',
7185
+ # data: [
7186
+ # {
7187
+ # bal: '67621.4325135010619812',
7188
+ # balChg: '-10.0000000000000000',
7189
+ # billId: '691293628710342659',
7190
+ # ccy: 'USDT',
7191
+ # clOrdId: '',
7192
+ # execType: '',
7193
+ # fee: '0',
7194
+ # fillFwdPx: '',
7195
+ # fillIdxPx: '',
7196
+ # fillMarkPx: '',
7197
+ # fillMarkVol: '',
7198
+ # fillPxUsd: '',
7199
+ # fillPxVol: '',
7200
+ # fillTime: '1711089244850',
7201
+ # from: '',
7202
+ # instId: 'XRP-USDT-SWAP',
7203
+ # instType: 'SWAP',
7204
+ # interest: '0',
7205
+ # mgnMode: 'isolated',
7206
+ # notes: '',
7207
+ # ordId: '',
7208
+ # pnl: '0',
7209
+ # posBal: '73.12',
7210
+ # posBalChg: '10.00',
7211
+ # px: '',
7212
+ # subType: '160',
7213
+ # sz: '10',
7214
+ # tag: '',
7215
+ # to: '',
7216
+ # tradeId: '0',
7217
+ # ts: '1711089244699',
7218
+ # type: '6'
7219
+ # }
7220
+ # ],
7221
+ # msg: ''
7222
+ # }
7223
+ #
7224
+ data = self.safe_list(response, 'data')
7225
+ modifications = self.parse_margin_modifications(data)
7226
+ return self.filter_by_symbol_since_limit(modifications, symbol, since, limit)
@@ -3792,6 +3792,7 @@ class phemex(Exchange, ImplicitAPI):
3792
3792
  'info': data,
3793
3793
  'symbol': self.safe_symbol(None, market),
3794
3794
  'type': 'set',
3795
+ 'marginMode': 'isolated',
3795
3796
  'amount': None,
3796
3797
  'total': None,
3797
3798
  'code': market[codeCurrency],
ccxt/async_support/woo.py CHANGED
@@ -77,6 +77,7 @@ class woo(Exchange, ImplicitAPI):
77
77
  'fetchIndexOHLCV': False,
78
78
  'fetchLedger': True,
79
79
  'fetchLeverage': True,
80
+ 'fetchMarginAdjustmentHistory': False,
80
81
  'fetchMarginMode': False,
81
82
  'fetchMarkets': True,
82
83
  'fetchMarkOHLCV': False,
ccxt/base/exchange.py CHANGED
@@ -4,7 +4,7 @@
4
4
 
5
5
  # -----------------------------------------------------------------------------
6
6
 
7
- __version__ = '4.2.89'
7
+ __version__ = '4.2.91'
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
 
@@ -2270,7 +2303,7 @@ class Exchange(object):
2270
2303
  def parse_to_int(self, number):
2271
2304
  # Solve Common intmisuse ex: int((since / str(1000)))
2272
2305
  # using a number which is not valid in ts
2273
- stringifiedNumber = str(number)
2306
+ stringifiedNumber = self.number_to_string(number)
2274
2307
  convertedNumber = float(stringifiedNumber)
2275
2308
  return int(convertedNumber)
2276
2309
 
@@ -4393,12 +4426,6 @@ class Exchange(object):
4393
4426
  def common_currency_code(self, code: str):
4394
4427
  if not self.substituteCommonCurrencyCodes:
4395
4428
  return code
4396
- # if the provided code already exists value in commonCurrencies dict, then we should not again transform it
4397
- # more details at: https://github.com/ccxt/ccxt/issues/21112#issuecomment-2031293691
4398
- commonCurrencies = list(self.commonCurrencies.values())
4399
- exists = self.in_array(code, commonCurrencies)
4400
- if exists:
4401
- return code
4402
4429
  return self.safe_string(self.commonCurrencies, code, code)
4403
4430
 
4404
4431
  def currency(self, code: str):
@@ -4952,7 +4979,6 @@ class Exchange(object):
4952
4979
  :returns dict: objects with withdraw and deposit fees, indexed by currency codes
4953
4980
  """
4954
4981
  depositWithdrawFees = {}
4955
- codes = self.marketCodes(codes)
4956
4982
  isArray = isinstance(response, list)
4957
4983
  responseKeys = response
4958
4984
  if not isArray:
@@ -5447,3 +5473,16 @@ class Exchange(object):
5447
5473
  day = date[5:7]
5448
5474
  reconstructedDate = day + month + year
5449
5475
  return reconstructedDate
5476
+
5477
+ def parse_margin_modification(self, data, market: Market = None):
5478
+ raise NotSupported(self.id + ' parseMarginModification() is not supported yet')
5479
+
5480
+ def parse_margin_modifications(self, response: List[object], symbols: List[str] = None, symbolKey: Str = None, marketType: MarketType = None):
5481
+ marginModifications = []
5482
+ for i in range(0, len(response)):
5483
+ info = response[i]
5484
+ marketId = self.safe_string(info, symbolKey)
5485
+ market = self.safe_market(marketId, None, None, marketType)
5486
+ if (symbols is None) or self.in_array(market['symbol'], symbols):
5487
+ marginModifications.append(self.parse_margin_modification(info, market))
5488
+ 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