ccxt 4.4.67__py2.py3-none-any.whl → 4.4.69__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.
@@ -6,7 +6,7 @@
6
6
  from ccxt.async_support.base.exchange import Exchange
7
7
  from ccxt.abstract.whitebit import ImplicitAPI
8
8
  import hashlib
9
- from ccxt.base.types import Any, Balances, BorrowInterest, Bool, Conversion, Currencies, Currency, DepositAddress, FundingHistory, Int, Market, MarketType, Num, Order, OrderBook, OrderSide, OrderType, Str, Strings, Ticker, Tickers, FundingRate, FundingRates, Trade, TradingFees, Transaction, TransferEntry
9
+ from ccxt.base.types import Any, Balances, BorrowInterest, Bool, Conversion, Currencies, Currency, DepositAddress, FundingHistory, Int, Market, MarketType, Num, Order, OrderBook, OrderSide, OrderType, Position, Str, Strings, Ticker, Tickers, FundingRate, FundingRates, Trade, TradingFees, Transaction, TransferEntry
10
10
  from typing import List
11
11
  from ccxt.base.errors import ExchangeError
12
12
  from ccxt.base.errors import AuthenticationError
@@ -38,7 +38,7 @@ class whitebit(Exchange, ImplicitAPI):
38
38
  'CORS': None,
39
39
  'spot': True,
40
40
  'margin': True,
41
- 'swap': False,
41
+ 'swap': True,
42
42
  'future': False,
43
43
  'option': False,
44
44
  'cancelAllOrders': True,
@@ -88,7 +88,10 @@ class whitebit(Exchange, ImplicitAPI):
88
88
  'fetchOpenOrders': True,
89
89
  'fetchOrderBook': True,
90
90
  'fetchOrderTrades': True,
91
+ 'fetchPosition': True,
92
+ 'fetchPositionHistory': True,
91
93
  'fetchPositionMode': False,
94
+ 'fetchPositions': True,
92
95
  'fetchPremiumIndexOHLCV': False,
93
96
  'fetchStatus': True,
94
97
  'fetchTicker': True,
@@ -2859,6 +2862,211 @@ class whitebit(Exchange, ImplicitAPI):
2859
2862
  'fee': None,
2860
2863
  }
2861
2864
 
