ccxt 4.4.34__py2.py3-none-any.whl → 4.4.36__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 (67) hide show
  1. ccxt/__init__.py +3 -1
  2. ccxt/abstract/bingx.py +1 -0
  3. ccxt/abstract/bitopro.py +1 -0
  4. ccxt/abstract/bitpanda.py +0 -12
  5. ccxt/abstract/bitrue.py +3 -3
  6. ccxt/abstract/bybit.py +15 -0
  7. ccxt/abstract/defx.py +69 -0
  8. ccxt/abstract/deribit.py +1 -0
  9. ccxt/abstract/gate.py +14 -0
  10. ccxt/abstract/gateio.py +14 -0
  11. ccxt/abstract/okx.py +1 -0
  12. ccxt/abstract/onetrading.py +0 -12
  13. ccxt/abstract/xt.py +5 -5
  14. ccxt/async_support/__init__.py +3 -1
  15. ccxt/async_support/base/exchange.py +1 -1
  16. ccxt/async_support/bingx.py +324 -138
  17. ccxt/async_support/bitfinex2.py +18 -13
  18. ccxt/async_support/bitmex.py +104 -2
  19. ccxt/async_support/bitopro.py +21 -4
  20. ccxt/async_support/bitrue.py +2 -2
  21. ccxt/async_support/bitso.py +2 -1
  22. ccxt/async_support/btcmarkets.py +3 -3
  23. ccxt/async_support/btcturk.py +19 -19
  24. ccxt/async_support/bybit.py +21 -1
  25. ccxt/async_support/defx.py +1981 -0
  26. ccxt/async_support/deribit.py +27 -12
  27. ccxt/async_support/gate.py +156 -39
  28. ccxt/async_support/htx.py +11 -2
  29. ccxt/async_support/hyperliquid.py +68 -11
  30. ccxt/async_support/idex.py +3 -4
  31. ccxt/async_support/kraken.py +97 -90
  32. ccxt/async_support/kucoin.py +1 -1
  33. ccxt/async_support/okx.py +1 -0
  34. ccxt/async_support/onetrading.py +47 -369
  35. ccxt/async_support/xt.py +10 -10
  36. ccxt/base/exchange.py +2 -1
  37. ccxt/bingx.py +324 -138
  38. ccxt/bitfinex2.py +18 -13
  39. ccxt/bitmex.py +104 -2
  40. ccxt/bitopro.py +21 -4
  41. ccxt/bitrue.py +2 -2
  42. ccxt/bitso.py +2 -1
  43. ccxt/btcmarkets.py +3 -3
  44. ccxt/btcturk.py +19 -19
  45. ccxt/bybit.py +21 -1
  46. ccxt/defx.py +1980 -0
  47. ccxt/deribit.py +27 -12
  48. ccxt/gate.py +156 -39
  49. ccxt/htx.py +11 -2
  50. ccxt/hyperliquid.py +68 -11
  51. ccxt/idex.py +3 -4
  52. ccxt/kraken.py +97 -90
  53. ccxt/kucoin.py +1 -1
  54. ccxt/okx.py +1 -0
  55. ccxt/onetrading.py +47 -369
  56. ccxt/pro/__init__.py +3 -1
  57. ccxt/pro/bitrue.py +13 -11
  58. ccxt/pro/defx.py +832 -0
  59. ccxt/pro/probit.py +54 -66
  60. ccxt/test/tests_async.py +44 -3
  61. ccxt/test/tests_sync.py +44 -3
  62. ccxt/xt.py +10 -10
  63. {ccxt-4.4.34.dist-info → ccxt-4.4.36.dist-info}/METADATA +7 -6
  64. {ccxt-4.4.34.dist-info → ccxt-4.4.36.dist-info}/RECORD +67 -63
  65. {ccxt-4.4.34.dist-info → ccxt-4.4.36.dist-info}/LICENSE.txt +0 -0
  66. {ccxt-4.4.34.dist-info → ccxt-4.4.36.dist-info}/WHEEL +0 -0
  67. {ccxt-4.4.34.dist-info → ccxt-4.4.36.dist-info}/top_level.txt +0 -0
ccxt/hyperliquid.py CHANGED
@@ -11,12 +11,14 @@ from typing import List
11
11
  from ccxt.base.errors import ExchangeError
