ccxt 4.2.68__py2.py3-none-any.whl → 4.2.70__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.

Potentially problematic release.


This version of ccxt might be problematic. Click here for more details.

ccxt/__init__.py CHANGED
@@ -22,7 +22,7 @@
22
22
 
23
23
  # ----------------------------------------------------------------------------
24
24
 
25
- __version__ = '4.2.68'
25
+ __version__ = '4.2.70'
26
26
 
27
27
  # ----------------------------------------------------------------------------
28
28
 
ccxt/abstract/htx.py CHANGED
@@ -193,6 +193,7 @@ class ImplicitAPI:
193
193
  spot_private_post_v2_sub_user_api_key_modification = spotPrivatePostV2SubUserApiKeyModification = Entry('v2/sub-user/api-key-modification', ['spot', 'private'], 'POST', {'cost': 1})
194
194
  spot_private_post_v2_sub_user_api_key_deletion = spotPrivatePostV2SubUserApiKeyDeletion = Entry('v2/sub-user/api-key-deletion', ['spot', 'private'], 'POST', {'cost': 1})
195
195
  spot_private_post_v1_subuser_transfer = spotPrivatePostV1SubuserTransfer = Entry('v1/subuser/transfer', ['spot', 'private'], 'POST', {'cost': 10})
196
+ spot_private_post_v1_trust_user_active_credit = spotPrivatePostV1TrustUserActiveCredit = Entry('v1/trust/user/active/credit', ['spot', 'private'], 'POST', {'cost': 10})
196
197
  spot_private_post_v1_order_orders_place = spotPrivatePostV1OrderOrdersPlace = Entry('v1/order/orders/place', ['spot', 'private'], 'POST', {'cost': 0.2})
197
198
  spot_private_post_v1_order_batch_orders = spotPrivatePostV1OrderBatchOrders = Entry('v1/order/batch-orders', ['spot', 'private'], 'POST', {'cost': 0.4})
198
199
  spot_private_post_v1_order_auto_place = spotPrivatePostV1OrderAutoPlace = Entry('v1/order/auto/place', ['spot', 'private'], 'POST', {'cost': 0.2})
ccxt/abstract/huobi.py CHANGED
@@ -193,6 +193,7 @@ class ImplicitAPI:
193
193
  spot_private_post_v2_sub_user_api_key_modification = spotPrivatePostV2SubUserApiKeyModification = Entry('v2/sub-user/api-key-modification', ['spot', 'private'], 'POST', {'cost': 1})
194
194
  spot_private_post_v2_sub_user_api_key_deletion = spotPrivatePostV2SubUserApiKeyDeletion = Entry('v2/sub-user/api-key-deletion', ['spot', 'private'], 'POST', {'cost': 1})
195
195
  spot_private_post_v1_subuser_transfer = spotPrivatePostV1SubuserTransfer = Entry('v1/subuser/transfer', ['spot', 'private'], 'POST', {'cost': 10})
196
+ spot_private_post_v1_trust_user_active_credit = spotPrivatePostV1TrustUserActiveCredit = Entry('v1/trust/user/active/credit', ['spot', 'private'], 'POST', {'cost': 10})
196
197
  spot_private_post_v1_order_orders_place = spotPrivatePostV1OrderOrdersPlace = Entry('v1/order/orders/place', ['spot', 'private'], 'POST', {'cost': 0.2})
197
198
  spot_private_post_v1_order_batch_orders = spotPrivatePostV1OrderBatchOrders = Entry('v1/order/batch-orders', ['spot', 'private'], 'POST', {'cost': 0.4})
198
199
  spot_private_post_v1_order_auto_place = spotPrivatePostV1OrderAutoPlace = Entry('v1/order/auto/place', ['spot', 'private'], 'POST', {'cost': 0.2})
@@ -4,7 +4,7 @@
4
4
 
5
5
  # -----------------------------------------------------------------------------
6
6
 
7
- __version__ = '4.2.68'
7
+ __version__ = '4.2.70'
8
8
 
9
9
  # -----------------------------------------------------------------------------
10
10
 
@@ -2,7 +2,7 @@
2
2
 
3
3
  # -----------------------------------------------------------------------------
