ccxt 4.3.77__py2.py3-none-any.whl → 4.3.79__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.
ccxt/__init__.py CHANGED
@@ -22,7 +22,7 @@
22
22
 
23
23
  # ----------------------------------------------------------------------------
24
24
 
25
- __version__ = '4.3.77'
25
+ __version__ = '4.3.79'
26
26
 
27
27
  # ----------------------------------------------------------------------------
28
28
 
@@ -4,7 +4,7 @@
4
4
 
5
5
  # -----------------------------------------------------------------------------
6
6
 
7
- __version__ = '4.3.77'
7
+ __version__ = '4.3.79'
8
8
 
9
9
  # -----------------------------------------------------------------------------
10
10
 
@@ -2,7 +2,7 @@
2
2
 
3
3
  # -----------------------------------------------------------------------------
4
4
 
5
- __version__ = '4.3.77'
5
+ __version__ = '4.3.79'
6
6
 
7
7
  # -----------------------------------------------------------------------------
8
8
 
@@ -3473,7 +3473,7 @@ class binance(Exchange, ImplicitAPI):
3473
3473
  #
3474
3474
  # futures(fapi)
3475
3475
  #
3476
- # fapiPrivateV2GetAccount
3476
+ # fapiPrivateV3GetAccount
3477
3477
  #
3478
3478
  # {
3479
3479
  # "feeTier":0,
@@ -8378,6 +8378,7 @@ class binance(Exchange, ImplicitAPI):
8378
8378
  :see: https://developers.binance.com/docs/wallet/asset/trade-fee
8379
8379
  :see: https://developers.binance.com/docs/derivatives/usds-margined-futures/account/rest-api/Account-Information-V2
8380
8380
  :see: https://developers.binance.com/docs/derivatives/coin-margined-futures/account/Account-Information
8381
+ :see: https://developers.binance.com/docs/derivatives/usds-margined-futures/account/rest-api/Account-Config
8381
8382
  :param dict [params]: extra parameters specific to the exchange API endpoint
8382
8383
  :param str [params.subType]: "linear" or "inverse"
8383
8384
  :returns dict: a dictionary of `fee structures <https://docs.ccxt.com/#/?id=fee-structure>` indexed by market symbols
@@ -8394,7 +8395,7 @@ class binance(Exchange, ImplicitAPI):
8394
8395
  if isSpotOrMargin:
8395
8396
  response = await self.sapiGetAssetTradeFee(params)
8396
8397
  elif isLinear:
8397
- response = await self.fapiPrivateV2GetAccount(params)
8398
+ response = await self.fapiPrivateGetAccountConfig(params)
8398
8399
  elif isInverse:
8399
8400
  response = await self.dapiPrivateGetAccount(params)
8400
8401
  #
@@ -9576,6 +9577,7 @@ class binance(Exchange, ImplicitAPI):
9576
9577
  :see: https://developers.binance.com/docs/derivatives/coin-margined-futures/account/Account-Information
9577
9578
  :see: https://developers.binance.com/docs/derivatives/usds-margined-futures/trade/rest-api/Position-Information-V2
9578
9579
  :see: https://developers.binance.com/docs/derivatives/coin-margined-futures/trade/Position-Information
9580
+ :see: https://developers.binance.com/docs/derivatives/usds-margined-futures/account/rest-api/Account-Information-V3
9579
9581
  :param str[] [symbols]: list of unified market symbols
9580
9582
  :param dict [params]: extra parameters specific to the exchange API endpoint
9581
9583
  :param boolean [params.portfolioMargin]: set to True if you would like to fetch positions in a portfolio margin account
@@ -9694,6 +9696,7 @@ class binance(Exchange, ImplicitAPI):
9694
9696
  :see: https://developers.binance.com/docs/derivatives/coin-margined-futures/trade/Position-Information
9695
9697
  :see: https://developers.binance.com/docs/derivatives/portfolio-margin/account/Query-UM-Position-Information
9696
9698
  :see: https://developers.binance.com/docs/derivatives/portfolio-margin/account/Query-CM-Position-Information