12
12
  from ccxt.base.errors import ArgumentsRequired
13
13
  from ccxt.base.errors import BadRequest
14
+ from ccxt.base.errors import InsufficientFunds
14
15
  from ccxt.base.errors import InvalidOrder
15
16
  from ccxt.base.errors import OrderNotFound
16
17
  from ccxt.base.errors import NotSupported
17
18
  from ccxt.base.decimal_to_precision import ROUND
18
19
  from ccxt.base.decimal_to_precision import DECIMAL_PLACES
19
20
  from ccxt.base.decimal_to_precision import SIGNIFICANT_DIGITS
21
+ from ccxt.base.decimal_to_precision import TICK_SIZE
20
22
  from ccxt.base.precise import Precise
21
23
 
22
24
 
@@ -210,9 +212,11 @@ class hyperliquid(Exchange, ImplicitAPI):
210
212
  'User or API Wallet ': InvalidOrder,
211
213
  'Order has invalid size': InvalidOrder,
212
214
  'Order price cannot be more than 80% away from the reference price': InvalidOrder,
215
+ 'Order has zero size.': InvalidOrder,
216
+ 'Insufficient spot balance asset': InsufficientFunds,
213
217
  },
214
218
  },
215
- 'precisionMode': DECIMAL_PLACES,
219
+ 'precisionMode': TICK_SIZE,
216
220
  'commonCurrencies': {
217
221
  },