4
4
 
5
- __version__ = '4.2.68'
5
+ __version__ = '4.2.70'
6
6
 
7
7
  # -----------------------------------------------------------------------------
8
8
 
@@ -6,7 +6,7 @@
6
6
  from ccxt.async_support.base.exchange import Exchange
7
7
  from ccxt.abstract.bitstamp import ImplicitAPI
8
8
  import hashlib
9
- from ccxt.base.types import Balances, Currency, Int, Market, Order, OrderBook, OrderSide, OrderType, Str, Strings, Ticker, Tickers, Trade, Transaction
9
+ from ccxt.base.types import Balances, Currency, Int, Market, Order, TransferEntry, OrderBook, OrderSide, OrderType, Str, Strings, Ticker, Tickers, Trade, Transaction
10
10
  from typing import List
11
11
  from ccxt.base.errors import ExchangeError
12
12
  from ccxt.base.errors import PermissionDenied
@@ -98,6 +98,7 @@ class bitstamp(Exchange, ImplicitAPI):
98
98
  'setLeverage': False,
99
99
  'setMarginMode': False,
100
100
  'setPositionMode': False,
101
+ 'transfer': True,
101
102
  'withdraw': True,
102
103
  },
103
104
  'urls': {
@@ -1983,6 +1984,68 @@ class bitstamp(Exchange, ImplicitAPI):
1983
1984
  response = await getattr(self, method)(self.extend(request, params))
1984
1985
  return self.parse_transaction(response, currency)
1985
1986
 
1987
+ async def transfer(self, code: str, amount: float, fromAccount: str, toAccount: str, params={}) -> TransferEntry:
1988
+ """
1989
+ transfer currency internally between wallets on the same account
1990
+ :see: https://www.bitstamp.net/api/#tag/Sub-account/operation/TransferFromMainToSub
1991
+ :see: https://www.bitstamp.net/api/#tag/Sub-account/operation/TransferFromSubToMain
1992
+ :param str code: unified currency code
1993
+ :param float amount: amount to transfer
1994
+ :param str fromAccount: account to transfer from
1995
+ :param str toAccount: account to transfer to
1996
+ :param dict [params]: extra parameters specific to the exchange API endpoint
1997
+ :returns dict: a `transfer structure <https://docs.ccxt.com/#/?id=transfer-structure>`
1998
+ """
1999
+ await self.load_markets()
2000
+ currency = self.currency(code)
2001
+ amount = self.currency_to_precision(code, amount)
2002
+ amount = self.parse_to_numeric(amount)
2003
+ request = {
2004
+ 'amount': amount,
2005
+ 'currency': currency['id'].upper(),
2006
+ }
2007
+ response = None
2008
+ if fromAccount == 'main':
2009
+ request['subAccount'] = toAccount
2010
+ response = await self.privatePostTransferFromMain(self.extend(request, params))
2011
+ elif toAccount == 'main':
2012
+ request['subAccount'] = fromAccount
2013
+ response = await self.privatePostTransferToMain(self.extend(request, params))
2014
+ else:
2015
+ raise BadRequest(self.id + ' transfer() only supports from or to main')
2016
+ #
2017
+ # {status: 'ok'}
2018
+ #
2019
+ transfer = self.parse_transfer(response, currency)
2020
+ transfer['amount'] = amount
2021
+ transfer['fromAccount'] = fromAccount
2022
+ transfer['toAccount'] = toAccount
2023
+ return transfer
2024
+
2025
+ def parse_transfer(self, transfer, currency=None):
2026
+ #
2027
+ # {status: 'ok'}
2028
+ #
2029
+ status = self.safe_string(transfer, 'status')
2030
+ return {
2031
+ 'info': transfer,
2032
+ 'id': None,
2033
+ 'timestamp': None,
2034
+ 'datetime': None,
2035
+ 'currency': currency['code'],
2036
+ 'amount': None,
2037
+ 'fromAccount': None,
2038
+ 'toAccount': None,
2039
+ 'status': self.parse_transfer_status(status),
2040
+ }
2041
+
2042
+ def parse_transfer_status(self, status):
2043
+ statuses = {
2044
+ 'ok': 'ok',
2045
+ 'error': 'failed',
2046
+ }
2047
+ return self.safe_string(statuses, status, status)
2048
+
1986
2049
  def nonce(self):
1987
2050
  return self.milliseconds()
1988
2051
 
@@ -7,7 +7,7 @@ from ccxt.async_support.base.exchange import Exchange
7
7
  from ccxt.abstract.gate import ImplicitAPI
8
8
  import asyncio
9
9
  import hashlib
10
- from ccxt.base.types import Balances, Currency, Greeks, Int, Market, Order, TransferEntry, OrderBook, OrderRequest, OrderSide, OrderType, FundingHistory, Str, Strings, Ticker, Tickers, Trade, Transaction
10
+ from ccxt.base.types import Balances, Currency, Greeks, Int, Leverage, Leverages, Market, Order, TransferEntry, OrderBook, OrderRequest, OrderSide, OrderType, FundingHistory, Str, Strings, Ticker, Tickers, Trade, Transaction
11
11
  from typing import List
12
12
  from ccxt.base.errors import ExchangeError
13
13
  from ccxt.base.errors import PermissionDenied
@@ -134,7 +134,8 @@ class gate(Exchange, ImplicitAPI):
134
134
  'fetchIsolatedBorrowRate': False,
135
135
  'fetchIsolatedBorrowRates': False,
136
136
  'fetchLedger': True,
137
- 'fetchLeverage': False,
137
+ 'fetchLeverage': True,
138
+ 'fetchLeverages': True,
138
139
  'fetchLeverageTiers': True,
139
140
  'fetchLiquidations': True,
140
141
  'fetchMarginMode': False,
@@ -4199,7 +4200,9 @@ class gate(Exchange, ImplicitAPI):
4199
4200
  lastTradeTimestamp = self.safe_integer(order, 'update_time_ms')
4200
4201
  if lastTradeTimestamp is None:
4201
4202
  lastTradeTimestamp = self.safe_timestamp_2(order, 'update_time', 'finish_time')
4202
- marketType = 'spot' if ('currency_pair' in order) else 'contract'
4203
+ marketType = 'contract'
4204
+ if ('currency_pair' in order) or ('market' in order):
4205
+ marketType = 'spot'
4203
4206
  exchangeSymbol = self.safe_string_2(order, 'currency_pair', 'market', contract)
4204
4207
  symbol = self.safe_symbol(exchangeSymbol, market, '_', marketType)
4205
4208
  # Everything below self(above return) is related to fees
@@ -6515,6 +6518,166 @@ class gate(Exchange, ImplicitAPI):
6515
6518
  side = '' # side is not used but needs to be present, otherwise crashes in php
6516
6519
  return await self.create_order(symbol, 'market', side, 0, None, params)
6517
6520
 
6521
+ async def fetch_leverage(self, symbol: str, params={}) -> Leverage:
6522
+ """
6523
+ fetch the set leverage for a market
6524
+ :see: https://www.gate.io/docs/developers/apiv4/en/#get-unified-account-information
6525
+ :see: https://www.gate.io/docs/developers/apiv4/en/#get-detail-of-lending-market
6526
+ :see: https://www.gate.io/docs/developers/apiv4/en/#query-one-single-margin-currency-pair-deprecated
6527
+ :param str symbol: unified market symbol
6528
+ :param dict [params]: extra parameters specific to the exchange API endpoint
6529
+ :param boolean [params.unified]: default False, set to True for fetching the unified accounts leverage
6530
+ :returns dict: a `leverage structure <https://docs.ccxt.com/#/?id=leverage-structure>`
6531
+ """
6532
+ await self.load_markets()
6533
+ market = None
6534
+ if symbol is not None:
6535
+ # unified account does not require a symbol
6536
+ market = self.market(symbol)
6537
+ request = {}
6538
+ response = None
6539
+ isUnified = self.safe_bool(params, 'unified')
6540
+ params = self.omit(params, 'unified')
6541
+ if market['spot']:
6542
+ request['currency_pair'] = market['id']
6543
+ if isUnified:
6544
+ response = await self.publicMarginGetUniCurrencyPairsCurrencyPair(self.extend(request, params))
6545
+ #
6546
+ # {
6547
+ # "currency_pair": "BTC_USDT",
6548
+ # "base_min_borrow_amount": "0.0001",
6549
+ # "quote_min_borrow_amount": "1",
6550
+ # "leverage": "10"
6551
+ # }
6552
+ #
6553
+ else:
6554
+ response = await self.publicMarginGetCurrencyPairsCurrencyPair(self.extend(request, params))
6555
+ #
6556
+ # {
6557
+ # "id": "BTC_USDT",
6558
+ # "base": "BTC",
6559
+ # "quote": "USDT",
6560
+ # "leverage": 10,
6561
+ # "min_base_amount": "0.0001",
6562
+ # "min_quote_amount": "1",
6563
+ # "max_quote_amount": "40000000",
6564
+ # "status": 1
6565
+ # }
6566
+ #
6567
+ elif isUnified:
6568
+ response = await self.privateUnifiedGetAccounts(self.extend(request, params))
6569
+ #
6570
+ # {
6571
+ # "user_id": 10001,
6572
+ # "locked": False,
6573
+ # "balances": {
6574
+ # "ETH": {
6575
+ # "available": "0",
6576
+ # "freeze": "0",
6577
+ # "borrowed": "0.075393666654",
6578
+ # "negative_liab": "0",
6579
+ # "futures_pos_liab": "0",
6580
+ # "equity": "1016.1",
6581
+ # "total_freeze": "0",
6582
+ # "total_liab": "0"
6583
+ # },
6584
+ # "POINT": {
6585
+ # "available": "9999999999.017023138734",
6586
+ # "freeze": "0",
6587
+ # "borrowed": "0",
6588
+ # "negative_liab": "0",
6589
+ # "futures_pos_liab": "0",
6590
+ # "equity": "12016.1",
6591
+ # "total_freeze": "0",
6592
+ # "total_liab": "0"
6593
+ # },
6594
+ # "USDT": {
6595
+ # "available": "0.00000062023",
6596
+ # "freeze": "0",
6597
+ # "borrowed": "0",
6598
+ # "negative_liab": "0",
6599
+ # "futures_pos_liab": "0",
6600
+ # "equity": "16.1",
6601
+ # "total_freeze": "0",
6602
+ # "total_liab": "0"
6603
+ # }
6604
+ # },
6605
+ # "total": "230.94621713",
6606
+ # "borrowed": "161.66395521",
6607
+ # "total_initial_margin": "1025.0524665088",
6608
+ # "total_margin_balance": "3382495.944473949183",
6609
+ # "total_maintenance_margin": "205.01049330176",
6610
+ # "total_initial_margin_rate": "3299.827135672679",
6611
+ # "total_maintenance_margin_rate": "16499.135678363399",
6612
+ # "total_available_margin": "3381470.892007440383",
6613
+ # "unified_account_total": "3381470.892007440383",
6614
+ # "unified_account_total_liab": "0",
6615
+ # "unified_account_total_equity": "100016.1",
6616
+ # "leverage": "2"
6617
+ # }
6618
+ #
6619
+ else:
6620
+ raise NotSupported(self.id + ' fetchLeverage() does not support ' + market['type'] + ' markets')
6621
+ return self.parse_leverage(response, market)
6622
+
6623
+ async def fetch_leverages(self, symbols: List[str] = None, params={}) -> Leverages:
6624
+ """
6625
+ fetch the set leverage for all leverage markets, only spot margin is supported on gate
6626
+ :see: https://www.gate.io/docs/developers/apiv4/en/#list-lending-markets
6627
+ :see: https://www.gate.io/docs/developers/apiv4/en/#list-all-supported-currency-pairs-supported-in-margin-trading-deprecated
6628
+ :param str[] symbols: a list of unified market symbols
6629
+ :param dict [params]: extra parameters specific to the exchange API endpoint
6630
+ :param boolean [params.unified]: default False, set to True for fetching unified account leverages
6631
+ :returns dict: a list of `leverage structures <https://docs.ccxt.com/#/?id=leverage-structure>`
6632
+ """
6633
+ await self.load_markets()
6634
+ symbols = self.market_symbols(symbols)
6635
+ response = None
6636
+ isUnified = self.safe_bool(params, 'unified')
6637
+ params = self.omit(params, 'unified')
6638
+ marketIdRequest = 'id'
6639
+ if isUnified:
6640
+ marketIdRequest = 'currency_pair'
6641
+ response = await self.publicMarginGetUniCurrencyPairs(params)
6642
+ #
6643
+ # [
6644
+ # {
6645
+ # "currency_pair": "1INCH_USDT",
6646
+ # "base_min_borrow_amount": "8",
6647
+ # "quote_min_borrow_amount": "1",
6648
+ # "leverage": "3"
6649
+ # },
6650
+ # ]
6651
+ #
6652
+ else:
6653
+ response = await self.publicMarginGetCurrencyPairs(params)
6654
+ #
6655
+ # [
6656
+ # {
6657
+ # "id": "1CAT_USDT",
6658
+ # "base": "1CAT",
6659
+ # "quote": "USDT",
6660
+ # "leverage": 3,
6661
+ # "min_base_amount": "71",
6662
+ # "min_quote_amount": "1",
6663
+ # "max_quote_amount": "10000",
6664
+ # "status": 1
6665
+ # },
6666
+ # ]
6667
+ #
6668
+ return self.parse_leverages(response, symbols, marketIdRequest, 'spot')
6669
+
6670
+ def parse_leverage(self, leverage, market=None) -> Leverage:
6671
+ marketId = self.safe_string_2(leverage, 'currency_pair', 'id')
6672
+ leverageValue = self.safe_integer(leverage, 'leverage')
6673
+ return {
6674
+ 'info': leverage,
6675
+ 'symbol': self.safe_symbol(marketId, market, '_', 'spot'),
6676
+ 'marginMode': None,
6677
+ 'longLeverage': leverageValue,
6678
+ 'shortLeverage': leverageValue,
6679
+ }
6680
+
6518
6681
  def handle_errors(self, code, reason, url, method, headers, body, response, requestHeaders, requestBody):
6519
6682
  if response is None:
6520
6683
  return None
@@ -42,7 +42,7 @@ class gemini(Exchange, ImplicitAPI):
42
42
  'CORS': None,
43
43
  'spot': True,
44
44
  'margin': False,
45
- 'swap': False,
45
+ 'swap': True,
46
46
  'future': False,
47
47
  'option': False,
48
48
  'addMargin': False,
@@ -267,11 +267,11 @@ class gemini(Exchange, ImplicitAPI):
267
267
  },