9699
+ :see: https://developers.binance.com/docs/derivatives/usds-margined-futures/trade/rest-api/Position-Information-V3
9697
9700
  :param str[]|None symbols: list of unified market symbols
9698
9701
  :param dict [params]: extra parameters specific to the exchange API endpoint
9699
9702
  :param boolean [params.portfolioMargin]: set to True if you would like to fetch positions for a portfolio margin account
@@ -10049,6 +10052,7 @@ class binance(Exchange, ImplicitAPI):
10049
10052
  :see: https://developers.binance.com/docs/derivatives/coin-margined-futures/account/Account-Information
10050
10053
  :see: https://developers.binance.com/docs/derivatives/portfolio-margin/account/Get-UM-Account-Detail
10051
10054
  :see: https://developers.binance.com/docs/derivatives/portfolio-margin/account/Get-CM-Account-Detail
10055
+ :see: https://developers.binance.com/docs/derivatives/usds-margined-futures/account/rest-api/Symbol-Config
10052
10056
  :param str[] [symbols]: a list of unified market symbols
10053
10057
  :param dict [params]: extra parameters specific to the exchange API endpoint
10054
10058
  :param str [params.subType]: "linear" or "inverse"
@@ -10067,7 +10071,7 @@ class binance(Exchange, ImplicitAPI):
10067
10071
  if isPortfolioMargin:
10068
10072
  response = await self.papiGetUmAccount(params)
10069
10073
  else:
10070
- response = await self.fapiPrivateV2GetAccount(params)
10074
+ response = await self.fapiPrivateGetSymbolConfig(params)
10071
10075
  elif self.is_inverse(type, subType):
10072
10076
  if isPortfolioMargin:
10073
10077
  response = await self.papiGetCmAccount(params)
@@ -10076,6 +10080,8 @@ class binance(Exchange, ImplicitAPI):
10076
10080
  else:
10077
10081
  raise NotSupported(self.id + ' fetchLeverages() supports linear and inverse contracts only')
10078
10082
  leverages = self.safe_list(response, 'positions', [])
10083
+ if isinstance(response, list):
10084
+ leverages = response
10079
10085
  return self.parse_leverages(leverages, symbols, 'symbol')
10080
10086
 
10081
10087
  def parse_leverage(self, leverage: dict, market: Market = None) -> Leverage:
@@ -10084,6 +10090,9 @@ class binance(Exchange, ImplicitAPI):
10084
10090
  marginMode = None
10085
10091
  if marginModeRaw is not None:
10086
10092
  marginMode = 'isolated' if marginModeRaw else 'cross'
10093
+ marginTypeRaw = self.safe_string_lower(leverage, 'marginType')
10094
+ if marginTypeRaw is not None:
10095
+ marginMode = 'cross' if (marginTypeRaw == 'crossed') else 'isolated'
10087
10096
  side = self.safe_string_lower(leverage, 'positionSide')
10088
10097
  longLeverage = None
10089
10098
  shortLeverage = None
@@ -11763,6 +11772,7 @@ class binance(Exchange, ImplicitAPI):
11763
11772
  fetches margin modes("isolated" or "cross") that the market for the symbol in in, with symbol=None all markets for a subType(linear/inverse) are returned
11764
11773
  :see: https://developers.binance.com/docs/derivatives/coin-margined-futures/account/Account-Information
11765
11774
  :see: https://developers.binance.com/docs/derivatives/usds-margined-futures/account/rest-api/Account-Information-V2
11775
+ :see: https://developers.binance.com/docs/derivatives/usds-margined-futures/account/rest-api/Symbol-Config
11766
11776
  :param str symbol: unified symbol of the market the order was made in
11767
11777
  :param dict [params]: extra parameters specific to the exchange API endpoint
11768
11778
  :param str [params.subType]: "linear" or "inverse"
@@ -11777,70 +11787,17 @@ class binance(Exchange, ImplicitAPI):
11777
11787
  subType, params = self.handle_sub_type_and_params('fetchMarginMode', market, params)
11778
11788
  response = None
11779
11789
  if subType == 'linear':