218
222
  'options': {
@@ -361,6 +365,48 @@ class hyperliquid(Exchange, ImplicitAPI):
361
365
  result.append(data)
362
366
  return self.parse_markets(result)
363
367
 
368
+ def calculate_price_precision(self, price: float, amountPrecision: float, maxDecimals: float):
369
+ """
370
+ Helper function to calculate the Hyperliquid DECIMAL_PLACES price precision
371
+ :param float price: the price to use in the calculation
372
+ :param int amountPrecision: the amountPrecision to use in the calculation
373
+ :param int maxDecimals: the maxDecimals to use in the calculation
374
+ :returns int: The calculated price precision
375
+ """
376
+ pricePrecision = 0
377
+ priceStr = self.number_to_string(price)
378
+ if priceStr is None:
379
+ return 0
380
+ priceSplitted = priceStr.split('.')
381
+ if Precise.string_eq(priceStr, '0'):
382
+ # Significant digits is always hasattr(self, 5) case
383
+ significantDigits = 5
384
+ # Integer digits is always hasattr(self, 0) case(0 doesn't count)
385
+ integerDigits = 0
386
+ # Calculate the price precision
387
+ pricePrecision = min(maxDecimals - amountPrecision, significantDigits - integerDigits)
388
+ elif Precise.string_gt(priceStr, '0') and Precise.string_lt(priceStr, '1'):
389
+ # Significant digits, always hasattr(self, 5) case
390
+ significantDigits = 5
391
+ # Get the part after the decimal separator
392
+ decimalPart = self.safe_string(priceSplitted, 1, '')
393
+ # Count the number of leading zeros in the decimal part
394
+ leadingZeros = 0
395
+ while((leadingZeros <= len(decimalPart)) and (decimalPart[leadingZeros] == '0')):
396
+ leadingZeros = leadingZeros + 1
397
+ # Calculate price precision based on leading zeros and significant digits
398
+ pricePrecision = leadingZeros + significantDigits
399
+ # Calculate the price precision based on maxDecimals - szDecimals and the calculated price precision from the previous step
400
+ pricePrecision = min(maxDecimals - amountPrecision, pricePrecision)
401
+ else:
402
+ # Count the numbers before the decimal separator
403
+ integerPart = self.safe_string(priceSplitted, 0, '')
404
+ # Get significant digits, take the max() of 5 and the integer digits count
405
+ significantDigits = max(5, len(integerPart))
406
+ # Calculate price precision based on maxDecimals - szDecimals and significantDigits - len(integerPart)
407
+ pricePrecision = min(maxDecimals - amountPrecision, significantDigits - len(integerPart))
408
+ return self.parse_to_int(pricePrecision)
409
+
364
410
  def fetch_spot_markets(self, params={}) -> List[Market]:
365
411
  """
366
412
  retrieves data on all spot markets for hyperliquid
@@ -450,7 +496,11 @@ class hyperliquid(Exchange, ImplicitAPI):
450
496
  symbol = base + '/' + quote
451
497
  innerBaseTokenInfo = self.safe_dict(baseTokenInfo, 'spec', baseTokenInfo)
452
498
  # innerQuoteTokenInfo = self.safe_dict(quoteTokenInfo, 'spec', quoteTokenInfo)
453
- amountPrecision = self.safe_integer(innerBaseTokenInfo, 'szDecimals')
499
+ amountPrecisionStr = self.safe_string(innerBaseTokenInfo, 'szDecimals')
500
+ amountPrecision = int(amountPrecisionStr)
501
+ price = self.safe_number(extraData, 'midPx')
502
+ pricePrecision = self.calculate_price_precision(price, amountPrecision, 8)
503
+ pricePrecisionStr = self.number_to_string(pricePrecision)
454
504
  # quotePrecision = self.parse_number(self.parse_precision(self.safe_string(innerQuoteTokenInfo, 'szDecimals')))
455
505
  baseId = self.number_to_string(i + 10000)
456
506
  markets.append(self.safe_market_structure({
@@ -481,8 +531,8 @@ class hyperliquid(Exchange, ImplicitAPI):
481
531
  'strike': None,
482
532
  'optionType': None,
483
533
  'precision': {
484
- 'amount': amountPrecision, # decimal places
485
- 'price': 8 - amountPrecision, # MAX_DECIMALS is 8
534
+ 'amount': self.parse_number(self.parse_precision(amountPrecisionStr)),
535
+ 'price': self.parse_number(self.parse_precision(pricePrecisionStr)),
486
536
  },
487
537
  'limits': {
488
538
  'leverage': {
@@ -543,7 +593,11 @@ class hyperliquid(Exchange, ImplicitAPI):
543
593
  fees = self.safe_dict(self.fees, 'swap', {})
544
594
  taker = self.safe_number(fees, 'taker')
545
595
  maker = self.safe_number(fees, 'maker')
546
- amountPrecision = self.safe_integer(market, 'szDecimals')
596
+ amountPrecisionStr = self.safe_string(market, 'szDecimals')
597
+ amountPrecision = int(amountPrecisionStr)
598
+ price = self.safe_number(market, 'markPx', 0)
599
+ pricePrecision = self.calculate_price_precision(price, amountPrecision, 6)
600
+ pricePrecisionStr = self.number_to_string(pricePrecision)
547
601
  return self.safe_market_structure({
548
602
  'id': baseId,
549
603
  'symbol': symbol,
@@ -571,8 +625,8 @@ class hyperliquid(Exchange, ImplicitAPI):
571
625
  'strike': None,
572
626
  'optionType': None,
573
627
  'precision': {
574
- 'amount': amountPrecision, # decimal places
575
- 'price': 6 - amountPrecision, # MAX_DECIMALS is 6
628
+ 'amount': self.parse_number(self.parse_precision(amountPrecisionStr)),
629
+ 'price': self.parse_number(self.parse_precision(pricePrecisionStr)),
576
630
  },
577
631
  'limits': {
578
632
  'leverage': {
@@ -1039,10 +1093,13 @@ class hyperliquid(Exchange, ImplicitAPI):
1039
1093
 
1040
1094
  def price_to_precision(self, symbol: str, price) -> str:
1041
1095
  market = self.market(symbol)
1042
- # https://hyperliquid.gitbook.io/hyperliquid-docs/for-developers/api/tick-and-lot-size
1043
- result = self.decimal_to_precision(price, ROUND, 5, SIGNIFICANT_DIGITS, self.paddingMode)
1044
- decimalParsedResult = self.decimal_to_precision(result, ROUND, market['precision']['price'], self.precisionMode, self.paddingMode)
1045
- return decimalParsedResult
1096
+ priceStr = self.number_to_string(price)
1097
+ integerPart = priceStr.split('.')[0]
1098
+ significantDigits = max(5, len(integerPart))
1099
+ result = self.decimal_to_precision(price, ROUND, significantDigits, SIGNIFICANT_DIGITS, self.paddingMode)
1100
+ maxDecimals = 8 if market['spot'] else 6
1101
+ subtractedValue = maxDecimals - self.precision_from_string(self.safe_string(market['precision'], 'amount'))
1102
+ return self.decimal_to_precision(result, ROUND, subtractedValue, DECIMAL_PLACES, self.paddingMode)
1046
1103
 
1047
1104
  def hash_message(self, message):
1048
1105
  return '0x' + self.hash(message, 'keccak', 'hex')
ccxt/idex.py CHANGED
@@ -19,7 +19,6 @@ from ccxt.base.errors import DDoSProtection
19
19
  from ccxt.base.errors import ExchangeNotAvailable
20
20
  from ccxt.base.decimal_to_precision import ROUND
21
21
  from ccxt.base.decimal_to_precision import TRUNCATE
22
- from ccxt.base.decimal_to_precision import DECIMAL_PLACES
23
22
  from ccxt.base.decimal_to_precision import TICK_SIZE
24
23
  from ccxt.base.decimal_to_precision import PAD_WITH_ZERO
25
24
  from ccxt.base.precise import Precise
@@ -206,10 +205,8 @@ class idex(Exchange, ImplicitAPI):
206
205
  # {"code":"INVALID_PARAMETER","message":"invalid value provided for request parameter \"price\": all quantities and prices must be below 100 billion, above 0, need to be provided, and always require 4 decimals ending with 4 zeroes"}
207
206
  #
208
207
  market = self.market(symbol)
209
- info = self.safe_value(market, 'info', {})
210
- quoteAssetPrecision = self.safe_integer(info, 'quoteAssetPrecision')
211
208
  price = self.decimal_to_precision(price, ROUND, market['precision']['price'], self.precisionMode)
212
- return self.decimal_to_precision(price, TRUNCATE, quoteAssetPrecision, DECIMAL_PLACES, PAD_WITH_ZERO)
209
+ return self.decimal_to_precision(price, TRUNCATE, market['precision']['quote'], TICK_SIZE, PAD_WITH_ZERO)
213
210
 
214
211
  def fetch_markets(self, params={}) -> List[Market]:
215
212
  """
@@ -316,6 +313,8 @@ class idex(Exchange, ImplicitAPI):
316
313
  'precision': {
317
314
  'amount': basePrecision,
318
315
  'price': self.safe_number(entry, 'tickSize'),
316
+ 'base': basePrecision,
317
+ 'quote': quotePrecision,
319
318
  },
320
319
  'limits': {
321
320
  'leverage': {
ccxt/kraken.py CHANGED
@@ -447,31 +447,43 @@ class kraken(Exchange, ImplicitAPI):
447
447
  },
448
448
  'precisionMode': TICK_SIZE,
449
449
  'exceptions': {
450
- 'EQuery:Invalid asset pair': BadSymbol, # {"error":["EQuery:Invalid asset pair"]}
451
- 'EAPI:Invalid key': AuthenticationError,
452
- 'EFunding:Unknown withdraw key': InvalidAddress, # {"error":["EFunding:Unknown withdraw key"]}
453
- 'EFunding:Invalid amount': InsufficientFunds,
454
- 'EService:Unavailable': ExchangeNotAvailable,
455
- 'EDatabase:Internal error': ExchangeNotAvailable,
456
- 'EService:Busy': ExchangeNotAvailable,
457
- 'EQuery:Unknown asset': BadSymbol, # {"error":["EQuery:Unknown asset"]}
458
- 'EAPI:Rate limit exceeded': DDoSProtection,
459
- 'EOrder:Rate limit exceeded': DDoSProtection,
460
- 'EGeneral:Internal error': ExchangeNotAvailable,
461
- 'EGeneral:Temporary lockout': DDoSProtection,
462
- 'EGeneral:Permission denied': PermissionDenied,
463
- 'EGeneral:Invalid arguments:price': InvalidOrder,
464
- 'EOrder:Unknown order': InvalidOrder,
465
- 'EOrder:Invalid price:Invalid price argument': InvalidOrder,
466
- 'EOrder:Order minimum not met': InvalidOrder,
467
- 'EGeneral:Invalid arguments': BadRequest,
468
- 'ESession:Invalid session': AuthenticationError,
469
- 'EAPI:Invalid nonce': InvalidNonce,
470
- 'EFunding:No funding method': BadRequest, # {"error":"EFunding:No funding method"}
471
- 'EFunding:Unknown asset': BadSymbol, # {"error":["EFunding:Unknown asset"]}
472
- 'EService:Market in post_only mode': OnMaintenance, # {"error":["EService:Market in post_only mode"]}
473
- 'EGeneral:Too many requests': DDoSProtection, # {"error":["EGeneral:Too many requests"]}
474
- 'ETrade:User Locked': AccountSuspended, # {"error":["ETrade:User Locked"]}
450
+ 'exact': {
451
+ 'EQuery:Invalid asset pair': BadSymbol, # {"error":["EQuery:Invalid asset pair"]}
452
+ 'EAPI:Invalid key': AuthenticationError,
453
+ 'EFunding:Unknown withdraw key': InvalidAddress, # {"error":["EFunding:Unknown withdraw key"]}
454
+ 'EFunding:Invalid amount': InsufficientFunds,
455
+ 'EService:Unavailable': ExchangeNotAvailable,
456
+ 'EDatabase:Internal error': ExchangeNotAvailable,
457
+ 'EService:Busy': ExchangeNotAvailable,
458
+ 'EQuery:Unknown asset': BadSymbol, # {"error":["EQuery:Unknown asset"]}
459
+ 'EAPI:Rate limit exceeded': DDoSProtection,
460
+ 'EOrder:Rate limit exceeded': DDoSProtection,
461
+ 'EGeneral:Internal error': ExchangeNotAvailable,
462
+ 'EGeneral:Temporary lockout': DDoSProtection,
463
+ 'EGeneral:Permission denied': PermissionDenied,
464
+ 'EGeneral:Invalid arguments:price': InvalidOrder,
465
+ 'EOrder:Unknown order': InvalidOrder,
466
+ 'EOrder:Invalid price:Invalid price argument': InvalidOrder,
467
+ 'EOrder:Order minimum not met': InvalidOrder,
468
+ 'EOrder:Insufficient funds': InsufficientFunds,
469
+ 'EGeneral:Invalid arguments': BadRequest,
470
+ 'ESession:Invalid session': AuthenticationError,
471
+ 'EAPI:Invalid nonce': InvalidNonce,
472
+ 'EFunding:No funding method': BadRequest, # {"error":"EFunding:No funding method"}
473
+ 'EFunding:Unknown asset': BadSymbol, # {"error":["EFunding:Unknown asset"]}
474
+ 'EService:Market in post_only mode': OnMaintenance, # {"error":["EService:Market in post_only mode"]}
475
+ 'EGeneral:Too many requests': DDoSProtection, # {"error":["EGeneral:Too many requests"]}
476
+ 'ETrade:User Locked': AccountSuspended, # {"error":["ETrade:User Locked"]}
477
+ },
478
+ 'broad': {
479
+ ':Invalid order': InvalidOrder,
480
+ ':Invalid arguments:volume': InvalidOrder,
481
+ ':Invalid arguments:viqc': InvalidOrder,
482
+ ':Invalid nonce': InvalidNonce,
483
+ ':IInsufficient funds': InsufficientFunds,
484
+ ':Cancel pending': CancelPending,
485
+ ':Rate limit exceeded': RateLimitExceeded,
486
+ },
475
487
  },
476
488
  })