268
268
  },
269
269
  'options': {
270
- 'fetchMarketsMethod': 'fetch_markets_from_web', # fetch_markets_from_api, fetch_markets_from_web
270
+ 'fetchMarketsMethod': 'fetch_markets_from_api', # fetch_markets_from_api, fetch_markets_from_web
271
271
  'fetchMarketFromWebRetries': 10,
272
272
  'fetchMarketsFromAPI': {
273
273
  'fetchDetailsForAllSymbols': False,
274
- 'fetchDetailsForMarketIds': [],
274
+ 'quoteCurrencies': ['USDT', 'GUSD', 'USD', 'DAI', 'EUR', 'GBP', 'SGD', 'BTC', 'ETH', 'LTC', 'BCH'],
275
275
  },
276
276
  'fetchMarkets': {
277
277
  'webApiEnable': True, # fetches from WEB
@@ -322,10 +322,7 @@ class gemini(Exchange, ImplicitAPI):
322
322
  return None
323
323
  #
324
324
  # {
325
- # "tradingPairs": [
326
- # ["BTCAUD", 2, 8, "0.00001", 10, True],
327
- # ...
328
- # ],
325
+ # "tradingPairs": [['BTCUSD', 2, 8, '0.00001', 10, True], ...],
329
326
  # "currencies": [
330
327
  # ["ORCA", "Orca", 204, 6, 0, 6, 8, False, null, "solana"], #, precisions seem to be the 5th index
331
328
  # ["ATOM", "Cosmos", 44, 6, 0, 6, 8, False, null, "cosmos"],
@@ -344,6 +341,7 @@ class gemini(Exchange, ImplicitAPI):
344
341
  # }
345
342
  #
346
343
  result = {}
344
+ self.options['tradingPairs'] = self.safe_list(data, 'tradingPairs')
347
345
  currenciesArray = self.safe_value(data, 'currencies', [])
348
346
  for i in range(0, len(currenciesArray)):
349
347
  currency = currenciesArray[i]
@@ -540,7 +538,7 @@ class gemini(Exchange, ImplicitAPI):
540
538
  return result
541
539
 
542
540
  async def fetch_markets_from_api(self, params={}):
543
- response = await self.publicGetV1Symbols(params)
541
+ marketIdsRaw = await self.publicGetV1Symbols(params)
544
542
  #
545
543
  # [
546
544
  # "btcusd",
@@ -548,87 +546,166 @@ class gemini(Exchange, ImplicitAPI):
548
546
  # ...
549
547
  # ]
550
548
  #
551
- result = {}
552
- for i in range(0, len(response)):
553
- marketId = response[i]
554
- market = {
555
- 'symbol': marketId,
556
- }
557
- result[marketId] = self.parse_market(market)
558
- options = self.safe_value(self.options, 'fetchMarketsFromAPI', {})
559
- fetchDetailsForAllSymbols = self.safe_bool(options, 'fetchDetailsForAllSymbols', False)
560
- fetchDetailsForMarketIds = self.safe_value(options, 'fetchDetailsForMarketIds', [])
561
- promises = []
549
+ result = []
550
+ options = self.safe_dict(self.options, 'fetchMarketsFromAPI', {})
551
+ bugSymbol = 'efilfil' # we skip self inexistent test symbol, which bugs other functions
562
552
  marketIds = []
563
- if fetchDetailsForAllSymbols:
564
- marketIds = response
553
+ for i in range(0, len(marketIdsRaw)):
554
+ if marketIdsRaw[i] != bugSymbol:
555
+ marketIds.append(marketIdsRaw[i])
556
+ if self.safe_bool(options, 'fetchDetailsForAllSymbols', False):
557
+ promises = []
558
+ for i in range(0, len(marketIds)):
559
+ marketId = marketIds[i]
560
+ request = {
561
+ 'symbol': marketId,
562
+ }
563
+ promises.append(self.publicGetV1SymbolsDetailsSymbol(self.extend(request, params)))
564
+ #
565
+ # {
566
+ # "symbol": "BTCUSD",
567
+ # "base_currency": "BTC",
568
+ # "quote_currency": "USD",
569
+ # "tick_size": 1E-8,
570
+ # "quote_increment": 0.01,
571
+ # "min_order_size": "0.00001",
572
+ # "status": "open",
573
+ # "wrap_enabled": False
574
+ # }
575
+ #
576
+ responses = await asyncio.gather(*promises)
577
+ for i in range(0, len(responses)):
578
+ result.append(self.parse_market(responses[i]))
565
579
  else:
566
- marketIds = fetchDetailsForMarketIds
567
- for i in range(0, len(marketIds)):
568
- marketId = marketIds[i]
569
- request = {
570
- 'symbol': marketId,
571
- }
572
- promises.append(self.publicGetV1SymbolsDetailsSymbol(self.extend(request, params)))
573
- #
574
- # {
575
- # "symbol": "BTCUSD",
576
- # "base_currency": "BTC",
577
- # "quote_currency": "USD",
578
- # "tick_size": 1E-8,
579
- # "quote_increment": 0.01,
580
- # "min_order_size": "0.00001",
581
- # "status": "open",
582
- # "wrap_enabled": False
583
- # }
584
- #
585
- promises = await asyncio.gather(*promises)
586
- for i in range(0, len(promises)):
587
- responseInner = promises[i]
588
- marketId = self.safe_string_lower(responseInner, 'symbol')
589
- result[marketId] = self.parse_market(responseInner)
590
- return self.to_array(result)
580
+ # use trading-pairs info, if it was fetched
581
+ tradingPairs = self.safe_list(self.options, 'tradingPairs')
582
+ if tradingPairs is not None:
583
+ indexedTradingPairs = self.index_by(tradingPairs, 0)
584
+ for i in range(0, len(marketIds)):
585
+ marketId = marketIds[i]
586
+ tradingPair = self.safe_list(indexedTradingPairs, marketId.upper())
587
+ if tradingPair is not None:
588
+ result.append(self.parse_market(tradingPair))
589
+ else:
590
+ for i in range(0, len(marketIds)):
591
+ result.append(self.parse_market(marketIds[i]))
592
+ return result
591
593
 
592
594
  def parse_market(self, response) -> Market:
593
- marketId = self.safe_string_lower(response, 'symbol')
594
- baseId = self.safe_string(response, 'base_currency')
595
- quoteId = self.safe_string(response, 'quote_currency')
596
- if baseId is None:
597
- idLength = len(marketId) - 0
598
- isUSDT = marketId.find('usdt') >= 0
599
- quoteSize = 4 if isUSDT else 3
600
- baseId = marketId[0:idLength - quoteSize] # Not True for all markets
601
- quoteId = marketId[idLength - quoteSize:idLength]
595
+ #
596
+ # response might be:
597
+ #
598
+ # btcusd
599
+ #
600
+ # or
601
+ #
602
+ # [
603
+ # 'BTCUSD', # symbol
604
+ # 2, # priceTickDecimalPlaces
605
+ # 8, # quantityTickDecimalPlaces
606
+ # '0.00001', # quantityMinimum
607
+ # 10, # quantityRoundDecimalPlaces
608
+ # True # minimumsAreInclusive
609
+ # ],
610
+ #
611
+ # or
612
+ #
613
+ # {
614
+ # "symbol": "BTCUSD", # perpetuals have 'PERP' suffix, i.e. DOGEUSDPERP
615
+ # "base_currency": "BTC",
616
+ # "quote_currency": "USD",
617
+ # "tick_size": 1E-8,
618
+ # "quote_increment": 0.01,
619
+ # "min_order_size": "0.00001",
620
+ # "status": "open",
621
+ # "wrap_enabled": False
622
+ # "product_type": "swap", # only in perps
623
+ # "contract_type": "linear", # only in perps
624
+ # "contract_price_currency": "GUSD" # only in perps
625
+ # }
626
+ #
627
+ marketId = None
628
+ baseId = None
629
+ quoteId = None
630
+ settleId = None
631
+ tickSize = None
632
+ increment = None
633
+ minSize = None
634
+ status = None
635
+ swap = False
636
+ contractSize = None
637
+ linear = None
638
+ inverse = None
639
+ isString = (isinstance(response, str))
640
+ isArray = (isinstance(response, list))
641
+ if not isString and not isArray:
642
+ marketId = self.safe_string_lower(response, 'symbol')
643
+ minSize = self.safe_number(response, 'min_order_size')
644
+ tickSize = self.safe_number(response, 'tick_size')
645
+ increment = self.safe_number(response, 'quote_increment')
646
+ status = self.parse_market_active(self.safe_string(response, 'status'))
647
+ baseId = self.safe_string(response, 'base_currency')
648
+ quoteId = self.safe_string(response, 'quote_currency')
649
+ settleId = self.safe_string(response, 'contract_price_currency')
650
+ else:
651
+ # if no detailed API was called, then parse either string or array
652
+ if isString:
653
+ marketId = response
654
+ else:
655
+ marketId = self.safe_string_lower(response, 0)
656
+ minSize = self.safe_number(response, 3)
657
+ tickSize = self.parse_number(self.parse_precision(self.safe_string(response, 1)))
658
+ increment = self.parse_number(self.parse_precision(self.safe_string(response, 2)))
659
+ marketIdUpper = marketId.upper()
660
+ isPerp = (marketIdUpper.find('PERP') >= 0)
661
+ marketIdWithoutPerp = marketIdUpper.replace('PERP', '')
662
+ quoteQurrencies = self.handle_option('fetchMarketsFromAPI', 'quoteCurrencies', [])
663
+ for i in range(0, len(quoteQurrencies)):
664
+ quoteCurrency = quoteQurrencies[i]
665
+ if marketIdWithoutPerp.endswith(quoteCurrency):
666
+ baseId = marketIdWithoutPerp.replace(quoteCurrency, '')
667
+ quoteId = quoteCurrency
668
+ if isPerp:
669
+ settleId = quoteCurrency # always same
670
+ break
602
671
  base = self.safe_currency_code(baseId)
603
672
  quote = self.safe_currency_code(quoteId)
604
- status = self.safe_string(response, 'status')
673
+ settle = self.safe_currency_code(settleId)
674
+ symbol = base + '/' + quote
675
+ if settleId is not None:
676
+ symbol = symbol + ':' + settle
677
+ swap = True
678
+ contractSize = tickSize # always same
679
+ linear = True # always linear
680
+ inverse = False
681
+ type = 'swap' if swap else 'spot'
605
682
  return {
606
683
  'id': marketId,
607
- 'symbol': base + '/' + quote,
684
+ 'symbol': symbol,
608
685
  'base': base,
609
686
  'quote': quote,
610
- 'settle': None,
687
+ 'settle': settle,
611
688
  'baseId': baseId,
612
689
  'quoteId': quoteId,
613
- 'settleId': None,
614
- 'type': 'spot',
615
- 'spot': True,
690
+ 'settleId': settleId,
691
+ 'type': type,
692
+ 'spot': not swap,
616
693
  'margin': False,
617
- 'swap': False,
694
+ 'swap': swap,
618
695
  'future': False,
619
696
  'option': False,
620
- 'active': self.parse_market_active(status),
621
- 'contract': False,
622
- 'linear': None,
623
- 'inverse': None,
624
- 'contractSize': None,
697
+ 'active': status,
698
+ 'contract': swap,
699
+ 'linear': linear,
700
+ 'inverse': inverse,
701
+ 'contractSize': contractSize,
625
702
  'expiry': None,
626
703
  'expiryDatetime': None,
627
704
  'strike': None,
628
705
  'optionType': None,
629
706
  'precision': {
630
- 'price': self.safe_number(response, 'quote_increment'),
631
- 'amount': self.safe_number(response, 'tick_size'),
707
+ 'price': increment,
708
+ 'amount': tickSize,
632
709
  },
633
710
  'limits': {
634
711
  'leverage': {
@@ -636,7 +713,7 @@ class gemini(Exchange, ImplicitAPI):
636
713
  'max': None,
637
714
  },
638
715
  'amount': {
639
- 'min': self.safe_number(response, 'min_order_size'),
716
+ 'min': minSize,
640
717
  'max': None,
641
718
  },
642
719
  'price': {
@@ -332,6 +332,7 @@ class hitbtc(Exchange, ImplicitAPI):
332
332
  '2012': BadRequest,
333
333
  '2020': BadRequest,
334
334
  '2022': BadRequest,
335
+ '2024': InvalidOrder, # Invalid margin mode.
335
336
  '10001': BadRequest,
336
337
  '10021': AccountSuspended,
337
338
  '10022': BadRequest,
@@ -349,6 +350,7 @@ class hitbtc(Exchange, ImplicitAPI):
349
350
  '20012': ExchangeError,
350
351
  '20014': ExchangeError,
351
352
  '20016': ExchangeError,
353
+ '20018': ExchangeError, # Withdrawals are unavailable due to the current configuration. Any of: - internal withdrawals are disabled; - in-chain withdrawals are disabled.
352
354
  '20031': ExchangeError,
353
355
  '20032': ExchangeError,
354
356
  '20033': ExchangeError,
@@ -359,10 +361,15 @@ class hitbtc(Exchange, ImplicitAPI):
359
361
  '20043': ExchangeError,
360
362
  '20044': PermissionDenied,
361
363
  '20045': InvalidOrder,
364
+ '20047': InvalidOrder, # Order placing exceeds the central counterparty balance limit.
365
+ '20048': InvalidOrder, # Provided Time-In-Force instruction is invalid or the combination of the instruction and the order type is not allowed.
366
+ '20049': InvalidOrder, # Provided order type is invalid.
362
367
  '20080': ExchangeError,
363
368
  '21001': ExchangeError,
364
369
  '21003': AccountSuspended,
365
370
  '21004': AccountSuspended,
371
+ '22004': ExchangeError, # User is not found.
372
+ '22008': ExchangeError, # Gateway timeout exceeded.
366
373
  },
367
374
  'broad': {},
368
375
  },