11780
- response = await self.fapiPrivateV2GetAccount(params)
11790
+ response = await self.fapiPrivateGetSymbolConfig(params)
11781
11791
  #
11782
- # {
11783
- # feeTier: '0',
11784
- # canTrade: True,
11785
- # canDeposit: True,
11786
- # canWithdraw: True,
11787
- # tradeGroupId: '-1',
11788
- # updateTime: '0',
11789
- # multiAssetsMargin: True,
11790
- # totalInitialMargin: '438.31134352',
11791
- # totalMaintMargin: '5.90847101',
11792
- # totalWalletBalance: '4345.15626338',
11793
- # totalUnrealizedProfit: '376.45220224',
11794
- # totalMarginBalance: '4721.60846562',
11795
- # totalPositionInitialMargin: '425.45252687',
11796
- # totalOpenOrderInitialMargin: '12.85881664',
11797
- # totalCrossWalletBalance: '4345.15626338',
11798
- # totalCrossUnPnl: '376.45220224',
11799
- # availableBalance: '4281.84764041',
11800
- # maxWithdrawAmount: '4281.84764041',
11801
- # assets: [
11802
- # {
11803
- # asset: 'ETH',
11804
- # walletBalance: '0.00000000',
11805
- # unrealizedProfit: '0.00000000',
11806
- # marginBalance: '0.00000000',
11807
- # maintMargin: '0.00000000',
11808
- # initialMargin: '0.00000000',
11809
- # positionInitialMargin: '0.00000000',
11810
- # openOrderInitialMargin: '0.00000000',
11811
- # maxWithdrawAmount: '0.00000000',
11812
- # crossWalletBalance: '0.00000000',
11813
- # crossUnPnl: '0.00000000',
11814
- # availableBalance: '1.26075574',
11815
- # marginAvailable: True,
11816
- # updateTime: '0'
11817
- # },
11818
- # ...
11819
- # ],
11820
- # positions: [
11821
- # {
11822
- # symbol: 'SNTUSDT',
11823
- # initialMargin: '0',
11824
- # maintMargin: '0',
11825
- # unrealizedProfit: '0.00000000',
11826
- # positionInitialMargin: '0',
11827
- # openOrderInitialMargin: '0',
11828
- # leverage: '20',
11829
- # isolated: False,
11830
- # entryPrice: '0.0',
11831
- # breakEvenPrice: '0.0',
11832
- # maxNotional: '25000',
11833
- # positionSide: 'BOTH',
11834
- # positionAmt: '0',
11835
- # notional: '0',
11836
- # isolatedWallet: '0',
11837
- # updateTime: '0',
11838
- # bidNotional: '0',
11839
- # askNotional: '0'
11840
- # },
11841
- # ...
11842
- # ]
11843
- # }
11792
+ # [
11793
+ # {
11794
+ # "symbol": "BTCUSDT",
11795
+ # "marginType": "CROSSED",
11796
+ # "isAutoAddMargin": "false",
11797
+ # "leverage": 21,
11798
+ # "maxNotionalValue": "1000000",
11799
+ # }
11800
+ # ]
11844
11801
  #
11845
11802
  elif subType == 'inverse':
11846
11803
  response = await self.dapiPrivateGetAccount(params)
@@ -11895,16 +11852,24 @@ class binance(Exchange, ImplicitAPI):
11895
11852
  else:
11896
11853
  raise BadRequest(self.id + ' fetchMarginModes() supports linear and inverse subTypes only')
11897
11854
  assets = self.safe_list(response, 'positions', [])
11855
+ if isinstance(response, list):
11856
+ assets = response
11898
11857
  return self.parse_margin_modes(assets, symbols, 'symbol', 'swap')
11899
11858
 
11900
11859
  def parse_margin_mode(self, marginMode: dict, market=None) -> MarginMode:
11901
11860
  marketId = self.safe_string(marginMode, 'symbol')
11902
11861
  market = self.safe_market(marketId, market)
