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/okx.py CHANGED
@@ -106,6 +106,7 @@ class okx(Exchange, ImplicitAPI):
106
106
  'fetchLedgerEntry': None,
107
107
  'fetchLeverage': True,
108
108
  'fetchLeverageTiers': False,
109
+ 'fetchMarginAdjustmentHistory': True,
109
110
  'fetchMarketLeverageTiers': True,
110
111
  'fetchMarkets': True,
111
112
  'fetchMarkOHLCV': True,
@@ -1234,7 +1235,7 @@ class okx(Exchange, ImplicitAPI):
1234
1235
  # ]
1235
1236
  # }
1236
1237
  #
1237
- data = self.safe_value(response, 'data', [])
1238
+ data = self.safe_list(response, 'data', [])
1238
1239
  dataLength = len(data)
1239
1240
  update = {
1240
1241
  'updated': None,
@@ -1275,8 +1276,8 @@ class okx(Exchange, ImplicitAPI):
1275
1276
  # "msg": ""
1276
1277
  # }
1277
1278
  #
1278
- data = self.safe_value(response, 'data', [])
1279
- first = self.safe_value(data, 0, {})
1279
+ data = self.safe_list(response, 'data', [])
1280
+ first = self.safe_dict(data, 0, {})
1280
1281
  return self.safe_integer(first, 'ts')
1281
1282
 
1282
1283
  def fetch_accounts(self, params={}) -> List[Account]:
@@ -1306,7 +1307,7 @@ class okx(Exchange, ImplicitAPI):
1306
1307
  # "msg": ""
1307
1308
  # }
1308
1309
  #
1309
- data = self.safe_value(response, 'data', [])
1310
+ data = self.safe_list(response, 'data', [])
1310
1311
  result = []
1311
1312
  for i in range(0, len(data)):
1312
1313
  account = data[i]
@@ -1328,7 +1329,7 @@ class okx(Exchange, ImplicitAPI):
1328
1329
  :param dict [params]: extra parameters specific to the exchange API endpoint
1329
1330
  :returns dict[]: an array of objects representing market data
1330
1331
  """
1331
- types = self.safe_value(self.options, 'fetchMarkets')
1332
+ types = self.safe_list(self.options, 'fetchMarkets', [])
1332
1333
  promises = []
1333
1334
  result = []
1334
1335
  for i in range(0, len(types)):
@@ -1425,7 +1426,7 @@ class okx(Exchange, ImplicitAPI):
1425
1426
  symbol = symbol + '-' + ymd + '-' + strikePrice + '-' + optionType
1426
1427
  optionType = 'put' if (optionType == 'P') else 'call'
1427
1428
  tickSize = self.safe_string(market, 'tickSz')
1428
- fees = self.safe_value_2(self.fees, type, 'trading', {})
1429
+ fees = self.safe_dict_2(self.fees, type, 'trading', {})
1429
1430
  maxLeverage = self.safe_string(market, 'lever', '1')
1430
1431
  maxLeverage = Precise.string_max(maxLeverage, '1')
1431
1432
  maxSpotCost = self.safe_number(market, 'maxMktSz')
@@ -1484,7 +1485,7 @@ class okx(Exchange, ImplicitAPI):
1484
1485
  'instType': self.convert_to_instrument_type(type),
1485
1486
  }
1486
1487
  if type == 'option':
1487
- optionsUnderlying = self.safe_value(self.options, 'defaultUnderlying', ['BTC-USD', 'ETH-USD'])
1488
+ optionsUnderlying = self.safe_list(self.options, 'defaultUnderlying', ['BTC-USD', 'ETH-USD'])
1488
1489
  promises = []
1489
1490
  for i in range(0, len(optionsUnderlying)):
1490
1491
  underlying = optionsUnderlying[i]
@@ -1493,8 +1494,8 @@ class okx(Exchange, ImplicitAPI):
1493
1494
  promisesResult = promises
1494
1495
  markets = []
1495
1496
  for i in range(0, len(promisesResult)):
1496
- res = self.safe_value(promisesResult, i, {})
1497
- options = self.safe_value(res, 'data', [])
1497
+ res = self.safe_dict(promisesResult, i, {})
1498
+ options = self.safe_list(res, 'data', [])
1498
1499
  markets = self.array_concat(markets, options)
1499
1500
  return self.parse_markets(markets)
1500
1501
  response = self.publicGetPublicInstruments(self.extend(request, params))
@@ -1531,7 +1532,7 @@ class okx(Exchange, ImplicitAPI):
1531
1532
  # "msg": ""
1532
1533
  # }
1533
1534
  #
1534
- dataResponse = self.safe_value(response, 'data', [])
1535
+ dataResponse = self.safe_list(response, 'data', [])
1535
1536
  return self.parse_markets(dataResponse)
1536
1537
 
1537
1538
  def safe_network(self, networkId):
@@ -1605,7 +1606,7 @@ class okx(Exchange, ImplicitAPI):
1605
1606
  # "msg": ""
1606
1607
  # }
1607
1608
  #
1608
- data = self.safe_value(response, 'data', [])
1609
+ data = self.safe_list(response, 'data', [])
1609
1610
  result = {}
1610
1611
  dataByCurrencyId = self.group_by(data, 'ccy')
1611
1612
  currencyIds = list(dataByCurrencyId.keys())
@@ -1621,11 +1622,11 @@ class okx(Exchange, ImplicitAPI):
1621
1622
  maxPrecision = None
1622
1623
  for j in range(0, len(chains)):
1623
1624
  chain = chains[j]
1624
- canDeposit = self.safe_value(chain, 'canDep')
1625
+ canDeposit = self.safe_bool(chain, 'canDep')
1625
1626
  depositEnabled = canDeposit if (canDeposit) else depositEnabled
1626
- canWithdraw = self.safe_value(chain, 'canWd')
1627
+ canWithdraw = self.safe_bool(chain, 'canWd')
1627
1628
  withdrawEnabled = canWithdraw if (canWithdraw) else withdrawEnabled
1628
- canInternal = self.safe_value(chain, 'canInternal')
1629
+ canInternal = self.safe_bool(chain, 'canInternal')
1629
1630
  active = True if (canDeposit and canWithdraw and canInternal) else False
1630
1631
  currencyActive = active if (active) else currencyActive
1631
1632
  networkId = self.safe_string(chain, 'chain')
@@ -1654,7 +1655,7 @@ class okx(Exchange, ImplicitAPI):
1654
1655
  },
1655
1656
  'info': chain,
1656
1657
  }
1657
- firstChain = self.safe_value(chains, 0)
1658
+ firstChain = self.safe_dict(chains, 0, {})
1658
1659
  result[code] = {
1659
1660
  'info': None,
1660
1661
  'code': code,
@@ -1723,8 +1724,8 @@ class okx(Exchange, ImplicitAPI):
1723
1724
  # ]
1724
1725
  # }
1725
1726
  #
1726
- data = self.safe_value(response, 'data', [])
1727
- first = self.safe_value(data, 0, {})
1727
+ data = self.safe_list(response, 'data', [])
1728
+ first = self.safe_dict(data, 0, {})
1728
1729
  timestamp = self.safe_integer(first, 'ts')
1729
1730
  return self.parse_order_book(first, symbol, timestamp)
1730
1731
 
@@ -1823,7 +1824,7 @@ class okx(Exchange, ImplicitAPI):
1823
1824
  # ]
1824
1825
  # }
1825
1826
  #
1826
- data = self.safe_value(response, 'data', [])
1827
+ data = self.safe_list(response, 'data', [])
1827
1828
  first = self.safe_dict(data, 0, {})
1828
1829
  return self.parse_ticker(first, market)
1829
1830
 
@@ -1844,7 +1845,7 @@ class okx(Exchange, ImplicitAPI):
1844
1845
  'instType': self.convert_to_instrument_type(marketType),
1845
1846
  }
1846
1847
  if marketType == 'option':
1847
- defaultUnderlying = self.safe_value(self.options, 'defaultUnderlying', 'BTC-USD')
1848
+ defaultUnderlying = self.safe_string(self.options, 'defaultUnderlying', 'BTC-USD')
1848
1849
  currencyId = self.safe_string_2(params, 'uly', 'marketId', defaultUnderlying)
1849
1850
  if currencyId is None:
1850
1851
  raise ArgumentsRequired(self.id + ' fetchTickers() requires an underlying uly or marketId parameter for options markets')
@@ -2093,7 +2094,7 @@ class okx(Exchange, ImplicitAPI):
2093
2094
  return self.fetch_paginated_call_deterministic('fetchOHLCV', symbol, since, limit, timeframe, params, 200)
2094
2095
  price = self.safe_string(params, 'price')
2095
2096
  params = self.omit(params, 'price')
2096
- options = self.safe_value(self.options, 'fetchOHLCV', {})
2097
+ options = self.safe_dict(self.options, 'fetchOHLCV', {})
2097
2098
  timezone = self.safe_string(options, 'timezone', 'UTC')
2098
2099
  if limit is None:
2099
2100
  limit = 100 # default 100, max 100
@@ -2206,7 +2207,7 @@ class okx(Exchange, ImplicitAPI):
2206
2207
  # }
2207
2208
  #
2208
2209
  rates = []
2209
- data = self.safe_value(response, 'data', [])
2210
+ data = self.safe_list(response, 'data', [])
2210
2211
  for i in range(0, len(data)):
2211
2212
  rate = data[i]
2212
2213
  timestamp = self.safe_integer(rate, 'fundingTime')
@@ -2228,10 +2229,10 @@ class okx(Exchange, ImplicitAPI):
2228
2229
 
2229
2230
  def parse_trading_balance(self, response):
2230
2231
  result = {'info': response}
2231
- data = self.safe_value(response, 'data', [])
2232
- first = self.safe_value(data, 0, {})
2232
+ data = self.safe_list(response, 'data', [])
2233
+ first = self.safe_dict(data, 0, {})
2233
2234
  timestamp = self.safe_integer(first, 'uTime')
2234
- details = self.safe_value(first, 'details', [])
2235
+ details = self.safe_list(first, 'details', [])
2235
2236
  for i in range(0, len(details)):
2236
2237
  balance = details[i]
2237
2238
  currencyId = self.safe_string(balance, 'ccy')
@@ -2253,7 +2254,7 @@ class okx(Exchange, ImplicitAPI):
2253
2254
 
2254
2255
  def parse_funding_balance(self, response):
2255
2256
  result = {'info': response}
2256
- data = self.safe_value(response, 'data', [])
2257
+ data = self.safe_list(response, 'data', [])
2257
2258
  for i in range(0, len(data)):
2258
2259
  balance = data[i]
2259
2260
  currencyId = self.safe_string(balance, 'ccy')
@@ -2331,8 +2332,8 @@ class okx(Exchange, ImplicitAPI):
2331
2332
  # "msg": ""
2332
2333
  # }
2333
2334
  #
2334
- data = self.safe_value(response, 'data', [])
2335
- first = self.safe_value(data, 0, {})
2335
+ data = self.safe_list(response, 'data', [])
2336
+ first = self.safe_dict(data, 0, {})
2336
2337
  return self.parse_trading_fee(first, market)
2337
2338
 
2338
2339
  def fetch_balance(self, params={}) -> Balances:
@@ -2746,8 +2747,8 @@ class okx(Exchange, ImplicitAPI):
2746
2747
  response = self.privatePostTradeOrderAlgo(request)
2747
2748
  else:
2748
2749
  response = self.privatePostTradeBatchOrders(request)
2749
- data = self.safe_value(response, 'data', [])
2750
- first = self.safe_value(data, 0)
2750
+ data = self.safe_list(response, 'data', [])
2751
+ first = self.safe_dict(data, 0, {})
2751
2752
  order = self.parse_order(first, market)
2752
2753
  order['type'] = type
2753
2754
  order['side'] = side
@@ -4126,7 +4127,7 @@ class okx(Exchange, ImplicitAPI):
4126
4127
  # ]
4127
4128
  # }
4128
4129
  #
4129
- data = self.safe_value(response, 'data', [])
4130
+ data = self.safe_list(response, 'data', [])
4130
4131
  return self.parse_ledger(data, currency, since, limit)
4131
4132
 
4132
4133
  def parse_ledger_entry_type(self, type):
@@ -6035,9 +6036,9 @@ class okx(Exchange, ImplicitAPI):
6035
6036
  # }
6036
6037
  #
6037
6038
  data = self.safe_list(response, 'data', [])
6039
+ entry = self.safe_dict(data, 0, {})
6038
6040
  errorCode = self.safe_string(response, 'code')
6039
- item = self.safe_dict(data, 0, {})
6040
- return self.extend(self.parse_margin_modification(item, market), {
6041
+ return self.extend(self.parse_margin_modification(entry, market), {
6041
6042
  'status': 'ok' if (errorCode == '0') else 'failed',
6042
6043
  })
6043
6044
 
@@ -6052,22 +6053,66 @@ class okx(Exchange, ImplicitAPI):
6052
6053
  # "type": "reduce"
6053
6054
  # }
6054
6055
  #
6055
- amountRaw = self.safe_number(data, 'amt')
6056
+ # fetchMarginAdjustmentHistory
6057
+ #
6058
+ # {
6059
+ # bal: '67621.4325135010619812',
6060
+ # balChg: '-10.0000000000000000',
6061
+ # billId: '691293628710342659',
6062
+ # ccy: 'USDT',
6063
+ # clOrdId: '',
6064
+ # execType: '',
6065
+ # fee: '0',
6066
+ # fillFwdPx: '',
6067
+ # fillIdxPx: '',
6068
+ # fillMarkPx: '',
6069
+ # fillMarkVol: '',
6070
+ # fillPxUsd: '',
6071
+ # fillPxVol: '',
6072
+ # fillTime: '1711089244850',
6073
+ # from: '',
6074
+ # instId: 'XRP-USDT-SWAP',
6075
+ # instType: 'SWAP',
6076
+ # interest: '0',
6077
+ # mgnMode: 'isolated',
6078
+ # notes: '',
6079
+ # ordId: '',
6080
+ # pnl: '0',
6081
+ # posBal: '73.12',
6082
+ # posBalChg: '10.00',
6083
+ # px: '',
6084
+ # subType: '160',
6085
+ # sz: '10',
6086
+ # tag: '',
6087
+ # to: '',
6088
+ # tradeId: '0',
6089
+ # ts: '1711089244699',
6090
+ # type: '6'
6091
+ # }
6092
+ #
6093
+ amountRaw = self.safe_string_2(data, 'amt', 'posBalChg')
6056
6094
  typeRaw = self.safe_string(data, 'type')
6057
- type = 'reduce' if (typeRaw == 'reduce') else 'add'
6095
+ type = None
6096
+ if typeRaw == '6':
6097
+ type = 'add' if Precise.string_gt(amountRaw, '0') else 'reduce'
6098
+ else:
6099
+ type = typeRaw
6100
+ amount = Precise.string_abs(amountRaw)
6058
6101
  marketId = self.safe_string(data, 'instId')
6059
6102
  responseMarket = self.safe_market(marketId, market)
6060
6103
  code = responseMarket['base'] if responseMarket['inverse'] else responseMarket['quote']
6104
+ timestamp = self.safe_integer(data, 'ts')
6061
6105
  return {
6062
6106
  'info': data,
6063
6107
  'symbol': responseMarket['symbol'],
6064
6108
  'type': type,
6065
- 'amount': amountRaw,
6066
- 'total': None,
6109
+ 'marginMode': 'isolated',
6110
+ 'amount': self.parse_number(amount),
6067
6111
  'code': code,
6112
+ 'total': None,
6068
6113
  'status': None,
6069
- 'timestamp': None,
6070
- 'datetime': None,
6114
+ 'timestamp': timestamp,
6115
+ 'datetime': self.iso8601(timestamp),
6071
6116
  }
6072
6117
 
6073
6118
  def reduce_margin(self, symbol: str, amount, params={}) -> MarginModification:
@@ -7088,3 +7133,93 @@ class okx(Exchange, ImplicitAPI):
7088
7133
  self.throw_exactly_matched_exception(self.exceptions['exact'], code, feedback)
7089
7134
  raise ExchangeError(feedback) # unknown message
7090
7135
  return None
7136
+
7137
+ def fetch_margin_adjustment_history(self, symbol: Str = None, type: Str = None, since: Num = None, limit: Num = None, params={}) -> List[MarginModification]:
7138
+ """
7139
+ fetches the history of margin added or reduced from contract isolated positions
7140
+ :see: https://www.okx.com/docs-v5/en/#trading-account-rest-api-get-bills-details-last-7-days
7141
+ :see: https://www.okx.com/docs-v5/en/#trading-account-rest-api-get-bills-details-last-3-months
7142
+ :param str [symbol]: not used by okx fetchMarginAdjustmentHistory
7143
+ :param str [type]: "add" or "reduce"
7144
+ :param dict params: extra parameters specific to the exchange api endpoint
7145
+ :param boolean [params.auto]: True if fetching auto margin increases
7146
+ :returns dict[]: a list of `margin structures <https://docs.ccxt.com/#/?id=margin-loan-structure>`
7147
+ """
7148
+ self.load_markets()
7149
+ auto = self.safe_bool(params, 'auto')
7150
+ if type is None:
7151
+ raise ArgumentsRequired(self.id + ' fetchMarginAdjustmentHistory() requires a type argument')
7152
+ isAdd = type == 'add'
7153
+ subType = '160' if isAdd else '161'
7154
+ if auto:
7155
+ if isAdd:
7156
+ subType = '162'
7157
+ else:
7158
+ raise BadRequest(self.id + ' cannot fetch margin adjustments for type ' + type)
7159
+ request = {
7160
+ 'subType': subType,
7161
+ 'mgnMode': 'isolated',
7162
+ }
7163
+ until = self.safe_integer(params, 'until')
7164
+ params = self.omit(params, 'until')
7165
+ if since is not None:
7166
+ request['startTime'] = since
7167
+ if limit is not None:
7168
+ request['limit'] = limit
7169
+ if until is not None:
7170
+ request['endTime'] = until
7171
+ response = None
7172
+ now = self.milliseconds()
7173
+ oneWeekAgo = now - 604800000
7174
+ threeMonthsAgo = now - 7776000000
7175
+ if (since is None) or (since > oneWeekAgo):
7176
+ response = self.privateGetAccountBills(self.extend(request, params))
7177
+ elif since > threeMonthsAgo:
7178
+ response = self.privateGetAccountBillsArchive(self.extend(request, params))
7179
+ else:
7180
+ raise BadRequest(self.id + ' fetchMarginAdjustmentHistory() cannot fetch margin adjustments older than 3 months')
7181
+ #
7182
+ # {
7183
+ # code: '0',
7184
+ # data: [
7185
+ # {
7186
+ # bal: '67621.4325135010619812',
7187
+ # balChg: '-10.0000000000000000',
7188
+ # billId: '691293628710342659',
7189
+ # ccy: 'USDT',
7190
+ # clOrdId: '',
7191
+ # execType: '',
7192
+ # fee: '0',
7193
+ # fillFwdPx: '',
7194
+ # fillIdxPx: '',
7195
+ # fillMarkPx: '',
7196
+ # fillMarkVol: '',
7197
+ # fillPxUsd: '',
7198
+ # fillPxVol: '',
7199
+ # fillTime: '1711089244850',
7200
+ # from: '',
7201
+ # instId: 'XRP-USDT-SWAP',
7202
+ # instType: 'SWAP',
7203
+ # interest: '0',
7204
+ # mgnMode: 'isolated',
7205
+ # notes: '',
7206
+ # ordId: '',
7207
+ # pnl: '0',
7208
+ # posBal: '73.12',
7209
+ # posBalChg: '10.00',
7210
+ # px: '',
7211
+ # subType: '160',
7212
+ # sz: '10',
7213
+ # tag: '',
7214
+ # to: '',
7215
+ # tradeId: '0',
7216
+ # ts: '1711089244699',
7217
+ # type: '6'
7218
+ # }
7219
+ # ],
7220
+ # msg: ''
7221
+ # }
7222
+ #
7223
+ data = self.safe_list(response, 'data')
7224
+ modifications = self.parse_margin_modifications(data)
7225
+ return self.filter_by_symbol_since_limit(modifications, symbol, since, limit)
ccxt/phemex.py CHANGED
@@ -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/pro/__init__.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
 
ccxt/pro/bitmex.py CHANGED
@@ -540,8 +540,8 @@ class bitmex(ccxt.async_support.bitmex):
540
540
  for i in range(0, len(marketIds)):
541
541
  marketId = marketIds[i]
542
542
  market = self.safe_market(marketId)
543
- messageHash = table + ':' + marketId
544
543
  symbol = market['symbol']
544
+ messageHash = table + ':' + symbol
545
545
  trades = self.parse_trades(dataByMarketIds[marketId], market)
546
546
  stored = self.safe_value(self.trades, symbol)
547
547
  if stored is None:
@@ -561,22 +561,7 @@ class bitmex(ccxt.async_support.bitmex):
561
561
  :param dict [params]: extra parameters specific to the exchange API endpoint
562
562
  :returns dict[]: a list of `trade structures <https://docs.ccxt.com/#/?id=public-trades>`
563
563
  """
564
- await self.load_markets()
565
- market = self.market(symbol)
566
- symbol = market['symbol']
567
- table = 'trade'
568
- messageHash = table + ':' + market['id']
569
- url = self.urls['api']['ws']
570
- request = {
571
- 'op': 'subscribe',
572
- 'args': [
573
- messageHash,
574
- ],
575
- }
576
- trades = await self.watch(url, messageHash, self.extend(request, params), messageHash)
577
- if self.newUpdates:
578
- limit = trades.getLimit(symbol, limit)
579
- return self.filter_by_since_limit(trades, since, limit, 'timestamp', True)
564
+ return await self.watch_trades_for_symbols([symbol], since, limit, params)
580
565
 
581
566
  async def authenticate(self, params={}):
582
567
  url = self.urls['api']['ws']
@@ -1171,6 +1156,39 @@ class bitmex(ccxt.async_support.bitmex):
1171
1156
  orderbook = await self.watch_multiple(url, messageHashes, self.deep_extend(request, params), topics)
1172
1157
  return orderbook.limit()
1173
1158
 
1159
+ async def watch_trades_for_symbols(self, symbols: List[str], since: Int = None, limit: Int = None, params={}) -> List[Trade]:
1160
+ """
1161
+ get the list of most recent trades for a list of symbols
1162
+ :param str[] symbols: unified symbol of the market to fetch trades for
1163
+ :param int [since]: timestamp in ms of the earliest trade to fetch
1164
+ :param int [limit]: the maximum amount of trades to fetch
1165
+ :param dict [params]: extra parameters specific to the exchange API endpoint
1166
+ :returns dict[]: a list of `trade structures <https://docs.ccxt.com/#/?id=public-trades>`
1167
+ """
1168
+ await self.load_markets()
1169
+ symbols = self.market_symbols(symbols, None, False)
1170
+ table = 'trade'
1171
+ topics = []
1172
+ messageHashes = []
1173
+ for i in range(0, len(symbols)):
1174
+ symbol = symbols[i]
1175
+ market = self.market(symbol)
1176
+ topic = table + ':' + market['id']
1177
+ topics.append(topic)
1178
+ messageHash = table + ':' + symbol
1179
+ messageHashes.append(messageHash)
1180
+ url = self.urls['api']['ws']
1181
+ request = {
1182
+ 'op': 'subscribe',
1183
+ 'args': topics,
1184
+ }
1185
+ trades = await self.watch_multiple(url, messageHashes, self.deep_extend(request, params), topics)
1186
+ if self.newUpdates:
1187
+ first = self.safe_value(trades, 0)
1188
+ tradeSymbol = self.safe_string(first, 'symbol')
1189
+ limit = trades.getLimit(tradeSymbol, limit)
1190
+ return self.filter_by_since_limit(trades, since, limit, 'timestamp', True)
1191
+
1174
1192
  async def watch_ohlcv(self, symbol: str, timeframe='1m', since: Int = None, limit: Int = None, params={}) -> List[list]:
1175
1193
  """
1176
1194
  watches historical candlestick data containing the open, high, low, and close price, and the volume of a market
ccxt/pro/kucoin.py CHANGED
@@ -25,6 +25,7 @@ class kucoin(ccxt.async_support.kucoin):
25
25
  'cancelOrderWs': False,
26
26
  'cancelOrdersWs': False,
27
27
  'cancelAllOrdersWs': False,
28
+ 'watchBidsAsks': True,
28
29
  'watchOrderBook': True,
29
30
  'watchOrders': True,
30
31
  'watchMyTrades': True,
@@ -273,6 +274,87 @@ class kucoin(ccxt.async_support.kucoin):
273
274
  if numTickers > 0:
274
275
  client.resolve(tickers, currentMessageHash)
275
276
 
277
+ async def watch_bids_asks(self, symbols: Strings = None, params={}) -> Tickers:
278
+ """
279
+ :see: https://www.kucoin.com/docs/websocket/spot-trading/public-channels/level1-bbo-market-data
280
+ watches best bid & ask for symbols
281
+ :param str[] symbols: unified symbol of the market to fetch the ticker for
282
+ :param dict [params]: extra parameters specific to the exchange API endpoint
283
+ :returns dict: a `ticker structure <https://docs.ccxt.com/#/?id=ticker-structure>`
284
+ """
285
+ ticker = await self.watch_multi_helper('watchBidsAsks', '/spotMarket/level1:', symbols, params)
286
+ if self.newUpdates:
287
+ tickers = {}
288
+ tickers[ticker['symbol']] = ticker
289
+ return tickers
290
+ return self.filter_by_array(self.bidsasks, 'symbol', symbols)
291
+
292
+ async def watch_multi_helper(self, methodName, channelName: str, symbols: Strings = None, params={}):
293
+ await self.load_markets()
294
+ symbols = self.market_symbols(symbols, None, False, True, False)
295
+ length = len(symbols)
296
+ if length > 100:
297
+ raise ArgumentsRequired(self.id + ' ' + methodName + '() accepts a maximum of 100 symbols')
298
+ messageHashes = []
299
+ for i in range(0, len(symbols)):
300
+ symbol = symbols[i]
301
+ market = self.market(symbol)
302
+ messageHashes.append('bidask@' + market['symbol'])
303
+ url = await self.negotiate(False)
304
+ marketIds = self.market_ids(symbols)
305
+ joined = ','.join(marketIds)
306
+ requestId = str(self.request_id())
307
+ request = {
308
+ 'id': requestId,
309
+ 'type': 'subscribe',
310
+ 'topic': channelName + joined,
311
+ 'response': True,
312
+ }
313
+ message = self.extend(request, params)
314
+ return await self.watch_multiple(url, messageHashes, message, messageHashes)
315
+
316
+ def handle_bid_ask(self, client: Client, message):
317
+ #
318
+ # arrives one symbol dict
319
+ #
320
+ # {
321
+ # topic: '/spotMarket/level1:ETH-USDT',
322
+ # type: 'message',
323
+ # data: {
324
+ # asks: ['3347.42', '2.0778387'],
325
+ # bids: ['3347.41', '6.0411697'],
326
+ # timestamp: 1712231142085
327
+ # },
328
+ # subject: 'level1'
329
+ # }
330
+ #
331
+ parsedTicker = self.parse_ws_bid_ask(message)
332
+ symbol = parsedTicker['symbol']
333
+ self.bidsasks[symbol] = parsedTicker
334
+ messageHash = 'bidask@' + symbol
335
+ client.resolve(parsedTicker, messageHash)
336
+
337
+ def parse_ws_bid_ask(self, ticker, market=None):
338
+ topic = self.safe_string(ticker, 'topic')
339
+ parts = topic.split(':')
340
+ marketId = parts[1]
341
+ market = self.safe_market(marketId, market)
342
+ symbol = self.safe_string(market, 'symbol')
343
+ data = self.safe_dict(ticker, 'data', {})
344
+ ask = self.safe_list(data, 'asks', [])
345
+ bid = self.safe_list(data, 'bids', [])
346
+ timestamp = self.safe_integer(data, 'timestamp')
347
+ return self.safe_ticker({
348
+ 'symbol': symbol,
349
+ 'timestamp': timestamp,
350
+ 'datetime': self.iso8601(timestamp),
351
+ 'ask': self.safe_number(ask, 0),
352
+ 'askVolume': self.safe_number(ask, 1),
353
+ 'bid': self.safe_number(bid, 0),
354
+ 'bidVolume': self.safe_number(bid, 1),
355
+ 'info': ticker,
356
+ }, market)
357
+
276
358
  async def watch_ohlcv(self, symbol: str, timeframe='1m', since: Int = None, limit: Int = None, params={}) -> List[list]:
277
359
  """
278
360
  watches historical candlestick data containing the open, high, low, and close price, and the volume of a market
@@ -625,6 +707,8 @@ class kucoin(ccxt.async_support.kucoin):
625
707
  # }
626
708
  #
627
709
  id = self.safe_string(message, 'id')
710
+ if not (id in client.subscriptions):
711
+ return
628
712
  subscriptionHash = self.safe_string(client.subscriptions, id)
629
713
  subscription = self.safe_value(client.subscriptions, subscriptionHash)
630
714
  del client.subscriptions[id]
@@ -978,6 +1062,7 @@ class kucoin(ccxt.async_support.kucoin):
978
1062
  return
979
1063
  subject = self.safe_string(message, 'subject')
980
1064
  methods = {
1065
+ 'level1': self.handle_bid_ask,
981
1066
  'level2': self.handle_order_book,
982
1067
  'trade.l2update': self.handle_order_book,
983
1068
  'trade.ticker': self.handle_ticker,