2865
+ async def fetch_position_history(self, symbol: str, since: Int = None, limit: Int = None, params={}) -> List[Position]:
2866
+ """
2867
+ fetches historical positions
2868
+
2869
+ https://docs.whitebit.com/private/http-trade-v4/#positions-history
2870
+
2871
+ :param str symbol: unified contract symbol
2872
+ :param int [since]: the earliest time in ms to fetch positions for
2873
+ :param int [limit]: the maximum amount of records to fetch
2874
+ :param dict [params]: extra parameters specific to the exchange api endpoint
2875
+ :param int [params.positionId]: the id of the requested position
2876
+ :returns dict[]: a list of `position structures <https://docs.ccxt.com/#/?id=position-structure>`
2877
+ """
2878
+ await self.load_markets()
2879
+ market = self.market(symbol)
2880
+ request: dict = {
2881
+ 'market': market['id'],
2882
+ }
2883
+ if since is not None:
2884
+ request['startDate'] = since
2885
+ if limit is not None:
2886
+ request['limit'] = since
2887
+ request, params = self.handle_until_option('endDate', request, params)
2888
+ response = await self.v4PrivatePostCollateralAccountPositionsHistory(self.extend(request, params))
2889
+ #
2890
+ # [
2891
+ # {
2892
+ # "positionId": 479975679,
2893
+ # "market": "BTC_PERP",
2894
+ # "openDate": 1741941025.309887,
2895
+ # "modifyDate": 1741941025.309887,
2896
+ # "amount": "0.001",
2897
+ # "basePrice": "82498.7",
2898
+ # "realizedFunding": "0",
2899
+ # "liquidationPrice": "0",
2900
+ # "liquidationState": null,
2901
+ # "orderDetail": {
2902
+ # "id": 1224727949521,
2903
+ # "tradeAmount": "0.001",
2904
+ # "price": "82498.7",
2905
+ # "tradeFee": "0.028874545",
2906
+ # "fundingFee": "0",
2907
+ # "realizedPnl": "-0.028874545"
2908
+ # }
2909
+ # }
2910
+ # ]
2911
+ #
2912
+ positions = self.parse_positions(response)
2913
+ return self.filter_by_symbol_since_limit(positions, symbol, since, limit)
2914
+
2915
+ async def fetch_positions(self, symbols: Strings = None, params={}) -> List[Position]:
2916
+ """
2917
+ fetch all open positions
2918
+
2919
+ https://docs.whitebit.com/private/http-trade-v4/#open-positions
2920
+
2921
+ :param str[] [symbols]: list of unified market symbols
2922
+ :param dict [params]: extra parameters specific to the exchange API endpoint
2923
+ :returns dict[]: a list of `position structures <https://docs.ccxt.com/#/?id=position-structure>`
2924
+ """
2925
+ await self.load_markets()
2926
+ symbols = self.market_symbols(symbols)
2927
+ response = await self.v4PrivatePostCollateralAccountPositionsOpen(params)
2928
+ #
2929
+ # [
2930
+ # {
2931
+ # "positionId": 479975679,
2932
+ # "market": "BTC_PERP",
2933
+ # "openDate": 1741941025.3098869,
2934
+ # "modifyDate": 1741941025.3098869,
2935
+ # "amount": "0.001",
2936
+ # "basePrice": "82498.7",
2937
+ # "liquidationPrice": "70177.2",
2938
+ # "pnl": "0",
2939
+ # "pnlPercent": "0.00",
2940
+ # "margin": "4.2",
2941
+ # "freeMargin": "9.9",
2942
+ # "funding": "0",
2943
+ # "unrealizedFunding": "0",
2944
+ # "liquidationState": null,
2945
+ # "tpsl": null
2946
+ # }
2947
+ # ]
2948
+ #
2949
+ return self.parse_positions(response, symbols)
2950
+
2951
+ async def fetch_position(self, symbol: str, params={}) -> Position:
2952
+ """
2953
+ fetch data on a single open contract trade position
2954
+
2955
+ https://docs.whitebit.com/private/http-trade-v4/#open-positions
2956
+
2957
+ :param str symbol: unified market symbol of the market the position is held in
2958
+ :param dict [params]: extra parameters specific to the exchange API endpoint
2959
+ :returns dict: a `position structure <https://docs.ccxt.com/#/?id=position-structure>`
2960
+ """
2961
+ await self.load_markets()
2962
+ market = self.market(symbol)
2963
+ request: dict = {
2964
+ 'symbol': market['id'],
2965
+ }
2966
+ response = await self.v4PrivatePostCollateralAccountPositionsOpen(self.extend(request, params))
2967
+ #
2968
+ # [
2969
+ # {
2970
+ # "positionId": 479975679,
2971
+ # "market": "BTC_PERP",
2972
+ # "openDate": 1741941025.3098869,
2973
+ # "modifyDate": 1741941025.3098869,
2974
+ # "amount": "0.001",
2975
+ # "basePrice": "82498.7",
2976
+ # "liquidationPrice": "70177.2",
2977
+ # "pnl": "0",
2978
+ # "pnlPercent": "0.00",
2979
+ # "margin": "4.2",
2980
+ # "freeMargin": "9.9",
2981
+ # "funding": "0",
2982
+ # "unrealizedFunding": "0",
2983
+ # "liquidationState": null,
2984
+ # "tpsl": null
2985
+ # }
2986
+ # ]
2987
+ #
2988
+ data = self.safe_dict(response, 0, {})
2989
+ return self.parse_position(data, market)
2990
+
2991
+ def parse_position(self, position: dict, market: Market = None) -> Position:
2992
+ #
2993
+ # fetchPosition, fetchPositions
2994
+ #
2995
+ # {
2996
+ # "positionId": 479975679,
2997
+ # "market": "BTC_PERP",
2998
+ # "openDate": 1741941025.3098869,
2999
+ # "modifyDate": 1741941025.3098869,
3000
+ # "amount": "0.001",
3001
+ # "basePrice": "82498.7",
3002
+ # "liquidationPrice": "70177.2",
3003
+ # "pnl": "0",
3004
+ # "pnlPercent": "0.00",
3005
+ # "margin": "4.2",
3006
+ # "freeMargin": "9.9",
3007
+ # "funding": "0",
3008
+ # "unrealizedFunding": "0",
3009
+ # "liquidationState": null,
3010
+ # "tpsl": null
3011
+ # }
3012
+ #
3013
+ # fetchPositionHistory
3014
+ #
3015
+ # {
3016
+ # "positionId": 479975679,
3017
+ # "market": "BTC_PERP",
3018
+ # "openDate": 1741941025.309887,
3019
+ # "modifyDate": 1741941025.309887,
3020
+ # "amount": "0.001",
3021
+ # "basePrice": "82498.7",
3022
+ # "realizedFunding": "0",
3023
+ # "liquidationPrice": "0",
3024
+ # "liquidationState": null,
3025
+ # "orderDetail": {
3026
+ # "id": 1224727949521,
3027
+ # "tradeAmount": "0.001",
3028
+ # "price": "82498.7",
3029
+ # "tradeFee": "0.028874545",
3030
+ # "fundingFee": "0",
3031
+ # "realizedPnl": "-0.028874545"
3032
+ # }
3033
+ # }
3034
+ #
3035
+ marketId = self.safe_string(position, 'market')
3036
+ timestamp = self.safe_timestamp(position, 'openDate')
3037
+ tpsl = self.safe_dict(position, 'tpsl', {})
3038
+ orderDetail = self.safe_dict(position, 'orderDetail', {})
3039
+ return self.safe_position({
3040
+ 'info': position,
3041
+ 'id': self.safe_string(position, 'positionId'),
3042
+ 'symbol': self.safe_symbol(marketId, market),
3043
+ 'notional': None,
3044
+ 'marginMode': None,
3045
+ 'liquidationPrice': self.safe_number(position, 'liquidationPrice'),
3046
+ 'entryPrice': self.safe_number(position, 'basePrice'),
3047
+ 'unrealizedPnl': self.safe_number(position, 'pnl'),
3048
+ 'realizedPnl': self.safe_number(orderDetail, 'realizedPnl'),
3049
+ 'percentage': self.safe_number(position, 'pnlPercent'),
3050
+ 'contracts': None,
3051
+ 'contractSize': None,
3052
+ 'markPrice': None,
3053
+ 'lastPrice': None,
3054
+ 'side': None,
3055
+ 'hedged': None,
3056
+ 'timestamp': timestamp,
3057
+ 'datetime': self.iso8601(timestamp),
3058
+ 'lastUpdateTimestamp': self.safe_timestamp(position, 'modifyDate'),
3059
+ 'maintenanceMargin': None,
3060
+ 'maintenanceMarginPercentage': None,
3061
+ 'collateral': self.safe_number(position, 'margin'),
3062
+ 'initialMargin': None,
3063
+ 'initialMarginPercentage': None,
3064
+ 'leverage': None,
3065
+ 'marginRatio': None,
3066
+ 'stopLossPrice': self.safe_number(tpsl, 'stopLoss'),
3067
+ 'takeProfitPrice': self.safe_number(tpsl, 'takeProfit'),
3068
+ })
3069
+
2862
3070
  def is_fiat(self, currency: str) -> bool:
2863
3071
  fiatCurrencies = self.safe_value(self.options, 'fiatCurrencies', [])
2864
3072
  return self.in_array(currency, fiatCurrencies)
ccxt/base/exchange.py CHANGED
@@ -4,7 +4,7 @@
4
4
 
5
5
  # -----------------------------------------------------------------------------
6
6
 
7
- __version__ = '4.4.67'
7
+ __version__ = '4.4.69'
8
8
 
9
9
  # -----------------------------------------------------------------------------
10
10
 
@@ -21,6 +21,7 @@ from ccxt.base.errors import ArgumentsRequired
21
21
  from ccxt.base.errors import BadSymbol
22
22
  from ccxt.base.errors import NullResponse
23
23
  from ccxt.base.errors import RateLimitExceeded
24
+ from ccxt.base.errors import OperationFailed
24
25
  from ccxt.base.errors import BadRequest
25
26
  from ccxt.base.errors import BadResponse
26
27
  from ccxt.base.errors import InvalidProxySettings
@@ -4351,14 +4352,15 @@ class Exchange(object):
4351
4352
  try:
4352
4353
  return self.fetch(request['url'], request['method'], request['headers'], request['body'])
4353
4354
  except Exception as e:
4354
- if isinstance(e, NetworkError):
4355
+ if isinstance(e, OperationFailed):
4355
4356
  if i < retries:
4356
4357
  if self.verbose:
4357
4358
  self.log('Request failed with the error: ' + str(e) + ', retrying ' + (i + str(1)) + ' of ' + str(retries) + '...')
4358
4359
  if (retryDelay is not None) and (retryDelay != 0):
4359
4360
  self.sleep(retryDelay)
4360
- # continue #check self
4361
- if i >= retries:
4361
+ else:
4362
+ raise e
4363
+ else:
4362
4364
  raise e
4363
4365
  return None # self line is never reached, but exists for c# value return requirement
4364
4366
 
ccxt/binance.py CHANGED
@@ -11325,8 +11325,22 @@ class binance(Exchange, ImplicitAPI):
11325
11325
  query = None
11326
11326
  # handle batchOrders
11327
11327
  if (path == 'batchOrders') and ((method == 'POST') or (method == 'PUT')):
11328
- batchOrders = self.safe_value(params, 'batchOrders')
11329
- queryBatch = (self.json(batchOrders))
11328
+ batchOrders = self.safe_list(params, 'batchOrders')
11329
+ checkedBatchOrders = batchOrders
11330
+ if method == 'POST' and api == 'fapiPrivate':
11331
+ # check broker id if batchOrders are called with fapiPrivatePostBatchOrders
11332
+ checkedBatchOrders = []
11333
+ for i in range(0, len(batchOrders)):
11334
+ batchOrder = batchOrders[i]
11335
+ newClientOrderId = self.safe_string(batchOrder, 'newClientOrderId')
11336
+ if newClientOrderId is None:
11337
+ defaultId = 'x-xcKtGhcu' # batchOrders can not be spot or margin
11338
+ broker = self.safe_dict(self.options, 'broker', {})
11339
+ brokerId = self.safe_string(broker, 'future', defaultId)
11340
+ newClientOrderId = brokerId + self.uuid22()
11341
+ batchOrder['newClientOrderId'] = newClientOrderId
11342
+ checkedBatchOrders.append(batchOrder)
11343
+ queryBatch = (self.json(checkedBatchOrders))
11330
11344
  params['batchOrders'] = queryBatch
11331
11345
  defaultRecvWindow = self.safe_integer(self.options, 'recvWindow')
