text: # -*- coding: utf-8 -*-
  
  # PLEASE DO NOT EDIT THIS FILE, IT IS GENERATED AND WILL BE OVERWRITTEN:
  # https://github.com/ccxt/ccxt/blob/master/CONTRIBUTING.md#how-to-contribute-code
  
  from ccxt.async_support.base.exchange import Exchange
  import hashlib
  from ccxt.base.errors import ExchangeError
  from ccxt.base.errors import AuthenticationError
  from ccxt.base.errors import PermissionDenied
  from ccxt.base.errors import ArgumentsRequired
  from ccxt.base.errors import BadSymbol
  from ccxt.base.errors import InsufficientFunds
  from ccxt.base.errors import InvalidOrder
  from ccxt.base.errors import OrderNotFound
  from ccxt.base.errors import NotSupported
  from ccxt.base.errors import RateLimitExceeded
  from ccxt.base.errors import ExchangeNotAvailable
  from ccxt.base.errors import InvalidNonce
  from ccxt.base.decimal_to_precision import ROUND
  from ccxt.base.decimal_to_precision import TRUNCATE
  from ccxt.base.decimal_to_precision import DECIMAL_PLACES
  from ccxt.base.decimal_to_precision import SIGNIFICANT_DIGITS
  from ccxt.base.precise import Precise
  
  
  class bitfinex(Exchange):
  
      def describe(self):
          return self.deep_extend(super(bitfinex, self).describe(), {
              'id': 'bitfinex',
              'name': 'Bitfinex',
              'countries': ['VG'],
              'version': 'v1',
              'rateLimit': 1500,
              'pro': True,
              # new metainfo interface
              'has': {
                  'cancelAllOrders': True,
                  'cancelOrder': True,
                  'CORS': False,
                  'createDepositAddress': True,
                  'createOrder': True,
                  'deposit': True,
                  'editOrder': True,
                  'fetchBalance': True,
                  'fetchClosedOrders': True,
                  'fetchDepositAddress': True,
                  'fetchDeposits': False,
                  'fetchFundingFees': True,
                  'fetchMarkets': True,
                  'fetchMyTrades': True,
                  'fetchOHLCV': True,
                  'fetchOpenOrders': True,
                  'fetchOrder': True,
                  'fetchOrderBook': True,
                  'fetchTicker': True,
                  'fetchTickers': True,
                  'fetchTrades': True,
                  'fetchTradingFee': True,
                  'fetchTradingFees': True,
                  'fetchTransactions': True,
                  'fetchWithdrawals': False,
                  'withdraw': True,
                  'transfer': True,
              },
              'timeframes': {
                  '1m': '1m',
                  '5m': '5m',
                  '15m': '15m',
                  '30m': '30m',
                  '1h': '1h',
                  '3h': '3h',
                  '4h': '4h',
                  '6h': '6h',
                  '12h': '12h',
                  '1d': '1D',
                  '1w': '7D',
                  '2w': '14D',
                  '1M': '1M',
              },
              'urls': {
                  'logo': 'https://user-images.githubusercontent.com/1294454/27766244-e328a50c-5ed2-11e7-947b-041416579bb3.jpg',
                  'api': {
                      'v2': 'https://api-pub.bitfinex.com',  # https://github.com/ccxt/ccxt/issues/5109
                      'public': 'https://api.bitfinex.com',
                      'private': 'https://api.bitfinex.com',
                  },
                  'www': 'https://www.bitfinex.com',
                  'referral': 'https://www.bitfinex.com/?refcode=P61eYxFL',
                  'doc': [
                      'https://docs.bitfinex.com/v1/docs',
                      'https://github.com/bitfinexcom/bitfinex-api-node',
                  ],
              },
              'api': {
                  # v2 symbol ids require a 't' prefix
                  # just the public part of it(use bitfinex2 for everything else)
                  'v2': {
                      'get': [
                          'platform/status',
                          'tickers',
                          'ticker/{symbol}',
                          'trades/{symbol}/hist',
                          'book/{symbol}/{precision}',
                          'book/{symbol}/P0',
                          'book/{symbol}/P1',
                          'book/{symbol}/P2',
                          'book/{symbol}/P3',
                          'book/{symbol}/R0',
                          'stats1/{key}:{size}:{symbol}:{side}/{section}',
                          'stats1/{key}:{size}:{symbol}/{section}',
                          'stats1/{key}:{size}:{symbol}:long/last',
                          'stats1/{key}:{size}:{symbol}:long/hist',
                          'stats1/{key}:{size}:{symbol}:short/last',
                          'stats1/{key}:{size}:{symbol}:short/hist',
                          'candles/trade:{timeframe}:{symbol}/{section}',
                          'candles/trade:{timeframe}:{symbol}/last',
                          'candles/trade:{timeframe}:{symbol}/hist',
                      ],
                  },
                  'public': {
                      'get': [
                          'book/{symbol}',
                          # 'candles/{symbol}',
                          'lendbook/{currency}',
                          'lends/{currency}',
                          'pubticker/{symbol}',
                          'stats/{symbol}',
                          'symbols',
                          'symbols_details',
                          'tickers',
                          'trades/{symbol}',
                      ],
                  },
                  'private': {
                      'post': [
                          'account_fees',
                          'account_infos',
                          'balances',
                          'basket_manage',
                          'credits',
                          'deposit/new',
                          'funding/close',
                          'history',
                          'history/movements',
                          'key_info',
                          'margin_infos',
                          'mytrades',
                          'mytrades_funding',
                          'offer/cancel',
                          'offer/new',
                          'offer/status',
                          'offers',
                          'offers/hist',
                          'order/cancel',
                          'order/cancel/all',
                          'order/cancel/multi',
                          'order/cancel/replace',
                          'order/new',
                          'order/new/multi',
                          'order/status',
                          'orders',
                          'orders/hist',
                          'position/claim',
                          'position/close',
                          'positions',
                          'summary',
                          'taken_funds',
                          'total_taken_funds',
                          'transfer',
                          'unused_taken_funds',
                          'withdraw',
                      ],
                  },
              },
              'fees': {
                  'trading': {
                      'feeSide': 'get',
                      'tierBased': True,
                      'percentage': True,
                      'maker': 0.1 / 100,
                      'taker': 0.2 / 100,
                      'tiers': {
                          'taker': [
                              [0, 0.2 / 100],
                              [500000, 0.2 / 100],
                              [1000000, 0.2 / 100],
                              [2500000, 0.2 / 100],
                              [5000000, 0.2 / 100],
                              [7500000, 0.2 / 100],
                              [10000000, 0.18 / 100],
                              [15000000, 0.16 / 100],
                              [20000000, 0.14 / 100],
                              [25000000, 0.12 / 100],
                              [30000000, 0.1 / 100],
                          ],
                          'maker': [
                              [0, 0.1 / 100],
                              [500000, 0.08 / 100],
                              [1000000, 0.06 / 100],
                              [2500000, 0.04 / 100],
                              [5000000, 0.02 / 100],
                              [7500000, 0],
                              [10000000, 0],
                              [15000000, 0],
                              [20000000, 0],
                              [25000000, 0],
                              [30000000, 0],
                          ],
                      },
                  },
                  'funding': {
                      'tierBased': False,  # True for tier-based/progressive
                      'percentage': False,  # fixed commission
                      # Actually deposit fees are free for larger deposits(> $1000 USD equivalent)
                      # these values below are deprecated, we should not hardcode fees and limits anymore
                      # to be reimplemented with bitfinex funding fees from their API or web endpoints
                      'deposit': {
                          'BTC': 0.0004,
                          'IOTA': 0.5,
                          'ETH': 0.0027,
                          'BCH': 0.0001,
                          'LTC': 0.001,
                          'EOS': 0.24279,
                          'XMR': 0.04,
                          'SAN': 0.99269,
                          'DASH': 0.01,
                          'ETC': 0.01,
                          'XRP': 0.02,
                          'YYW': 16.915,
                          'NEO': 0,
                          'ZEC': 0.001,
                          'BTG': 0,
                          'OMG': 0.14026,
                          'DATA': 20.773,
                          'QASH': 1.9858,
                          'ETP': 0.01,
                          'QTUM': 0.01,
                          'EDO': 0.95001,
                          'AVT': 1.3045,
                          'USDT': 0,
                          'TRX': 28.184,
                          'ZRX': 1.9947,
                          'RCN': 10.793,
                          'TNB': 31.915,
                          'SNT': 14.976,
                          'RLC': 1.414,
                          'GNT': 5.8952,
                          'SPK': 10.893,
                          'REP': 0.041168,
                          'BAT': 6.1546,
                          'ELF': 1.8753,
                          'FUN': 32.336,
                          'SNG': 18.622,
                          'AID': 8.08,
                          'MNA': 16.617,
                          'NEC': 1.6504,
                          'XTZ': 0.2,
                      },
                      'withdraw': {
                          'BTC': 0.0004,
                          'IOTA': 0.5,
                          'ETH': 0.0027,
                          'BCH': 0.0001,
                          'LTC': 0.001,
                          'EOS': 0.24279,
                          'XMR': 0.04,
                          'SAN': 0.99269,
                          'DASH': 0.01,
                          'ETC': 0.01,
                          'XRP': 0.02,
                          'YYW': 16.915,
                          'NEO': 0,
                          'ZEC': 0.001,
                          'BTG': 0,
                          'OMG': 0.14026,
                          'DATA': 20.773,
                          'QASH': 1.9858,
                          'ETP': 0.01,
                          'QTUM': 0.01,
                          'EDO': 0.95001,
                          'AVT': 1.3045,
                          'USDT': 20,
                          'TRX': 28.184,
                          'ZRX': 1.9947,
                          'RCN': 10.793,
                          'TNB': 31.915,
                          'SNT': 14.976,
                          'RLC': 1.414,
                          'GNT': 5.8952,
                          'SPK': 10.893,
                          'REP': 0.041168,
                          'BAT': 6.1546,
                          'ELF': 1.8753,
                          'FUN': 32.336,
                          'SNG': 18.622,
                          'AID': 8.08,
                          'MNA': 16.617,
                          'NEC': 1.6504,
                          'XTZ': 0.2,
                      },
                  },
              },
              # todo rewrite for https://api-pub.bitfinex.com//v2/conf/pub:map:tx:method
              'commonCurrencies': {
                  'ABS': 'ABYSS',
                  'AIO': 'AION',
                  'ALG': 'ALGO',  # https://github.com/ccxt/ccxt/issues/6034
                  'AMP': 'AMPL',
                  'ATM': 'ATMI',
                  'ATO': 'ATOM',  # https://github.com/ccxt/ccxt/issues/5118
                  'BAB': 'BCH',
                  'CTX': 'CTXC',
                  'DAD': 'DADI',
                  'DAT': 'DATA',
                  'DSH': 'DASH',
                  'DRK': 'DRK',
                  # https://github.com/ccxt/ccxt/issues/7399
                  # https://coinmarketcap.com/currencies/pnetwork/
                  # https://en.cryptonomist.ch/blog/eidoo/the-edo-to-pnt-upgrade-what-you-need-to-know-updated/
                  'EDO': 'PNT',
                  'GSD': 'GUSD',
                  'HOT': 'Hydro Protocol',
                  'IOS': 'IOST',
                  'IOT': 'IOTA',
                  'IQX': 'IQ',
                  'MIT': 'MITH',
                  'MNA': 'MANA',
                  'NCA': 'NCASH',
                  'ORS': 'ORS Group',  # conflict with Origin Sport  #3230
                  'POY': 'POLY',
                  'QSH': 'QASH',
                  'QTM': 'QTUM',
                  'RBT': 'RBTC',
                  'SEE': 'SEER',
                  'SNG': 'SNGLS',
                  'SPK': 'SPANK',
                  'STJ': 'STORJ',
                  'TRI': 'TRIO',
                  'TSD': 'TUSD',
                  'YYW': 'YOYOW',
                  'UDC': 'USDC',
                  'UST': 'USDT',
                  'UTN': 'UTNP',
                  'VSY': 'VSYS',
                  'WAX': 'WAXP',
                  'XCH': 'XCHF',
                  'ZBT': 'ZB',
              },
              'exceptions': {
                  'exact': {
                      'temporarily_unavailable': ExchangeNotAvailable,  # Sorry, the service is temporarily unavailable. See https://www.bitfinex.com/ for more info.
                      'Order could not be cancelled.': OrderNotFound,  # non-existent order
                      'No such order found.': OrderNotFound,  # ?
                      'Order price must be positive.': InvalidOrder,  # on price <= 0
                      'Could not find a key matching the given X-BFX-APIKEY.': AuthenticationError,
                      'Key price should be a decimal number, e.g. "123.456"': InvalidOrder,  # on isNaN(price)
                      'Key amount should be a decimal number, e.g. "123.456"': InvalidOrder,  # on isNaN(amount)
                      'ERR_RATE_LIMIT': RateLimitExceeded,
                      'Ratelimit': RateLimitExceeded,
                      'Nonce is too small.': InvalidNonce,
                      'No summary found.': ExchangeError,  # fetchTradingFees(summary) endpoint can give self vague error message
                      'Cannot evaluate your available balance, please try again': ExchangeNotAvailable,
                      'Unknown symbol': BadSymbol,
                      'Cannot complete transfer. Exchange balance insufficient.': InsufficientFunds,
                      'Momentary balance check. Please wait few seconds and try the transfer again.': ExchangeError,
                  },
                  'broad': {
                      'Invalid X-BFX-SIGNATURE': AuthenticationError,
                      'This API key does not have permission': PermissionDenied,  # authenticated but not authorized
                      'not enough exchange balance for ': InsufficientFunds,  # when buying cost is greater than the available quote currency
                      'minimum size for ': InvalidOrder,  # when amount below limits.amount.min
                      'Invalid order': InvalidOrder,  # ?
                      'The available balance is only': InsufficientFunds,  # {"status":"error","message":"Cannot withdraw 1.0027 ETH from your exchange wallet. The available balance is only 0.0 ETH. If you have limit orders, open positions, unused or active margin funding, self will decrease your available balance. To increase it, you can cancel limit orders or reduce/close your positions.","withdrawal_id":0,"fees":"0.0027"}
                  },
              },
              'precisionMode': SIGNIFICANT_DIGITS,
              'options': {
                  'currencyNames': {
                      'AGI': 'agi',
                      'AID': 'aid',
                      'AIO': 'aio',
                      'ANT': 'ant',
                      'AVT': 'aventus',  # #1811
                      'BAT': 'bat',
                      # https://github.com/ccxt/ccxt/issues/5833
                      'BCH': 'bab',  # undocumented
                      # 'BCH': 'bcash',  # undocumented
                      'BCI': 'bci',
                      'BFT': 'bft',
                      'BSV': 'bsv',
                      'BTC': 'bitcoin',
                      'BTG': 'bgold',
                      'CFI': 'cfi',
                      'COMP': 'comp',
                      'DAI': 'dai',
                      'DADI': 'dad',
                      'DASH': 'dash',
                      'DATA': 'datacoin',
                      'DTH': 'dth',
                      'EDO': 'eidoo',  # #1811
                      'ELF': 'elf',
                      'EOS': 'eos',
                      'ETC': 'ethereumc',
                      'ETH': 'ethereum',
                      'ETP': 'metaverse',
                      'FUN': 'fun',
                      'GNT': 'golem',
                      'IOST': 'ios',
                      'IOTA': 'iota',
                      # https://github.com/ccxt/ccxt/issues/5833
                      'LEO': 'let',  # ETH chain
                      # 'LEO': 'les',  # EOS chain
                      'LINK': 'link',
                      'LRC': 'lrc',
                      'LTC': 'litecoin',
                      'LYM': 'lym',
                      'MANA': 'mna',
                      'MIT': 'mit',
                      'MKR': 'mkr',
                      'MTN': 'mtn',
                      'NEO': 'neo',
                      'ODE': 'ode',
                      'OMG': 'omisego',
                      'OMNI': 'mastercoin',
                      'QASH': 'qash',
                      'QTUM': 'qtum',  # #1811
                      'RCN': 'rcn',
                      'RDN': 'rdn',
                      'REP': 'rep',
                      'REQ': 'req',
                      'RLC': 'rlc',
                      'SAN': 'santiment',
                      'SNGLS': 'sng',
                      'SNT': 'status',
                      'SPANK': 'spk',
                      'STORJ': 'stj',
                      'TNB': 'tnb',
                      'TRX': 'trx',
                      'TUSD': 'tsd',
                      'USD': 'wire',
                      'USDC': 'udc',  # https://github.com/ccxt/ccxt/issues/5833
                      'UTK': 'utk',
                      'USDT': 'tetheruso',  # Tether on Omni
                      # 'USDT': 'tetheruse',  # Tether on ERC20
                      # 'USDT': 'tetherusl',  # Tether on Liquid
                      # 'USDT': 'tetherusx',  # Tether on Tron
                      # 'USDT': 'tetheruss',  # Tether on EOS
                      'VEE': 'vee',
                      'WAX': 'wax',
                      'XLM': 'xlm',
                      'XMR': 'monero',
                      'XRP': 'ripple',
                      'XVG': 'xvg',
                      'YOYOW': 'yoyow',
                      'ZEC': 'zcash',
                      'ZRX': 'zrx',
                      'XTZ': 'xtz',
                  },
                  'orderTypes': {
                      'limit': 'exchange limit',
                      'market': 'exchange market',
                  },
                  'accountsByType': {
                      'spot': 'exchange',
                      'margin': 'trading',
                      'funding': 'deposit',
                      'exchange': 'exchange',
                      'trading': 'trading',
                      'deposit': 'deposit',
                      'derivatives': 'trading',
                  },
              },
          })
  
      async def fetch_funding_fees(self, params={}):
          await self.load_markets()
          response = await self.privatePostAccountFees(params)
          fees = response['withdraw']
          withdraw = {}
          ids = list(fees.keys())
          for i in range(0, len(ids)):
              id = ids[i]
              code = self.safe_currency_code(id)
              withdraw[code] = self.safe_number(fees, id)
          return {
              'info': response,
              'withdraw': withdraw,
              'deposit': withdraw,  # only for deposits of less than $1000
          }
  
      async def fetch_trading_fees(self, params={}):
          await self.load_markets()
          response = await self.privatePostSummary(params)
          #
          #     {
          #         time: '2019-02-20T15:50:19.152000Z',
          #         trade_vol_30d: [
          #             {
          #                 curr: 'Total(USD)',
          #                 vol: 0,
          #                 vol_maker: 0,
          #                 vol_BFX: 0,
          #                 vol_BFX_maker: 0,
          #                 vol_ETHFX: 0,
          #                 vol_ETHFX_maker: 0
          #             }
          #         ],
          #         fees_funding_30d: {},
          #         fees_funding_total_30d: 0,
          #         fees_trading_30d: {},
          #         fees_trading_total_30d: 0,
          #         maker_fee: 0.001,
          #         taker_fee: 0.002
          #     }
          #
          return {
              'info': response,
              'maker': self.safe_number(response, 'maker_fee'),
              'taker': self.safe_number(response, 'taker_fee'),
          }
  
      async def fetch_markets(self, params={}):
          ids = await self.publicGetSymbols()
          #
          #     ["btcusd", "ltcusd", "ltcbtc"]
          #
          details = await self.publicGetSymbolsDetails()
          #
          #     [
          #         {
          #             "pair":"btcusd",
          #             "price_precision":5,
          #             "initial_margin":"10.0",
          #             "minimum_margin":"5.0",
          #             "maximum_order_size":"2000.0",
          #             "minimum_order_size":"0.0002",
          #             "expiration":"NA",
          #             "margin":true
          #         },
          #     ]
          #
          result = []
          for i in range(0, len(details)):
              market = details[i]
              id = self.safe_string(market, 'pair')
              if not self.in_array(id, ids):
                  continue
              id = id.upper()
              baseId = None
              quoteId = None
              if id.find(':') >= 0:
                  parts = id.split(':')
                  baseId = parts[0]
                  quoteId = parts[1]
              else:
                  baseId = id[0:3]
                  quoteId = id[3:6]
              base = self.safe_currency_code(baseId)
              quote = self.safe_currency_code(quoteId)
              symbol = base + '/' + quote
              precision = {
                  'price': self.safe_integer(market, 'price_precision'),
                  # https://docs.bitfinex.com/docs/introduction#amount-precision
                  # The amount field allows up to 8 decimals.
                  # Anything exceeding self will be rounded to the 8th decimal.
                  'amount': 8,
              }
              minAmountString = self.safe_string(market, 'minimum_order_size')
              maxAmountString = self.safe_string(market, 'maximum_order_size')
              limits = {
                  'amount': {
                      'min': self.parse_number(minAmountString),
                      'max': self.parse_number(maxAmountString),
                  },
                  'price': {
                      'min': self.parse_number('1e-8'),
                      'max': None,
                  },
              }
              limits['cost'] = {
                  'min': self.parse_number(Precise.string_mul(minAmountString, maxAmountString)),
                  'max': None,
              }
              margin = self.safe_value(market, 'margin')
              result.append({
                  'id': id,
                  'symbol': symbol,
                  'base': base,
                  'quote': quote,
                  'baseId': baseId,
                  'quoteId': quoteId,
                  'active': True,
                  'type': 'spot',
                  'margin': margin,
                  'precision': precision,
                  'limits': limits,
                  'info': market,
              })
          return result
  
      def amount_to_precision(self, symbol, amount):
          # https://docs.bitfinex.com/docs/introduction#amount-precision
          # The amount field allows up to 8 decimals.
          # Anything exceeding self will be rounded to the 8th decimal.
          return self.decimal_to_precision(amount, TRUNCATE, self.markets[symbol]['precision']['amount'], DECIMAL_PLACES)
  
      def price_to_precision(self, symbol, price):
          price = self.decimal_to_precision(price, ROUND, self.markets[symbol]['precision']['price'], self.precisionMode)
          # https://docs.bitfinex.com/docs/introduction#price-precision
          # The precision level of all trading prices is based on significant figures.
          # All pairs on Bitfinex use up to 5 significant digits and up to 8 decimals(e.g. 1.2345, 123.45, 1234.5, 0.00012345).
          # Prices submit with a precision larger than 5 will be cut by the API.
          return self.decimal_to_precision(price, TRUNCATE, 8, DECIMAL_PLACES)
  
      async def fetch_balance(self, params={}):
          await self.load_markets()
          accountsByType = self.safe_value(self.options, 'accountsByType', {})
          requestedType = self.safe_string(params, 'type', 'exchange')
          accountType = self.safe_string(accountsByType, requestedType)
          if accountType is None:
              keys = list(accountsByType.keys())
              raise ExchangeError(self.id + ' fetchBalance type parameter must be one of ' + ', '.join(keys))
          query = self.omit(params, 'type')
          response = await self.privatePostBalances(query)
          #    [{type: 'deposit',
          #        currency: 'btc',
          #        amount: '0.00116721',
          #        available: '0.00116721'},
          #      {type: 'exchange',
          #        currency: 'ust',
          #        amount: '0.0000002',
          #        available: '0.0000002'},
          #      {type: 'trading',
          #        currency: 'btc',
          #        amount: '0.0005',
          #        available: '0.0005'}],
          result = {'info': response}
          isDerivative = requestedType == 'derivatives'
          for i in range(0, len(response)):
              balance = response[i]
              type = self.safe_string(balance, 'type')
              currencyId = self.safe_string_lower(balance, 'currency', '')
              start = len(currencyId) - 2
              isDerivativeCode = currencyId[start:] == 'f0'
              # self will only filter the derivative codes if the requestedType is 'derivatives'
              derivativeCondition = (not isDerivative or isDerivativeCode)
              if (accountType == type) and derivativeCondition:
                  code = self.safe_currency_code(currencyId)
                  # bitfinex had BCH previously, now it's BAB, but the old
                  # BCH symbol is kept for backward-compatibility
                  # we need a workaround here so that the old BCH balance
                  # would not override the new BAB balance(BAB is unified to BCH)
                  # https://github.com/ccxt/ccxt/issues/4989
                  if not (code in result):
                      account = self.account()
                      account['free'] = self.safe_string(balance, 'available')
                      account['total'] = self.safe_string(balance, 'amount')
                      result[code] = account
          return self.parse_balance(result, False)
  
      async def transfer(self, code, amount, fromAccount, toAccount, params={}):
          # transferring between derivatives wallet and regular wallet is not documented in their API
          # however we support it in CCXT(from just looking at web inspector)
          await self.load_markets()
          accountsByType = self.safe_value(self.options, 'accountsByType', {})
          fromId = self.safe_string(accountsByType, fromAccount)
          if fromId is None:
              keys = list(accountsByType.keys())
              raise ExchangeError(self.id + ' transfer fromAccount must be one of ' + ', '.join(keys))
          toId = self.safe_string(accountsByType, toAccount)
          if toId is None:
              keys = list(accountsByType.keys())
              raise ExchangeError(self.id + ' transfer toAccount must be one of ' + ', '.join(keys))
          currencyId = self.currency_id(code)
          fromCurrencyId = self.convert_derivatives_id(currencyId, fromAccount)
          toCurrencyId = self.convert_derivatives_id(currencyId, toAccount)
          requestedAmount = self.currency_to_precision(code, amount)
          request = {
              'amount': requestedAmount,
              'currency': fromCurrencyId,
              'currency_to': toCurrencyId,
              'walletfrom': fromId,
              'walletto': toId,
          }
          response = await self.privatePostTransfer(self.extend(request, params))
          # [
          #   {
          #     status: 'success',
          #     message: '0.0001 Bitcoin transfered from Margin to Exchange'
          #   }
          # ]
          result = self.safe_value(response, 0)
          status = self.safe_string(result, 'status')
          message = self.safe_string(result, 'message')
          if message is None:
              raise ExchangeError(self.id + ' transfer failed')
          # [{"status":"error","message":"Momentary balance check. Please wait few seconds and try the transfer again."}]
          if status == 'error':
              self.throw_exactly_matched_exception(self.exceptions['exact'], message, self.id + ' ' + message)
              raise ExchangeError(self.id + ' ' + message)
          return {
              'info': response,
              'status': status,
              'amount': requestedAmount,
              'code': code,
              'fromAccount': fromAccount,
              'toAccount': toAccount,
              'timestamp': None,
              'datetime': None,
          }
  
      def convert_derivatives_id(self, currencyId, type):
          start = len(currencyId) - 2
          isDerivativeCode = currencyId[start:] == 'F0'
          if (type != 'derivatives' and type != 'trading' and type != 'margin') and isDerivativeCode:
              currencyId = currencyId[0:start]
          elif type == 'derivatives' and not isDerivativeCode:
              currencyId = currencyId + 'F0'
          return currencyId
  
      async def fetch_order_book(self, symbol, limit=None, params={}):
          await self.load_markets()
          request = {
              'symbol': self.market_id(symbol),
          }
          if limit is not None:
              request['limit_bids'] = limit
              request['limit_asks'] = limit
          response = await self.publicGetBookSymbol(self.extend(request, params))
          return self.parse_order_book(response, None, 'bids', 'asks', 'price', 'amount')
  
      async def fetch_tickers(self, symbols=None, params={}):
          await self.load_markets()
          response = await self.publicGetTickers(params)
          result = {}
          for i in range(0, len(response)):
              ticker = self.parse_ticker(response[i])
              symbol = ticker['symbol']
              result[symbol] = ticker
          return self.filter_by_array(result, 'symbol', symbols)
  
      async def fetch_ticker(self, symbol, params={}):
          await self.load_markets()
          market = self.market(symbol)
          request = {
              'symbol': market['id'],
          }
          ticker = await self.publicGetPubtickerSymbol(self.extend(request, params))
          return self.parse_ticker(ticker, market)
  
      def parse_ticker(self, ticker, market=None):
          timestamp = self.safe_number(ticker, 'timestamp')
          if timestamp is not None:
              timestamp *= 1000
          timestamp = int(timestamp)
          symbol = None
          if market is not None:
              symbol = market['symbol']
          elif 'pair' in ticker:
              marketId = self.safe_string(ticker, 'pair')
              if marketId is not None:
                  if marketId in self.markets_by_id:
                      market = self.markets_by_id[marketId]
                      symbol = market['symbol']
                  else:
                      baseId = marketId[0:3]
                      quoteId = marketId[3:6]
                      base = self.safe_currency_code(baseId)
                      quote = self.safe_currency_code(quoteId)
                      symbol = base + '/' + quote
          last = self.safe_number(ticker, 'last_price')
          return {
              'symbol': symbol,
              'timestamp': timestamp,
              'datetime': self.iso8601(timestamp),
              'high': self.safe_number(ticker, 'high'),
              'low': self.safe_number(ticker, 'low'),
              'bid': self.safe_number(ticker, 'bid'),
              'bidVolume': None,
              'ask': self.safe_number(ticker, 'ask'),
              'askVolume': None,
              'vwap': None,
              'open': None,
              'close': last,
              'last': last,
              'previousClose': None,
              'change': None,
              'percentage': None,
              'average': self.safe_number(ticker, 'mid'),
              'baseVolume': self.safe_number(ticker, 'volume'),
              'quoteVolume': None,
              'info': ticker,
          }
  
      def parse_trade(self, trade, market):
          id = self.safe_string(trade, 'tid')
          timestamp = self.safe_number(trade, 'timestamp')
          if timestamp is not None:
              timestamp = int(timestamp) * 1000
          type = None
          side = self.safe_string_lower(trade, 'type')
          orderId = self.safe_string(trade, 'order_id')
          priceString = self.safe_string(trade, 'price')
          amountString = self.safe_string(trade, 'amount')
          price = self.parse_number(priceString)
          amount = self.parse_number(amountString)
          cost = self.parse_number(Precise.string_mul(priceString, amountString))
          fee = None
          if 'fee_amount' in trade:
              feeCost = -self.safe_number(trade, 'fee_amount')
              feeCurrencyId = self.safe_string(trade, 'fee_currency')
              feeCurrencyCode = self.safe_currency_code(feeCurrencyId)
              fee = {
                  'cost': feeCost,
                  'currency': feeCurrencyCode,
              }
          return {
              'id': id,
              'info': trade,
              'timestamp': timestamp,
              'datetime': self.iso8601(timestamp),
              'symbol': market['symbol'],
              'type': type,
              'order': orderId,
              'side': side,
              'takerOrMaker': None,
              'price': price,
              'amount': amount,
              'cost': cost,
              'fee': fee,
          }
  
      async def fetch_trades(self, symbol, since=None, limit=50, params={}):
          await self.load_markets()
          market = self.market(symbol)
          request = {
              'symbol': market['id'],
              'limit_trades': limit,
          }
          if since is not None:
              request['timestamp'] = int(since / 1000)
          response = await self.publicGetTradesSymbol(self.extend(request, params))
          return self.parse_trades(response, market, since, limit)
  
      async def fetch_my_trades(self, symbol=None, since=None, limit=None, params={}):
          if symbol is None:
              raise ArgumentsRequired(self.id + ' fetchMyTrades() requires a `symbol` argument')
          await self.load_markets()
          market = self.market(symbol)
          request = {
              'symbol': market['id'],
          }
          if limit is not None:
              request['limit_trades'] = limit
          if since is not None:
              request['timestamp'] = int(since / 1000)
          response = await self.privatePostMytrades(self.extend(request, params))
          return self.parse_trades(response, market, since, limit)
  
      async def create_order(self, symbol, type, side, amount, price=None, params={}):
          await self.load_markets()
          request = {
              'symbol': self.market_id(symbol),
              'side': side,
              'amount': self.amount_to_precision(symbol, amount),
              'type': self.safe_string(self.options['orderTypes'], type, type),
              'ocoorder': False,
              'buy_price_oco': 0,
              'sell_price_oco': 0,
          }
          if type == 'market':
              request['price'] = str(self.nonce())
          else:
              request['price'] = self.price_to_precision(symbol, price)
          response = await self.privatePostOrderNew(self.extend(request, params))
          return self.parse_order(response)
  
      async def edit_order(self, id, symbol, type, side, amount=None, price=None, params={}):
          await self.load_markets()
          order = {
              'order_id': int(id),
          }
          if price is not None:
              order['price'] = self.price_to_precision(symbol, price)
          if amount is not None:
              order['amount'] = self.number_to_string(amount)
          if symbol is not None:
              order['symbol'] = self.market_id(symbol)
          if side is not None:
              order['side'] = side
          if type is not None:
              order['type'] = self.safe_string(self.options['orderTypes'], type, type)
          response = await self.privatePostOrderCancelReplace(self.extend(order, params))
          return self.parse_order(response)
  
      async def cancel_order(self, id, symbol=None, params={}):
          await self.load_markets()
          request = {
              'order_id': int(id),
          }
          return await self.privatePostOrderCancel(self.extend(request, params))
  
      async def cancel_all_orders(self, symbol=None, params={}):
          return await self.privatePostOrderCancelAll(params)
  
      def parse_order(self, order, market=None):
          #
          #     {
          #           id: 57334010955,
          #           cid: 1611584840966,
          #           cid_date: null,
          #           gid: null,
          #           symbol: 'ltcbtc',
          #           exchange: null,
          #           price: '0.0042125',
          #           avg_execution_price: '0.0042097',
          #           side: 'sell',
          #           type: 'exchange market',
          #           timestamp: '1611584841.0',
          #           is_live: False,
          #           is_cancelled: False,
          #           is_hidden: 0,
          #           oco_order: 0,
          #           was_forced: False,
          #           original_amount: '0.205176',
          #           remaining_amount: '0.0',
          #           executed_amount: '0.205176',
          #           src: 'web'
          #     }
          #
          side = self.safe_string(order, 'side')
          open = self.safe_value(order, 'is_live')
          canceled = self.safe_value(order, 'is_cancelled')
          status = None
          if open:
              status = 'open'
          elif canceled:
              status = 'canceled'
          else:
              status = 'closed'
          marketId = self.safe_string_upper(order, 'symbol')
          symbol = self.safe_symbol(marketId, market)
          orderType = self.safe_string(order, 'type', '')
          exchange = orderType.find('exchange ') >= 0
          if exchange:
              parts = order['type'].split(' ')
              orderType = parts[1]
          timestamp = self.safe_timestamp(order, 'timestamp')
          id = self.safe_string(order, 'id')
          return self.safe_order({
              'info': order,
              'id': id,
              'clientOrderId': None,
              'timestamp': timestamp,
              'datetime': self.iso8601(timestamp),
              'lastTradeTimestamp': None,
              'symbol': symbol,
              'type': orderType,
              'timeInForce': None,
              'postOnly': None,
              'side': side,
              'price': self.safe_number(order, 'price'),
              'stopPrice': None,
              'average': self.safe_number(order, 'avg_execution_price'),
              'amount': self.safe_number(order, 'original_amount'),
              'remaining': self.safe_number(order, 'remaining_amount'),
              'filled': self.safe_number(order, 'executed_amount'),
              'status': status,
              'fee': None,
              'cost': None,
              'trades': None,
          })
  
      async def fetch_open_orders(self, symbol=None, since=None, limit=None, params={}):
          await self.load_markets()
          if symbol is not None:
              if not (symbol in self.markets):
                  raise ExchangeError(self.id + ' has no symbol ' + symbol)
          response = await self.privatePostOrders(params)
          orders = self.parse_orders(response, None, since, limit)
          if symbol is not None:
              orders = self.filter_by(orders, 'symbol', symbol)
          return orders
  
      async def fetch_closed_orders(self, symbol=None, since=None, limit=None, params={}):
          await self.load_markets()
          request = {}
          if limit is not None:
              request['limit'] = limit
          response = await self.privatePostOrdersHist(self.extend(request, params))
          orders = self.parse_orders(response, None, since, limit)
          if symbol is not None:
              orders = self.filter_by(orders, 'symbol', symbol)
          orders = self.filter_by_array(orders, 'status', ['closed', 'canceled'], False)
          return orders
  
      async def fetch_order(self, id, symbol=None, params={}):
          await self.load_markets()
          request = {
              'order_id': int(id),
          }
          response = await self.privatePostOrderStatus(self.extend(request, params))
          return self.parse_order(response)
  
      def parse_ohlcv(self, ohlcv, market=None):
          #
          #     [
          #         1457539800000,
          #         0.02594,
          #         0.02594,
          #         0.02594,
          #         0.02594,
          #         0.1
          #     ]
          #
          return [
              self.safe_integer(ohlcv, 0),
              self.safe_number(ohlcv, 1),
              self.safe_number(ohlcv, 3),
              self.safe_number(ohlcv, 4),
              self.safe_number(ohlcv, 2),
              self.safe_number(ohlcv, 5),
          ]
  
      async def fetch_ohlcv(self, symbol, timeframe='1m', since=None, limit=None, params={}):
          await self.load_markets()
          if limit is None:
              limit = 100
          market = self.market(symbol)
          v2id = 't' + market['id']
          request = {
              'symbol': v2id,
              'timeframe': self.timeframes[timeframe],
              'sort': 1,
              'limit': limit,
          }
          if since is not None:
              request['start'] = since
          response = await self.v2GetCandlesTradeTimeframeSymbolHist(self.extend(request, params))
          #
          #     [
          #         [1457539800000,0.02594,0.02594,0.02594,0.02594,0.1],
          #         [1457547300000,0.02577,0.02577,0.02577,0.02577,0.01],
          #         [1457550240000,0.0255,0.0253,0.0255,0.0252,3.2640000000000002],
          #     ]
          #
          return self.parse_ohlcvs(response, market, timeframe, since, limit)
  
      def get_currency_name(self, code):
          # todo rewrite for https://api-pub.bitfinex.com//v2/conf/pub:map:tx:method
          if code in self.options['currencyNames']:
              return self.options['currencyNames'][code]
          raise NotSupported(self.id + ' ' + code + ' not supported for withdrawal')
  
      async def create_deposit_address(self, code, params={}):
          await self.load_markets()
          request = {
              'renew': 1,
          }
          response = await self.fetch_deposit_address(code, self.extend(request, params))
          return response
  
      async def fetch_deposit_address(self, code, params={}):
          await self.load_markets()
          # todo rewrite for https://api-pub.bitfinex.com//v2/conf/pub:map:tx:method
          name = self.get_currency_name(code)
          request = {
              'method': name,
              'wallet_name': 'exchange',
              'renew': 0,  # a value of 1 will generate a new address
          }
          response = await self.privatePostDepositNew(self.extend(request, params))
          address = self.safe_value(response, 'address')
          tag = None
          if 'address_pool' in response:
              tag = address
              address = response['address_pool']
          self.check_address(address)
          return {
              'currency': code,
              'address': address,
              'tag': tag,
              'info': response,
          }
  
      async def fetch_transactions(self, code=None, since=None, limit=None, params={}):
          await self.load_markets()
          currencyId = self.safe_string(params, 'currency')
          query = self.omit(params, 'currency')
          currency = None
          if currencyId is None:
              if code is None:
                  raise ArgumentsRequired(self.id + ' fetchTransactions() requires a currency `code` argument or a `currency` parameter')
              else:
                  currency = self.currency(code)
                  currencyId = currency['id']
          query['currency'] = currencyId
          if since is not None:
              query['since'] = int(since / 1000)
          response = await self.privatePostHistoryMovements(self.extend(query, params))
          #
          #     [
          #         {
          #             "id":581183,
          #             "txid": 123456,
          #             "currency":"BTC",
          #             "method":"BITCOIN",
          #             "type":"WITHDRAWAL",
          #             "amount":".01",
          #             "description":"3QXYWgRGX2BPYBpUDBssGbeWEa5zq6snBZ, offchain transfer ",
          #             "address":"3QXYWgRGX2BPYBpUDBssGbeWEa5zq6snBZ",
          #             "status":"COMPLETED",
          #             "timestamp":"1443833327.0",
          #             "timestamp_created": "1443833327.1",
          #             "fee": 0.1,
          #         }
          #     ]
          #
          return self.parse_transactions(response, currency, since, limit)
  
      def parse_transaction(self, transaction, currency=None):
          #
          # crypto
          #
          #     {
          #         "id": 12042490,
          #         "fee": "-0.02",
          #         "txid": "EA5B5A66000B66855865EFF2494D7C8D1921FCBE996482157EBD749F2C85E13D",
          #         "type": "DEPOSIT",
          #         "amount": "2099.849999",
          #         "method": "RIPPLE",
          #         "status": "COMPLETED",
          #         "address": "2505189261",
          #         "currency": "XRP",
          #         "timestamp": "1551730524.0",
          #         "description": "EA5B5A66000B66855865EFF2494D7C8D1921FCBE996482157EBD749F2C85E13D",
          #         "timestamp_created": "1551730523.0"
          #     }
          #
          # fiat
          #
          #     {
          #         "id": 12725095,
          #         "fee": "-60.0",
          #         "txid": null,
          #         "type": "WITHDRAWAL",
          #         "amount": "9943.0",
          #         "method": "WIRE",
          #         "status": "SENDING",
          #         "address": null,
          #         "currency": "EUR",
          #         "timestamp": "1561802484.0",
          #         "description": "Name: bob, AccountAddress: some address, Account: someaccountno, Bank: bank address, SWIFT: foo, Country: UK, Details of Payment: withdrawal name, Intermediary Bank Name: , Intermediary Bank Address: , Intermediary Bank City: , Intermediary Bank Country: , Intermediary Bank Account: , Intermediary Bank SWIFT: , Fee: -60.0",
          #         "timestamp_created": "1561716066.0"
          #     }
          #
          timestamp = self.safe_number(transaction, 'timestamp_created')
          if timestamp is not None:
              timestamp = int(timestamp * 1000)
          updated = self.safe_number(transaction, 'timestamp')
          if updated is not None:
              updated = int(updated * 1000)
          currencyId = self.safe_string(transaction, 'currency')
          code = self.safe_currency_code(currencyId, currency)
          type = self.safe_string_lower(transaction, 'type')  # DEPOSIT or WITHDRAWAL
          status = self.parse_transaction_status(self.safe_string(transaction, 'status'))
          feeCost = self.safe_number(transaction, 'fee')
          if feeCost is not None:
              feeCost = abs(feeCost)
          return {
              'info': transaction,
              'id': self.safe_string(transaction, 'id'),
              'txid': self.safe_string(transaction, 'txid'),
              'timestamp': timestamp,
              'datetime': self.iso8601(timestamp),
              'address': self.safe_string(transaction, 'address'),  # todo: self is actually the tag for XRP transfers(the address is missing)
              'tag': None,  # refix it properly for the tag from description
              'type': type,
              'amount': self.safe_number(transaction, 'amount'),
              'currency': code,
              'status': status,
              'updated': updated,
              'fee': {
                  'currency': code,
                  'cost': feeCost,
                  'rate': None,
              },
          }
  
      def parse_transaction_status(self, status):
          statuses = {
              'SENDING': 'pending',
              'CANCELED': 'canceled',
              'ZEROCONFIRMED': 'failed',  # ZEROCONFIRMED happens e.g. in a double spend attempt(I had one in my movementsnot )
              'COMPLETED': 'ok',
          }
          return self.safe_string(statuses, status, status)
  
      async def withdraw(self, code, amount, address, tag=None, params={}):
          self.check_address(address)
          await self.load_markets()
          # todo rewrite for https://api-pub.bitfinex.com//v2/conf/pub:map:tx:method
          name = self.get_currency_name(code)
          request = {
              'withdraw_type': name,
              'walletselected': 'exchange',
              'amount': self.number_to_string(amount),
              'address': address,
          }
          if tag is not None:
              request['payment_id'] = tag
          responses = await self.privatePostWithdraw(self.extend(request, params))
          response = responses[0]
          id = self.safe_string(response, 'withdrawal_id')
          message = self.safe_string(response, 'message')
          errorMessage = self.find_broadly_matched_key(self.exceptions['broad'], message)
          if id == 0:
              if errorMessage is not None:
                  ExceptionClass = self.exceptions['broad'][errorMessage]
                  raise ExceptionClass(self.id + ' ' + message)
              raise ExchangeError(self.id + ' withdraw returned an id of zero: ' + self.json(response))
          return {
              'info': response,
              'id': id,
          }
  
      async def fetch_positions(self, symbols=None, params={}):
          await self.load_markets()
          response = await self.privatePostPositions(params)
          #
          #     [
          #         {
          #             "id":943715,
          #             "symbol":"btcusd",
          #             "status":"ACTIVE",
          #             "base":"246.94",
          #             "amount":"1.0",
          #             "timestamp":"1444141857.0",
          #             "swap":"0.0",
          #             "pl":"-2.22042"
          #         }
          #     ]
          #
          # todo unify parsePosition/parsePositions
          return response
  
      def nonce(self):
          return self.milliseconds()
  
      def sign(self, path, api='public', method='GET', params={}, headers=None, body=None):
          request = '/' + self.implode_params(path, params)
          if api == 'v2':
              request = '/' + api + request
          else:
              request = '/' + self.version + request
          query = self.omit(params, self.extract_params(path))
          url = self.urls['api'][api] + request
          if (api == 'public') or (path.find('/hist') >= 0):
              if query:
                  suffix = '?' + self.urlencode(query)
                  url += suffix
                  request += suffix
          if api == 'private':
              self.check_required_credentials()
              nonce = self.nonce()
              query = self.extend({
                  'nonce': str(nonce),
                  'request': request,
              }, query)
              body = self.json(query)
              payload = self.string_to_base64(body)
              secret = self.encode(self.secret)
              signature = self.hmac(payload, secret, hashlib.sha384)
              headers = {
                  'X-BFX-APIKEY': self.apiKey,
                  'X-BFX-PAYLOAD': self.decode(payload),
                  'X-BFX-SIGNATURE': signature,
                  'Content-Type': 'application/json',
              }
          return {'url': url, 'method': method, 'body': body, 'headers': headers}
  
      def handle_errors(self, code, reason, url, method, headers, body, response, requestHeaders, requestBody):
          if response is None:
              return
          if code >= 400:
              if body[0] == '{':
                  feedback = self.id + ' ' + body
                  message = self.safe_string_2(response, 'message', 'error')
                  self.throw_exactly_matched_exception(self.exceptions['exact'], message, feedback)
                  self.throw_broadly_matched_exception(self.exceptions['broad'], message, feedback)
                  raise ExchangeError(feedback)  # unknown message
  