477
489
 
@@ -1553,18 +1565,9 @@ class kraken(Exchange, ImplicitAPI):
1553
1565
  # editOrder
1554
1566
  #
1555
1567
  # {
1556
- # "status": "ok",
1557
- # "txid": "OAW2BO-7RWEK-PZY5UO",
1558
- # "originaltxid": "OXL6SS-UPNMC-26WBE7",
1559
- # "newuserref": 1234,
1560
- # "olduserref": 123,
1561
- # "volume": "0.00075000",
1562
- # "price": "13500.0",
1563
- # "orders_cancelled": 1,
1564
- # "descr": {
1565
- # "order": "buy 0.00075000 XBTUSDT @ limit 13500.0"
1566
- # }
1568
+ # "amend_id": "TJSMEH-AA67V-YUSQ6O"
1567
1569
  # }
1570
+ #
1568
1571
  # ws - createOrder
1569
1572
  # {
1570
1573
  # "descr": 'sell 0.00010000 XBTUSDT @ market',
@@ -1687,11 +1690,11 @@ class kraken(Exchange, ImplicitAPI):
1687
1690
  elif flags.find('fcib') >= 0:
1688
1691
  fee['currency'] = market['base']
1689
1692
  status = self.parse_order_status(self.safe_string(order, 'status'))