11903
- isIsolated = self.safe_bool(marginMode, 'isolated')
11862
+ marginModeRaw = self.safe_bool(marginMode, 'isolated')
11863
+ reMarginMode = None
11864
+ if marginModeRaw is not None:
11865
+ reMarginMode = 'isolated' if marginModeRaw else 'cross'
11866
+ marginTypeRaw = self.safe_string_lower(marginMode, 'marginType')
11867
+ if marginTypeRaw is not None:
11868
+ reMarginMode = 'cross' if (marginTypeRaw == 'crossed') else 'isolated'
11904
11869
  return {
11905
11870
  'info': marginMode,
11906
11871
  'symbol': market['symbol'],
11907
- 'marginMode': 'isolated' if isIsolated else 'cross',
11872
+ 'marginMode': reMarginMode,
11908
11873
  }
11909
11874
 
11910
11875
  async def fetch_option(self, symbol: str, params={}) -> Option:
@@ -1180,7 +1180,6 @@ class bingx(Exchange, ImplicitAPI):
1180
1180
  'fee': {
1181
1181
  'cost': self.parse_number(Precise.string_abs(self.safe_string_2(trade, 'commission', 'n'))),
1182
1182
  'currency': currencyCode,
1183
- 'rate': None,
1184
1183
  },
1185
1184
  }, market)
1186
1185
 
@@ -1331,6 +1331,7 @@ class bitget(Exchange, ImplicitAPI):
1331
1331
  'JADE': 'Jade Protocol',
1332
1332
  'DEGEN': 'DegenReborn',
1333
1333
  'TONCOIN': 'TON',
1334
+ 'OMNI': 'omni', # conflict with Omni Network
1334
1335
  },