11332
11346
  extendedParams = self.extend({
ccxt/bitrue.py CHANGED
@@ -1633,7 +1633,7 @@ class bitrue(Exchange, ImplicitAPI):
1633
1633
  tickers: dict = {}
1634
1634
  for i in range(0, len(data)):
1635
1635
  ticker = self.safe_dict(data, i, {})
1636
- market = self.market(self.safe_value(ticker, 'symbol'))
1636
+ market = self.safe_market(self.safe_string(ticker, 'symbol'))
1637
1637
  tickers[market['id']] = ticker
1638
1638
  return self.parse_tickers(tickers, symbols)
1639
1639
 
ccxt/bitstamp.py CHANGED
@@ -1282,10 +1282,9 @@ class bitstamp(Exchange, ImplicitAPI):
1282
1282
 
1283
1283
  def parse_trading_fees(self, fees):
1284
1284
  result: dict = {'info': fees}
1285
- symbols = self.symbols
1286
- for i in range(0, len(symbols)):
1287
- symbol = symbols[i]
1285
+ for i in range(0, len(fees)):
1288
1286
  fee = self.parse_trading_fee(fees[i])
1287
+ symbol = fee['symbol']
1289
1288
  result[symbol] = fee
1290
1289
  return result
1291
1290
 
ccxt/bybit.py CHANGED
@@ -8825,7 +8825,7 @@ classic accounts only/ spot not supported* fetches information on an order made
8825
8825
  feedback = self.id + ' private api uses /user/v3/private/query-api to check if you have a unified account. The API key of user id must own one of permissions: "Account Transfer", "Subaccount Transfer", "Withdrawal" ' + body
8826
8826
  else:
8827
8827
  feedback = self.id + ' ' + body
8828
- if body.find('Withdraw address chain or destination tag are not equal'):
8828
+ if body.find('Withdraw address chain or destination tag are not equal') > -1:
8829
8829
  feedback = feedback + '; You might also need to ensure the address is whitelisted'
8830
8830
  self.throw_broadly_matched_exception(self.exceptions['broad'], body, feedback)
8831
8831
  self.throw_exactly_matched_exception(self.exceptions['exact'], errorCode, feedback)
ccxt/coinbase.py CHANGED
@@ -4088,6 +4088,7 @@ class coinbase(Exchange, ImplicitAPI):
4088
4088
  'amount': self.number_to_string(amount),
4089
4089
  'currency': code.upper(), # need to use code in case depositing USD etc.
4090
4090
  'payment_method': id,
4091
+ 'commit': True, # otheriwse the deposit does not go through
4091
4092
  }
4092
4093
  response = self.v2PrivatePostAccountsAccountIdDeposits(self.extend(request, params))
4093
4094
  #
@@ -4126,7 +4127,8 @@ class coinbase(Exchange, ImplicitAPI):
4126
4127
  # }
4127
4128
  # }
4128
4129
  #
4129
- data = self.safe_dict(response, 'data', {})
4130
+ # https://github.com/ccxt/ccxt/issues/25484
4131
+ data = self.safe_dict_2(response, 'data', 'transfer', {})
4130
4132
  return self.parse_transaction(data)
4131
4133
 
4132
4134
  def fetch_deposit(self, id: str, code: Str = None, params={}):
@@ -4191,7 +4193,8 @@ class coinbase(Exchange, ImplicitAPI):
4191
4193
  # }
4192
4194
  # }
4193
4195
  #
4194
- data = self.safe_dict(response, 'data', {})
4196
+ # https://github.com/ccxt/ccxt/issues/25484
4197
+ data = self.safe_dict_2(response, 'data', 'transfer', {})
4195
4198
  return self.parse_transaction(data)
4196
4199
 
4197
4200
  def fetch_deposit_method_ids(self, params={}):
@@ -4669,6 +4672,70 @@ class coinbase(Exchange, ImplicitAPI):
4669
4672
  }
4670
4673
  return result
4671
4674
 