1690
- id = self.safe_string_2(order, 'id', 'txid')
1693
+ id = self.safe_string_n(order, ['id', 'txid', 'amend_id'])
1691
1694
  if (id is None) or (id.startswith('[')):
1692
1695
  txid = self.safe_list(order, 'txid')
1693
1696
  id = self.safe_string(txid, 0)
1694
- clientOrderId = self.safe_string_2(order, 'userref', 'newuserref')
1697
+ clientOrderId = self.safe_string(order, 'userref')
1695
1698
  rawTrades = self.safe_value(order, 'trades', [])
1696
1699
  trades = []
1697
1700
  for i in range(0, len(rawTrades)):
@@ -1706,22 +1709,26 @@ class kraken(Exchange, ImplicitAPI):
1706
1709
  takeProfitPrice = None
1707
1710
  # the dashed strings are not provided from fields(eg. fetch order)
1708
1711
  # while spaced strings from "order" sentence(when other fields not available)
1709
- if rawType.startswith('take-profit'):
1710
- takeProfitPrice = self.safe_string(description, 'price')
1711
- price = self.omit_zero(self.safe_string(description, 'price2'))
1712
- elif rawType.startswith('stop-loss'):
1713
- stopLossPrice = self.safe_string(description, 'price')
1714
- price = self.omit_zero(self.safe_string(description, 'price2'))
1715
- elif rawType == 'take profit':
1716
- takeProfitPrice = triggerPrice
1717
- elif rawType == 'stop loss':
1718
- stopLossPrice = triggerPrice
1712
+ if rawType is not None:
1713
+ if rawType.startswith('take-profit'):
1714
+ takeProfitPrice = self.safe_string(description, 'price')
1715
+ price = self.omit_zero(self.safe_string(description, 'price2'))
1716
+ elif rawType.startswith('stop-loss'):
1717
+ stopLossPrice = self.safe_string(description, 'price')
1718
+ price = self.omit_zero(self.safe_string(description, 'price2'))
1719
+ elif rawType == 'take profit':
1720
+ takeProfitPrice = triggerPrice
1721
+ elif rawType == 'stop loss':
1722
+ stopLossPrice = triggerPrice
1719
1723
  finalType = self.parse_order_type(rawType)