1335
1336
  'options': {
1336
1337
  'timeframes': {
@@ -1886,7 +1886,6 @@ class bitteam(Exchange, ImplicitAPI):
1886
1886
  fee = {
1887
1887
  'currency': self.safe_currency_code(feeCurrencyId),
1888
1888
  'cost': feeCost,
1889
- 'rate': None,
1890
1889
  }
1891
1890
  intTs = self.parse_to_int(timestamp)
1892
1891
  return self.safe_trade({
@@ -3294,8 +3294,8 @@ class gate(Exchange, ImplicitAPI):
3294
3294
  side = self.safe_string_2(trade, 'side', 'type', contractSide)
3295
3295
  orderId = self.safe_string(trade, 'order_id')
3296
3296
  feeAmount = self.safe_string(trade, 'fee')
3297
- gtFee = self.safe_string(trade, 'gt_fee')
3298
- pointFee = self.safe_string(trade, 'point_fee')
3297
+ gtFee = self.omit_zero(self.safe_string(trade, 'gt_fee'))
3298
+ pointFee = self.omit_zero(self.safe_string(trade, 'point_fee'))
3299
3299
  fees = []
3300
3300
  if feeAmount is not None:
3301
3301
  feeCurrencyId = self.safe_string(trade, 'fee_currency')
@@ -1336,7 +1336,7 @@ class kraken(Exchange, ImplicitAPI):
1336
1336
  async def create_market_order_with_cost(self, symbol: str, side: OrderSide, cost: float, params={}):
1337
1337
  """
1338
1338
  create a market order by providing the symbol, side and cost
1339
- :see: https://docs.kraken.com/rest/#tag/Trading/operation/addOrder
1339
+ :see: https://docs.kraken.com/rest/#tag/Spot-Trading/operation/addOrder
1340
1340
  :param str symbol: unified symbol of the market to create an order in(only USD markets are supported)
1341
1341
  :param str side: 'buy' or 'sell'
1342
1342
  :param float cost: how much you want to trade in units of the quote currency
@@ -1351,7 +1351,7 @@ class kraken(Exchange, ImplicitAPI):
1351
1351
  async def create_market_buy_order_with_cost(self, symbol: str, cost: float, params={}):
1352
1352
  """
1353
1353
  create a market buy order by providing the symbol, side and cost
1354
- :see: https://docs.kraken.com/rest/#tag/Trading/operation/addOrder
1354
+ :see: https://docs.kraken.com/rest/#tag/Spot-Trading/operation/addOrder
1355
1355
  :param str symbol: unified symbol of the market to create an order in
1356
1356
  :param float cost: how much you want to trade in units of the quote currency
1357
1357
  :param dict [params]: extra parameters specific to the exchange API endpoint
@@ -1362,7 +1362,7 @@ class kraken(Exchange, ImplicitAPI):
1362
1362
 
1363
1363
  async def create_order(self, symbol: str, type: OrderType, side: OrderSide, amount: float, price: Num = None, params={}):
1364
1364
  """
1365
- :see: https://docs.kraken.com/rest/#tag/Trading/operation/addOrder
1365
+ :see: https://docs.kraken.com/rest/#tag/Spot-Trading/operation/addOrder
1366
1366
  create a trade order
1367
1367
  :param str symbol: unified symbol of the market to create an order in
1368
1368
  :param str type: 'market' or 'limit'
@@ -1495,6 +1495,8 @@ class kraken(Exchange, ImplicitAPI):
1495
1495
  # "status": "ok",
1496
1496
  # "txid": "OAW2BO-7RWEK-PZY5UO",
1497
1497
  # "originaltxid": "OXL6SS-UPNMC-26WBE7",
1498
+ # "newuserref": 1234,
1499
+ # "olduserref": 123,
1498
1500
  # "volume": "0.00075000",
1499
1501
  # "price": "13500.0",
1500
1502
  # "orders_cancelled": 1,
@@ -1623,7 +1625,7 @@ class kraken(Exchange, ImplicitAPI):
1623
1625
  if (id is None) or (id.startswith('[')):
1624
1626
  txid = self.safe_list(order, 'txid')
1625
1627
  id = self.safe_string(txid, 0)
1626
- clientOrderId = self.safe_string(order, 'userref')
1628
+ clientOrderId = self.safe_string_2(order, 'userref', 'newuserref')
1627
1629
  rawTrades = self.safe_value(order, 'trades', [])
1628
1630
  trades = []
1629
1631
  for i in range(0, len(rawTrades)):
@@ -1750,7 +1752,7 @@ class kraken(Exchange, ImplicitAPI):
1750
1752
  if postOnly:
1751
1753
  extendedPostFlags = flags + ',post' if (flags is not None) else 'post'
1752
1754
  request['oflags'] = extendedPostFlags
1753
- if (flags is not None) and (request['oflags'] is None):
1755
+ if (flags is not None) and not ('oflags' in request):
1754
1756
  request['oflags'] = flags
1755
1757
  params = self.omit(params, ['timeInForce', 'reduceOnly', 'stopLossPrice', 'takeProfitPrice', 'trailingAmount', 'trailingLimitAmount', 'offset'])
1756
1758
  return [request, params]
@@ -1758,7 +1760,7 @@ class kraken(Exchange, ImplicitAPI):
1758
1760
  async def edit_order(self, id: str, symbol: str, type: OrderType, side: OrderSide, amount: Num = None, price: Num = None, params={}):
1759
1761
  """
1760
1762
  edit a trade order
1761
- :see: https://docs.kraken.com/rest/#tag/Trading/operation/editOrder
1763
+ :see: https://docs.kraken.com/rest/#tag/Spot-Trading/operation/editOrder
1762
1764
  :param str id: order id
1763
1765
  :param str symbol: unified symbol of the market to create an order in
1764
1766
  :param str type: 'market' or 'limit'
@@ -1817,15 +1819,13 @@ class kraken(Exchange, ImplicitAPI):
1817
1819
  clientOrderId = self.safe_value_2(params, 'userref', 'clientOrderId')
1818
1820
  request: dict = {
1819
1821
  'trades': True, # whether or not to include trades in output(optional, default False)
1820
- # 'txid': id, # do not comma separate a list of ids - use fetchOrdersByIds instead
1822
+ 'txid': id, # do not comma separate a list of ids - use fetchOrdersByIds instead
1821
1823
  # 'userref': 'optional', # restrict results to given user reference id(optional)
1822
1824
  }
1823
1825
  query = params
1824
1826
  if clientOrderId is not None:
1825
1827
  request['userref'] = clientOrderId
1826
1828
  query = self.omit(params, ['userref', 'clientOrderId'])
1827
- else:
1828
- request['txid'] = id
1829
1829
  response = await self.privatePostQueryOrders(self.extend(request, query))
1830
1830
  #
1831
1831
  # {
@@ -870,7 +870,6 @@ class kuna(Exchange, ImplicitAPI):
870
870
  'fee': {
871
871
  'cost': self.safe_string(trade, 'fee'),
872
872
  'currency': self.safe_currency_code(self.safe_string(trade, 'feeCurrency')),
873
- 'rate': None,
874
873
  },
875
874
  }, market)
876
875
 
@@ -658,6 +658,12 @@ class vertex(Exchange, ImplicitAPI):
658
658
  amount = None
659
659
  side = None
660
660
  fee = None
661
+ feeCost = self.convert_from_x18(self.safe_string(trade, 'fee'))
662
+ if feeCost is not None:
663
+ fee = {
664
+ 'cost': feeCost,
665
+ 'currency': None,
666
+ }
661
667
  id = self.safe_string_2(trade, 'trade_id', 'submission_idx')
662
668
  order = self.safe_string(trade, 'digest')
663
669
  timestamp = self.safe_timestamp(trade, 'timestamp')
@@ -673,10 +679,6 @@ class vertex(Exchange, ImplicitAPI):
673
679
  subOrder = self.safe_dict(trade, 'order', {})
674
680
  price = self.convert_from_x18(self.safe_string(subOrder, 'priceX18'))
675
681
  amount = self.convert_from_x18(self.safe_string(trade, 'base_filled'))
676
- fee = {
677
- 'cost': self.convert_from_x18(self.safe_string(trade, 'fee')),
678
- 'currency': None,
679
- }
680
682
  if Precise.string_lt(amount, '0'):
681
683
  side = 'sell'
682
684
  else:
ccxt/async_support/woo.py CHANGED
@@ -603,6 +603,9 @@ class woo(Exchange, ImplicitAPI):
603
603
  amount = self.safe_string(trade, 'executed_quantity')
604
604
  order_id = self.safe_string(trade, 'order_id')
605
605
  fee = self.parse_token_and_fee_temp(trade, 'fee_asset', 'fee')
606
+ feeCost = self.safe_string(fee, 'cost')
607
+ if feeCost is not None:
608
+ fee['cost'] = feeCost
606
609
  cost = Precise.string_mul(price, amount)
607
610
  side = self.safe_string_lower(trade, 'side')
608
611
  id = self.safe_string(trade, 'id')
@@ -673,6 +673,9 @@ class woofipro(Exchange, ImplicitAPI):
673
673
  amount = self.safe_string(trade, 'executed_quantity')
674
674
  order_id = self.safe_string(trade, 'order_id')
675
675
  fee = self.parse_token_and_fee_temp(trade, 'fee_asset', 'fee')
676
+ feeCost = self.safe_string(fee, 'cost')
677
+ if feeCost is not None:
678
+ fee['cost'] = feeCost
676
679
  cost = Precise.string_mul(price, amount)
677
680
  side = self.safe_string_lower(trade, 'side')
678
681
  id = self.safe_string(trade, 'id')
ccxt/async_support/xt.py CHANGED
@@ -2035,7 +2035,6 @@ class xt(Exchange, ImplicitAPI):
2035
2035
  'fee': {
2036
2036
  'currency': self.safe_currency_code(self.safe_string_2(trade, 'feeCurrency', 'feeCoin')),
2037
2037
  'cost': self.safe_string(trade, 'fee'),
2038
- 'rate': None,
2039
2038
  },
2040
2039
  }, market)