4675
+ def fetch_portfolio_details(self, portfolioUuid: str, params={}) -> List[Any]:
4676
+ """
4677
+ Fetch details for a specific portfolio by UUID
4678
+
4679
+ https://docs.cloud.coinbase.com/advanced-trade/reference/retailbrokerageapi_getportfolios
4680
+
4681
+ :param str portfolioUuid: The unique identifier of the portfolio to fetch
4682
+ :param Dict [params]: Extra parameters specific to the exchange API endpoint
4683
+ :returns any[]: An account structure <https://docs.ccxt.com/#/?id=account-structure>
4684
+ """
4685
+ self.load_markets()
4686
+ request = {
4687
+ 'portfolio_uuid': portfolioUuid,
4688
+ }
4689
+ response = self.v3PrivateGetBrokeragePortfoliosPortfolioUuid(self.extend(request, params))
4690
+ result = self.parse_portfolio_details(response)
4691
+ return result
4692
+
4693
+ def parse_portfolio_details(self, portfolioData: dict):
4694
+ breakdown = portfolioData['breakdown']
4695
+ portfolioInfo = self.safe_dict(breakdown, 'portfolio', {})
4696
+ portfolioName = self.safe_string(portfolioInfo, 'name', 'Unknown')
4697
+ portfolioUuid = self.safe_string(portfolioInfo, 'uuid', '')
4698
+ spotPositions = self.safe_list(breakdown, 'spot_positions', [])
4699
+ parsedPositions = []
4700
+ for i in range(0, len(spotPositions)):
4701
+ position: dict = spotPositions[i]
4702
+ currencyCode = self.safe_string(position, 'asset', 'Unknown')
4703
+ availableBalanceStr = self.safe_string(position, 'available_to_trade_fiat', '0')
4704
+ availableBalance = self.parse_number(availableBalanceStr)
4705
+ totalBalanceFiatStr = self.safe_string(position, 'total_balance_fiat', '0')
4706
+ totalBalanceFiat = self.parse_number(totalBalanceFiatStr)
4707
+ holdAmount = totalBalanceFiat - availableBalance
4708
+ costBasisDict = self.safe_dict(position, 'cost_basis', {})
4709
+ costBasisStr = self.safe_string(costBasisDict, 'value', '0')
4710
+ averageEntryPriceDict = self.safe_dict(position, 'average_entry_price', {})
4711
+ averageEntryPriceStr = self.safe_string(averageEntryPriceDict, 'value', '0')
4712
+ positionData: dict = {
4713
+ 'currency': currencyCode,
4714
+ 'available_balance': availableBalance,
4715
+ 'hold_amount': holdAmount > holdAmount if 0 else 0,
4716
+ 'wallet_name': portfolioName,
4717
+ 'account_id': portfolioUuid,
4718
+ 'account_uuid': self.safe_string(position, 'account_uuid', ''),
4719
+ 'total_balance_fiat': totalBalanceFiat,
4720
+ 'total_balance_crypto': self.parse_number(self.safe_string(position, 'total_balance_crypto', '0')),
4721
+ 'available_to_trade_fiat': self.parse_number(self.safe_string(position, 'available_to_trade_fiat', '0')),
4722
+ 'available_to_trade_crypto': self.parse_number(self.safe_string(position, 'available_to_trade_crypto', '0')),
4723
+ 'available_to_transfer_fiat': self.parse_number(self.safe_string(position, 'available_to_transfer_fiat', '0')),
4724
+ 'available_to_transfer_crypto': self.parse_number(self.safe_string(position, 'available_to_trade_crypto', '0')),
4725
+ 'allocation': self.parse_number(self.safe_string(position, 'allocation', '0')),
4726
+ 'cost_basis': self.parse_number(costBasisStr),
4727
+ 'cost_basis_currency': self.safe_string(costBasisDict, 'currency', 'USD'),
4728
+ 'is_cash': self.safe_bool(position, 'is_cash', False),
4729
+ 'average_entry_price': self.parse_number(averageEntryPriceStr),
4730
+ 'average_entry_price_currency': self.safe_string(averageEntryPriceDict, 'currency', 'USD'),
4731
+ 'asset_uuid': self.safe_string(position, 'asset_uuid', ''),
4732
+ 'unrealized_pnl': self.parse_number(self.safe_string(position, 'unrealized_pnl', '0')),
4733
+ 'asset_color': self.safe_string(position, 'asset_color', ''),
4734
+ 'account_type': self.safe_string(position, 'account_type', ''),
4735
+ }
4736
+ parsedPositions.append(positionData)
4737
+ return parsedPositions
4738
+
4672
4739
  def create_auth_token(self, seconds: Int, method: Str = None, url: Str = None):
4673
4740
  # it may not work for v2
4674
4741
  uri = None