1720
1724
  # unlike from endpoints which provide eg: "take-profit-limit"
1721
1725
  # for "space-delimited" orders we dont have market/limit suffixes, their format is
1722
1726
  # eg: `stop loss > limit 123`, so we need to parse them manually
1723
1727
  if self.in_array(finalType, ['stop loss', 'take profit']):
1724
1728
  finalType = 'market' if (price is None) else 'limit'
1729
+ amendId = self.safe_string(order, 'amend_id')
1730
+ if amendId is not None:
1731
+ isPostOnly = None
1725
1732
  return self.safe_order({
1726
1733
  'id': id,
1727
1734
  'clientOrderId': clientOrderId,
@@ -1751,10 +1758,10 @@ class kraken(Exchange, ImplicitAPI):
1751
1758
  }, market)
1752
1759
 
1753
1760
  def order_request(self, method: str, symbol: str, type: str, request: dict, amount: Num, price: Num = None, params={}):
1754
- clientOrderId = self.safe_string_2(params, 'userref', 'clientOrderId')
1755
- params = self.omit(params, ['userref', 'clientOrderId'])
1761
+ clientOrderId = self.safe_string(params, 'clientOrderId')
1762
+ params = self.omit(params, ['clientOrderId'])
1756
1763
  if clientOrderId is not None:
1757
- request['userref'] = clientOrderId
1764
+ request['cl_ord_id'] = clientOrderId
1758
1765
  stopLossTriggerPrice = self.safe_string(params, 'stopLossPrice')
1759
1766
  takeProfitTriggerPrice = self.safe_string(params, 'takeProfitPrice')
1760
1767
  isStopLossTriggerOrder = stopLossTriggerPrice is not None