2041
2040
 
ccxt/base/exchange.py CHANGED
@@ -4,7 +4,7 @@
4
4
 
5
5
  # -----------------------------------------------------------------------------
6
6
 
7
- __version__ = '4.3.77'
7
+ __version__ = '4.3.79'
8
8
 
9
9
  # -----------------------------------------------------------------------------
10
10
 
@@ -1328,7 +1328,7 @@ class Exchange(object):
1328
1328
  def starknet_encode_structured_data (domain, messageTypes, messageData, address):
1329
1329
  types = list(messageTypes.keys())
1330
1330
  if len(types) > 1:
1331
- raise NotSupported(this.id + 'starknetEncodeStructuredData only support single type')
1331
+ raise NotSupported('starknetEncodeStructuredData only support single type')
1332
1332
 
1333
1333
  request = {
1334
1334
  'domain': domain,
@@ -3276,40 +3276,53 @@ class Exchange(object):
3276
3276
  multiplyPrice = Precise.string_div('1', price)
3277
3277
  multiplyPrice = Precise.string_mul(multiplyPrice, contractSize)
3278
3278
  cost = Precise.string_mul(multiplyPrice, amount)
3279
- parseFee = self.safe_value(trade, 'fee') is None
3280
- parseFees = self.safe_value(trade, 'fees') is None
3281
- shouldParseFees = parseFee or parseFees
3282
- fees = []
3283
- fee = self.safe_value(trade, 'fee')
3284
- if shouldParseFees:
3285
- reducedFees = self.reduce_fees_by_currency(fees) if self.reduceFees else fees
3286
- reducedLength = len(reducedFees)
3287
- for i in range(0, reducedLength):
3288
- reducedFees[i]['cost'] = self.safe_number(reducedFees[i], 'cost')
3289
- if 'rate' in reducedFees[i]:
3290
- reducedFees[i]['rate'] = self.safe_number(reducedFees[i], 'rate')
3291
- if not parseFee and (reducedLength == 0):
3292
- # copy fee to avoid modification by reference
3293
- feeCopy = self.deep_extend(fee)
3294
- feeCopy['cost'] = self.safe_number(feeCopy, 'cost')
3295
- if 'rate' in feeCopy:
3296
- feeCopy['rate'] = self.safe_number(feeCopy, 'rate')
3297
- reducedFees.append(feeCopy)
3298
- if parseFees:
3299
- trade['fees'] = reducedFees
3300
- if parseFee and (reducedLength == 1):
3301
- trade['fee'] = reducedFees[0]
3302
- tradeFee = self.safe_value(trade, 'fee')
3303
- if tradeFee is not None:
3304
- tradeFee['cost'] = self.safe_number(tradeFee, 'cost')
3305
- if 'rate' in tradeFee:
3306
- tradeFee['rate'] = self.safe_number(tradeFee, 'rate')
3307
- trade['fee'] = tradeFee
3279
+ resultFee, resultFees = self.parsed_fee_and_fees(trade)
3280
+ trade['fee'] = resultFee
3281
+ trade['fees'] = resultFees
3308
3282
  trade['amount'] = self.parse_number(amount)
3309
3283
  trade['price'] = self.parse_number(price)
3310
3284
  trade['cost'] = self.parse_number(cost)
3311
3285
  return trade
3312
3286
 
3287
+ def parsed_fee_and_fees(self, container: Any):
3288
+ fee = self.safe_dict(container, 'fee')
3289
+ fees = self.safe_list(container, 'fees')
3290
+ feeDefined = fee is not None
3291
+ feesDefined = fees is not None
3292
+ # parsing only if at least one of them is defined
3293
+ shouldParseFees = (feeDefined or feesDefined)
3294
+ if shouldParseFees:
3295
+ if feeDefined:
3296
+ fee = self.parse_fee_numeric(fee)
3297
+ if not feesDefined:
3298
+ # just set it directly, no further processing needed
3299
+ fees = [fee]
3300
+ # 'fees' were set, so reparse them
3301
+ reducedFees = self.reduce_fees_by_currency(fees) if self.reduceFees else fees
3302
+ reducedLength = len(reducedFees)
3303
+ for i in range(0, reducedLength):
3304
+ reducedFees[i] = self.parse_fee_numeric(reducedFees[i])
3305
+ fees = reducedFees
3306
+ if reducedLength == 1:
3307
+ fee = reducedFees[0]
3308
+ elif reducedLength == 0:
3309
+ fee = None
3310
+ # in case `fee & fees` are None, set `fees` array
3311
+ if fee is None:
3312
+ fee = {
3313
+ 'cost': None,
3314
+ 'currency': None,
3315
+ }
3316
+ if fees is None:
3317
+ fees = []
3318
+ return [fee, fees]
3319
+
3320
+ def parse_fee_numeric(self, fee: Any):
3321
+ fee['cost'] = self.safe_number(fee, 'cost') # ensure numeric
3322
+ if 'rate' in fee:
3323
+ fee['rate'] = self.safe_number(fee, 'rate')
3324
+ return fee
3325
+
3313
3326
  def find_nearest_ceiling(self, arr: List[float], providedValue: float):
3314
3327
  # i.e. findNearestCeiling([10, 30, 50], 23) returns 30
3315
3328
  length = len(arr)
@@ -3378,12 +3391,13 @@ class Exchange(object):
3378
3391
  reduced = {}
3379
3392
  for i in range(0, len(fees)):
3380
3393
  fee = fees[i]
3381
- feeCurrencyCode = self.safe_string(fee, 'currency')
3394
+ code = self.safe_string(fee, 'currency')
3395
+ feeCurrencyCode = code is not code if None else str(i)
3382
3396
  if feeCurrencyCode is not None:
3383
3397
  rate = self.safe_string(fee, 'rate')
3384
- cost = self.safe_value(fee, 'cost')
3385
- if Precise.string_eq(cost, '0'):
3386
- # omit zero cost fees
3398
+ cost = self.safe_string(fee, 'cost')
3399
+ if cost is None:
3400
+ # omit None cost, does not make sense, however, don't omit '0' costs, still make sense
3387
3401
  continue
3388
3402
  if not (feeCurrencyCode in reduced):
3389
3403
  reduced[feeCurrencyCode] = {}
@@ -3392,7 +3406,7 @@ class Exchange(object):
3392
3406
  reduced[feeCurrencyCode][rateKey]['cost'] = Precise.string_add(reduced[feeCurrencyCode][rateKey]['cost'], cost)
3393
3407
  else:
3394
3408
  reduced[feeCurrencyCode][rateKey] = {
3395
- 'currency': feeCurrencyCode,
3409
+ 'currency': code,
3396
3410
  'cost': cost,
3397
3411
  }
3398
3412
  if rate is not None:
@@ -3424,7 +3438,13 @@ class Exchange(object):
3424
3438
  if change is None:
3425
3439
  change = Precise.string_sub(last, open)
3426
3440
  if average is None:
3427
- average = Precise.string_div(Precise.string_add(last, open), '2')
3441
+ precision = 18
3442
+ if market is not None and self.is_tick_precision():
3443
+ marketPrecision = self.safe_dict(market, 'precision')
3444
+ precisionPrice = self.safe_string(marketPrecision, 'price')
3445
+ if precisionPrice is not None:
3446
+ precision = self.precision_from_string(precisionPrice)
3447
+ average = Precise.string_div(Precise.string_add(last, open), '2', precision)
3428
3448
  if (percentage is None) and (change is not None) and (open is not None) and Precise.string_gt(open, '0'):
3429
3449
  percentage = Precise.string_mul(Precise.string_div(change, open), '100')
3430
3450
  if (change is None) and (percentage is not None) and (open is not None):
@@ -6215,7 +6235,7 @@ class Exchange(object):
6215
6235
  def parse_margin_modification(self, data: dict, market: Market = None):
6216
6236
  raise NotSupported(self.id + ' parseMarginModification() is not supported yet')
6217
6237
 
6218
- def parse_margin_modifications(self, response: List[object], symbols: List[str] = None, symbolKey: Str = None, marketType: MarketType = None):
6238
+ def parse_margin_modifications(self, response: List[object], symbols: Strings = None, symbolKey: Str = None, marketType: MarketType = None):
6219
6239
  marginModifications = []
6220
6240
  for i in range(0, len(response)):
6221
6241
  info = response[i]