@@ -1854,21 +1861,24 @@ class kraken(Exchange, ImplicitAPI):
1854
1861
  """
1855
1862
  edit a trade order
1856
1863
 
1857
- https://docs.kraken.com/rest/#tag/Spot-Trading/operation/editOrder
1864
+ https://docs.kraken.com/api/docs/rest-api/amend-order
1858
1865
 
1859
1866
  :param str id: order id
1860
1867
  :param str symbol: unified symbol of the market to create an order in
1861
1868
  :param str type: 'market' or 'limit'
1862
1869
  :param str side: 'buy' or 'sell'
1863
- :param float amount: how much of the currency you want to trade in units of the base currency
1870
+ :param float [amount]: how much of the currency you want to trade in units of the base currency
1864
1871
  :param float [price]: the price at which the order is to be fulfilled, in units of the quote currency, ignored in market orders
1865
1872
  :param dict [params]: extra parameters specific to the exchange API endpoint
1866
- :param float [params.stopLossPrice]: *margin only* the price that a stop loss order is triggered at
1867
- :param float [params.takeProfitPrice]: *margin only* the price that a take profit order is triggered at
1868
- :param str [params.trailingAmount]: *margin only* the quote price away from the current market price
1869
- :param str [params.trailingLimitAmount]: *margin only* the quote amount away from the trailingAmount
1870
- :param str [params.offset]: *margin only* '+' or '-' whether you want the trailingLimitAmount value to be positive or negative, default is negative '-'
1871
- :param str [params.trigger]: *margin only* the activation price type, 'last' or 'index', default is 'last'
1873
+ :param float [params.stopLossPrice]: the price that a stop loss order is triggered at
1874
+ :param float [params.takeProfitPrice]: the price that a take profit order is triggered at
1875
+ :param str [params.trailingAmount]: the quote amount to trail away from the current market price
1876
+ :param str [params.trailingPercent]: the percent to trail away from the current market price
1877
+ :param str [params.trailingLimitAmount]: the quote amount away from the trailingAmount
1878
+ :param str [params.trailingLimitPercent]: the percent away from the trailingAmount
1879
+ :param str [params.offset]: '+' or '-' whether you want the trailingLimitAmount value to be positive or negative
1880
+ :param boolean [params.postOnly]: if True, the order will only be posted to the order book and not executed immediately
1881
+ :param str [params.clientOrderId]: the orders client order id
1872
1882
  :returns dict: an `order structure <https://docs.ccxt.com/#/?id=order-structure>`
1873
1883
  """
1874
1884
  self.load_markets()
@@ -1877,30 +1887,41 @@ class kraken(Exchange, ImplicitAPI):
1877
1887
  raise NotSupported(self.id + ' editOrder() does not support ' + market['type'] + ' orders, only spot orders are accepted')
1878
1888
  request: dict = {
1879
1889
  'txid': id,
1880
- 'pair': market['id'],
1881
1890
  }
1891
+ clientOrderId = self.safe_string(params, 'clientOrderId')
1892
+ if clientOrderId is not None:
1893
+ request['cl_ord_id'] = clientOrderId
1894
+ params = self.omit(params, 'clientOrderId')
1895
+ request = self.omit(request, 'txid')
1896
+ isMarket = (type == 'market')
1897
+ postOnly = None
1898
+ postOnly, params = self.handle_post_only(isMarket, False, params)
1899
+ if postOnly:
1900
+ request['post_only'] = 'true' # not using hasattr(self, boolean) case, because the urlencodedNested transforms it into 'True' string
1882
1901
  if amount is not None:
1883
- request['volume'] = self.amount_to_precision(symbol, amount)
1884
- orderRequest = self.order_request('editOrder', symbol, type, request, amount, price, params)
1885
- response = self.privatePostEditOrder(self.extend(orderRequest[0], orderRequest[1]))
1902
+ request['order_qty'] = self.amount_to_precision(symbol, amount)
1903
+ if price is not None:
1904
+ request['limit_price'] = self.price_to_precision(symbol, price)
1905
+ allTriggerPrices = self.safe_string_n(params, ['stopLossPrice', 'takeProfitPrice', 'trailingAmount', 'trailingPercent', 'trailingLimitAmount', 'trailingLimitPercent'])
1906
+ if allTriggerPrices is not None:
1907
+ offset = self.safe_string(params, 'offset')
1908
+ params = self.omit(params, ['stopLossPrice', 'takeProfitPrice', 'trailingAmount', 'trailingPercent', 'trailingLimitAmount', 'trailingLimitPercent', 'offset'])
1909
+ if offset is not None:
1910
+ allTriggerPrices = offset + allTriggerPrices
1911
+ request['trigger_price'] = allTriggerPrices
1912
+ else:
1913
+ request['trigger_price'] = self.price_to_precision(symbol, allTriggerPrices)
1914
+ response = self.privatePostAmendOrder(self.extend(request, params))
1886
1915
  #
1887
1916
  # {
1888
1917
  # "error": [],
1889
1918
  # "result": {
1890
- # "status": "ok",
1891
- # "txid": "OAW2BO-7RWEK-PZY5UO",
1892
- # "originaltxid": "OXL6SS-UPNMC-26WBE7",
1893
- # "volume": "0.00075000",
1894
- # "price": "13500.0",
1895
- # "orders_cancelled": 1,
1896
- # "descr": {
1897
- # "order": "buy 0.00075000 XBTUSDT @ limit 13500.0"
1898
- # }
1919
+ # "amend_id": "TJSMEH-AA67V-YUSQ6O"
1899
1920
  # }
1900
1921
  # }
1901
1922
  #
1902
- data = self.safe_dict(response, 'result', {})
1903
- return self.parse_order(data, market)
1923
+ result = self.safe_dict(response, 'result', {})
1924
+ return self.parse_order(result, market)
1904
1925
 
1905
1926
  def fetch_order(self, id: str, symbol: Str = None, params={}):
1906
1927
  """
@@ -3055,21 +3076,6 @@ class kraken(Exchange, ImplicitAPI):
3055
3076
  def handle_errors(self, code: int, reason: str, url: str, method: str, headers: dict, body: str, response, requestHeaders, requestBody):
3056
3077
  if code == 520:
3057
3078
  raise ExchangeNotAvailable(self.id + ' ' + str(code) + ' ' + reason)
3058
- # todo: rewrite self for "broad" exceptions matching
3059
- if body.find('Invalid order') >= 0:
3060
- raise InvalidOrder(self.id + ' ' + body)
3061
- if body.find('Invalid nonce') >= 0:
3062
- raise InvalidNonce(self.id + ' ' + body)
3063
- if body.find('Insufficient funds') >= 0:
3064
- raise InsufficientFunds(self.id + ' ' + body)
3065
- if body.find('Cancel pending') >= 0:
3066
- raise CancelPending(self.id + ' ' + body)
3067
- if body.find('Invalid arguments:volume') >= 0:
3068
- raise InvalidOrder(self.id + ' ' + body)
3069
- if body.find('Invalid arguments:viqc') >= 0:
3070
- raise InvalidOrder(self.id + ' ' + body)
3071
- if body.find('Rate limit exceeded') >= 0:
3072
- raise RateLimitExceeded(self.id + ' ' + body)
3073
3079
  if response is None:
3074
3080
  return None
3075
3081
  if body[0] == '{':
@@ -3080,6 +3086,7 @@ class kraken(Exchange, ImplicitAPI):
3080
3086
  message = self.id + ' ' + body
3081
3087
  for i in range(0, len(response['error'])):
3082
3088
  error = response['error'][i]
3083
- self.throw_exactly_matched_exception(self.exceptions, error, message)
3089
+ self.throw_exactly_matched_exception(self.exceptions['exact'], error, message)
3090
+ self.throw_exactly_matched_exception(self.exceptions['broad'], error, message)
3084
3091
  raise ExchangeError(message)
3085
3092
  return None
ccxt/kucoin.py CHANGED
@@ -1199,7 +1199,7 @@ class kucoin(Exchange, ImplicitAPI):
1199
1199
  'type': 'spot',
1200
1200
  'spot': True,
1201
1201
  'margin': isMarginable,
1202
- 'marginMode': {
1202
+ 'marginModes': {
1203
1203
  'cross': hasCrossMargin,
1204
1204
  'isolated': hasIsolatedMargin,
1205
1205
  },
ccxt/okx.py CHANGED
@@ -351,6 +351,7 @@ class okx(Exchange, ImplicitAPI):
351
351
  'asset/convert/history': 5 / 3,
352
352
  'asset/monthly-statement': 2,
353
353
  # account
354
+ 'account/instruments': 1,
354
355
  'account/balance': 2,
355
356
  'account/positions': 2,
356
357
  'account/positions-history': 100,