# -*- 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
from ccxt.abstract.bitfinex import ImplicitAPI
import hashlib
from ccxt.base.types import Balances, Currency, Int, Market, Num, Order, OrderBook, OrderSide, OrderType, Str, Strings, Ticker, Tickers, Trade, TradingFees, Transaction, TransferEntry
from typing import List
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, ImplicitAPI):

    def describe(self):
        return self.deep_extend(super(bitfinex, self).describe(), {
            'id': 'bitfinex',
            'name': 'Bitfinex',
            'countries': ['VG'],
            'version': 'v1',
            # cheapest is 90 requests a minute = 1.5 requests per second on average =>( 1000ms / 1.5) = 666.666 ms between requests on average
            'rateLimit': 666.666,
            'pro': True,
            # new metainfo interface
            'has': {
                'CORS': None,
                'spot': True,
                'margin': None,  # has but unimplemented
                'swap': None,  # has but unimplemented
                'future': None,
                'option': None,
                'cancelAllOrders': True,
                'cancelOrder': True,
                'createDepositAddress': True,
                'createOrder': True,
                'editOrder': True,
                'fetchBalance': True,
                'fetchClosedOrders': True,
                'fetchDepositAddress': True,
                'fetchDeposits': False,
                'fetchDepositsWithdrawals': True,
                'fetchDepositWithdrawFee': 'emulated',
                'fetchDepositWithdrawFees': True,
                'fetchIndexOHLCV': False,
                'fetchLeverageTiers': False,
                'fetchMarginMode': False,
                'fetchMarkets': True,
                'fetchMarkOHLCV': False,
                'fetchMyTrades': True,
                'fetchOHLCV': True,
                'fetchOpenOrders': True,
                'fetchOrder': True,
                'fetchOrderBook': True,
                'fetchPositionMode': False,
                'fetchPositions': True,
                'fetchPremiumIndexOHLCV': False,
                'fetchTicker': True,
                'fetchTickers': True,
                'fetchTime': False,
                'fetchTrades': True,
                'fetchTradingFee': False,
                'fetchTradingFees': True,
                'fetchTransactionFees': True,
                'fetchTransactions': 'emulated',
                'transfer': True,
                'withdraw': 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': 3,  # 30 requests per minute
                        'tickers': 1,  # 90 requests a minute
                        'ticker/{symbol}': 1,
                        'tickers/hist': 1,
                        'trades/{symbol}/hist': 1,
                        'book/{symbol}/{precision}': 0.375,  # 240 requests per minute = 4 requests per second(1000ms / rateLimit) / 4  = 0.37500375
                        'book/{symbol}/P0': 0.375,
                        'book/{symbol}/P1': 0.375,
                        'book/{symbol}/P2': 0.375,
                        'book/{symbol}/P3': 0.375,
                        'book/{symbol}/R0': 0.375,
                        'stats1/{key}:{size}:{symbol}:{side}/{section}': 1,  # 90 requests a minute
                        'stats1/{key}:{size}:{symbol}/{section}': 1,
                        'stats1/{key}:{size}:{symbol}:long/last': 1,
                        'stats1/{key}:{size}:{symbol}:long/hist': 1,
                        'stats1/{key}:{size}:{symbol}:short/last': 1,
                        'stats1/{key}:{size}:{symbol}:short/hist': 1,
                        'candles/trade:{timeframe}:{symbol}/{section}': 1,  # 90 requests a minute
                        'candles/trade:{timeframe}:{symbol}/last': 1,
                        'candles/trade:{timeframe}:{symbol}/hist': 1,
                    },
                },
                'public': {
                    'get': {
                        'book/{symbol}': 1,  # 90 requests a minute
                        # 'candles/{symbol}':0,
                        'lendbook/{currency}': 6,  # 15 requests a minute
                        'lends/{currency}': 3,  # 30 requests a minute
                        'pubticker/{symbol}': 3,  # 30 requests a minute = 0.5 requests per second =>(1000ms / rateLimit) / 0.5 = 3.00003
                        'stats/{symbol}': 6,  # 15 requests a minute = 0.25 requests per second =>(1000ms / rateLimit ) /0.25 = 6.00006(endpoint returns red html... or 'unknown symbol')
                        'symbols': 18,  # 5 requests a minute = 0.08333 requests per second =>(1000ms / rateLimit) / 0.08333 = 18.0009
                        'symbols_details': 18,  # 5 requests a minute
                        'tickers': 1,  # endpoint not mentioned in v1 docs... but still responds
                        'trades/{symbol}': 3,  # 60 requests a minute = 1 request per second =>(1000ms / rateLimit) / 1 = 1.5 ... but only works if set to 3
                    },
                },
                'private': {
                    'post': {
                        'account_fees': 18,
                        'account_infos': 6,
                        'balances': 9.036,  # 10 requests a minute = 0.166 requests per second =>(1000ms / rateLimit) / 0.166 = 9.036
                        'basket_manage': 6,
                        'credits': 6,
                        'deposit/new': 18,
                        'funding/close': 6,
                        'history': 6,  # 15 requests a minute
                        'history/movements': 6,
                        'key_info': 6,
                        'margin_infos': 3,  # 30 requests a minute
                        'mytrades': 3,
                        'mytrades_funding': 6,
                        'offer/cancel': 6,
                        'offer/new': 6,
                        'offer/status': 6,
                        'offers': 6,
                        'offers/hist': 90.03,  # one request per minute
                        'order/cancel': 0.2,
                        'order/cancel/all': 0.2,
                        'order/cancel/multi': 0.2,
                        'order/cancel/replace': 0.2,
                        'order/new': 0.2,  # 450 requests a minute = 7.5 request a second =>(1000ms / rateLimit) / 7.5 = 0.2000002
                        'order/new/multi': 0.2,
                        'order/status': 0.2,
                        'orders': 0.2,
                        'orders/hist': 90.03,  # one request per minute = 0.1666 =>(1000ms /  rateLimit) / 0.01666 = 90.03
                        'position/claim': 18,
                        'position/close': 18,
                        'positions': 18,
                        'summary': 18,
                        'taken_funds': 6,
                        'total_taken_funds': 6,
                        'transfer': 18,
                        'unused_taken_funds': 6,
                        'withdraw': 18,
                    },
                },
            },
            'fees': {
                'trading': {
                    'feeSide': 'get',
                    'tierBased': True,
                    'percentage': True,
                    'maker': self.parse_number('0.001'),
                    'taker': self.parse_number('0.002'),
                    'tiers': {
                        'taker': [
                            [self.parse_number('0'), self.parse_number('0.002')],
                            [self.parse_number('500000'), self.parse_number('0.002')],
                            [self.parse_number('1000000'), self.parse_number('0.002')],
                            [self.parse_number('2500000'), self.parse_number('0.002')],
                            [self.parse_number('5000000'), self.parse_number('0.002')],
                            [self.parse_number('7500000'), self.parse_number('0.002')],
                            [self.parse_number('10000000'), self.parse_number('0.0018')],
                            [self.parse_number('15000000'), self.parse_number('0.0016')],
                            [self.parse_number('20000000'), self.parse_number('0.0014')],
                            [self.parse_number('25000000'), self.parse_number('0.0012')],
                            [self.parse_number('30000000'), self.parse_number('0.001')],
                        ],
                        'maker': [
                            [self.parse_number('0'), self.parse_number('0.001')],
                            [self.parse_number('500000'), self.parse_number('0.0008')],
                            [self.parse_number('1000000'), self.parse_number('0.0006')],
                            [self.parse_number('2500000'), self.parse_number('0.0004')],
                            [self.parse_number('5000000'), self.parse_number('0.0002')],
                            [self.parse_number('7500000'), self.parse_number('0')],
                            [self.parse_number('10000000'), self.parse_number('0')],
                            [self.parse_number('15000000'), self.parse_number('0')],
                            [self.parse_number('20000000'), self.parse_number('0')],
                            [self.parse_number('25000000'), self.parse_number('0')],
                            [self.parse_number('30000000'), self.parse_number('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': {},
                    'withdraw': {},
                },
            },
            # todo rewrite for https://api-pub.bitfinex.com//v2/conf/pub:map:tx:method
            'commonCurrencies': {
                'ALG': 'ALGO',  # https://github.com/ccxt/ccxt/issues/6034
                'AMP': 'AMPL',
                'ATO': 'ATOM',  # https://github.com/ccxt/ccxt/issues/5118
                'BCHABC': 'XEC',
                'BCHN': 'BCH',
                'DAT': 'DATA',
                'DOG': 'MDOGE',
                'DSH': 'DASH',
                # 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',
                'EUS': 'EURS',
                'EUT': 'EURT',
                'IDX': 'ID',
                'IOT': 'IOTA',
                'IQX': 'IQ',
                'LUNA': 'LUNC',
                'LUNA2': 'LUNA',
                'MNA': 'MANA',
                'ORS': 'ORS Group',  # conflict with Origin Sport  #3230
                'PAS': 'PASS',
                'QSH': 'QASH',
                'QTM': 'QTUM',
                'RBT': 'RBTC',
                'SNG': 'SNGLS',
                'STJ': 'STORJ',
                'TERRAUST': 'USTC',
                'TSD': 'TUSD',
                'YGG': 'YEED',  # conflict with Yield Guild Games
                'YYW': 'YOYOW',
                'UDC': 'USDC',
                'UST': 'USDT',
                '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',
                },
                'fiat': {
                    'USD': 'USD',
                    'EUR': 'EUR',
                    'JPY': 'JPY',
                    'GBP': 'GBP',
                    'CNH': 'CNH',
                },
                'accountsByType': {
                    'spot': 'exchange',
                    'margin': 'trading',
                    'funding': 'deposit',
                    'swap': 'trading',
                },
            },
        })

    async def fetch_transaction_fees(self, codes: List[str] = None, params={}):
        """
         * @deprecated
        please use fetchDepositWithdrawFees instead
        :see: https://docs.bitfinex.com/v1/reference/rest-auth-fees
        :param str[]|None codes: list of unified currency codes
        :param dict [params]: extra parameters specific to the exchange API endpoint
        :returns dict[]: a list of `fees structures <https://docs.ccxt.com/#/?id=fee-structure>`
        """
        await self.load_markets()
        result = {}
        response = await self.privatePostAccountFees(params)
        #
        # {
        #     "withdraw": {
        #         "BTC": "0.0004",
        #     }
        # }
        #
        fees = self.safe_value(response, 'withdraw')
        ids = list(fees.keys())
        for i in range(0, len(ids)):
            id = ids[i]
            code = self.safe_currency_code(id)
            if (codes is not None) and not self.in_array(code, codes):
                continue
            result[code] = {
                'withdraw': self.safe_number(fees, id),
                'deposit': {},
                'info': self.safe_number(fees, id),
            }
        return result

    async def fetch_deposit_withdraw_fees(self, codes: Strings = None, params={}):
        """
        fetch deposit and withdraw fees
        :see: https://docs.bitfinex.com/v1/reference/rest-auth-fees
        :param str[]|None codes: list of unified currency codes
        :param dict [params]: extra parameters specific to the exchange API endpoint
        :returns dict[]: a list of `fees structures <https://docs.ccxt.com/#/?id=fee-structure>`
        """
        await self.load_markets()
        response = await self.privatePostAccountFees(params)
        #
        #    {
        #        "withdraw": {
        #            "BTC": "0.0004",
        #            ...
        #        }
        #    }
        #
        withdraw = self.safe_list(response, 'withdraw')
        return self.parse_deposit_withdraw_fees(withdraw, codes)

    def parse_deposit_withdraw_fee(self, fee, currency: Currency = None):
        #
        #    '0.0004'
        #
        return {
            'withdraw': {
                'fee': self.parse_number(fee),
                'percentage': None,
            },
            'deposit': {
                'fee': None,
                'percentage': None,
            },
            'networks': {},
            'info': fee,
        }

    async def fetch_trading_fees(self, params={}) -> TradingFees:
        """
        fetch the trading fees for multiple markets
        :see: https://docs.bitfinex.com/v1/reference/rest-auth-summary
        :param dict [params]: extra parameters specific to the exchange API endpoint
        :returns dict: a dictionary of `fee structures <https://docs.ccxt.com/#/?id=fee-structure>` indexed by market symbols
        """
        await self.load_markets()
        response = await self.privatePostSummary(params)
        #
        #     {
        #          "time": "2022-02-23T16:05:47.659000Z",
        #          "status": {resid_hint: null, login_last: "2022-02-23T16:05:48Z"},
        #          "is_locked": False,
        #          "leo_lev": "0",
        #          "leo_amount_avg": "0.0",
        #          "trade_vol_30d": [
        #          {
        #              "curr": "Total(USD)",
        #              "vol": "0.0",
        #              "vol_safe": "0.0",
        #              "vol_maker": "0.0",
        #              "vol_BFX": "0.0",
        #              "vol_BFX_safe": "0.0",
        #              "vol_BFX_maker": "0.0"
        #          }
        #          ],
        #          "fees_funding_30d": {},
        #          "fees_funding_total_30d": "0",
        #          "fees_trading_30d": {},
        #          "fees_trading_total_30d": "0",
        #          "rebates_trading_30d": {},
        #          "rebates_trading_total_30d": "0",
        #          "maker_fee": "0.001",
        #          "taker_fee": "0.002",
        #          "maker_fee_2crypto": "0.001",
        #          "maker_fee_2stablecoin": "0.001",
        #          "maker_fee_2fiat": "0.001",
        #          "maker_fee_2deriv": "0.0002",
        #          "taker_fee_2crypto": "0.002",
        #          "taker_fee_2stablecoin": "0.002",
        #          "taker_fee_2fiat": "0.002",
        #          "taker_fee_2deriv": "0.00065",
        #          "deriv_maker_rebate": "0.0002",
        #          "deriv_taker_fee": "0.00065",
        #          "trade_last": null
        #     }
        #
        result = {}
        fiat = self.safe_value(self.options, 'fiat', {})
        makerFee = self.safe_number(response, 'maker_fee')
        takerFee = self.safe_number(response, 'taker_fee')
        makerFee2Fiat = self.safe_number(response, 'maker_fee_2fiat')
        takerFee2Fiat = self.safe_number(response, 'taker_fee_2fiat')
        makerFee2Deriv = self.safe_number(response, 'maker_fee_2deriv')
        takerFee2Deriv = self.safe_number(response, 'taker_fee_2deriv')
        for i in range(0, len(self.symbols)):
            symbol = self.symbols[i]
            market = self.market(symbol)
            fee = {
                'info': response,
                'symbol': symbol,
                'percentage': True,
                'tierBased': True,
            }
            if market['quote'] in fiat:
                fee['maker'] = makerFee2Fiat
                fee['taker'] = takerFee2Fiat
            elif market['contract']:
                fee['maker'] = makerFee2Deriv
                fee['taker'] = takerFee2Deriv
            else:
                fee['maker'] = makerFee
                fee['taker'] = takerFee
            result[symbol] = fee
        return result

    async def fetch_markets(self, params={}) -> List[Market]:
        """
        retrieves data on all markets for bitfinex
        :see: https://docs.bitfinex.com/v1/reference/rest-public-symbols
        :see: https://docs.bitfinex.com/v1/reference/rest-public-symbol-details
        :param dict [params]: extra parameters specific to the exchange API endpoint
        :returns dict[]: an array of objects representing market data
        """
        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
            type = 'spot'
            if id.find('F0') > -1:
                type = 'swap'
            result.append({
                'id': id,
                'symbol': symbol,
                'base': base,
                'quote': quote,
                'settle': None,
                'baseId': baseId,
                'quoteId': quoteId,
                'settleId': None,
                'type': type,
                'spot': (type == 'spot'),
                'margin': self.safe_value(market, 'margin'),
                'swap': (type == 'swap'),
                'future': False,
                'option': False,
                'active': True,
                'contract': (type == 'swap'),
                'linear': None,
                'inverse': None,
                'contractSize': None,
                'expiry': None,
                'expiryDatetime': None,
                'strike': None,
                'optionType': None,
                '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': int('8'),
                    'price': self.safe_integer(market, 'price_precision'),
                },
                'limits': {
                    'leverage': {
                        'min': None,
                        'max': None,
                    },
                    'amount': {
                        'min': self.safe_number(market, 'minimum_order_size'),
                        'max': self.safe_number(market, 'maximum_order_size'),
                    },
                    'price': {
                        'min': self.parse_number('1e-8'),
                        'max': None,
                    },
                    'cost': {
                        'min': None,
                        'max': None,
                    },
                },
                'created': None,
                '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.
        symbol = self.safe_symbol(symbol)
        return self.decimal_to_precision(amount, TRUNCATE, self.markets[symbol]['precision']['amount'], DECIMAL_PLACES)

    def price_to_precision(self, symbol, price):
        symbol = self.safe_symbol(symbol)
        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={}) -> Balances:
        """
        query for balance and get the amount of funds available for trading or funds locked in orders
        :see: https://docs.bitfinex.com/v1/reference/rest-auth-wallet-balances
        :param dict [params]: extra parameters specific to the exchange API endpoint
        :returns dict: a `balance structure <https://docs.ccxt.com/#/?id=balance-structure>`
        """
        await self.load_markets()
        accountsByType = self.safe_value(self.options, 'accountsByType', {})
        requestedType = self.safe_string(params, 'type', 'exchange')
        accountType = self.safe_string(accountsByType, requestedType, 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.safe_balance(result)

    async def transfer(self, code: str, amount: float, fromAccount: str, toAccount: str, params={}) -> TransferEntry:
        """
        transfer currency internally between wallets on the same account
        :see: https://docs.bitfinex.com/v1/reference/rest-auth-transfer-between-wallets
        :param str code: unified currency code
        :param float amount: amount to transfer
        :param str fromAccount: account to transfer from
        :param str toAccount: account to transfer to
        :param dict [params]: extra parameters specific to the exchange API endpoint
        :returns dict: a `transfer structure <https://docs.ccxt.com/#/?id=transfer-structure>`
        """
        # 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, fromAccount)
        toId = self.safe_string(accountsByType, toAccount, toAccount)
        currency = self.currency(code)
        fromCurrencyId = self.convert_derivatives_id(currency['id'], fromAccount)
        toCurrencyId = self.convert_derivatives_id(currency['id'], 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)
        message = self.safe_string(result, 'message')
        if message is None:
            raise ExchangeError(self.id + ' transfer failed')
        return self.extend(self.parse_transfer(result, currency), {
            'fromAccount': fromAccount,
            'toAccount': toAccount,
            'amount': self.parse_number(requestedAmount),
        })

    def parse_transfer(self, transfer: dict, currency: Currency = None) -> TransferEntry:
        #
        #     {
        #         "status": "success",
        #         "message": "0.0001 Bitcoin transfered from Margin to Exchange"
        #     }
        #
        return {
            'info': transfer,
            'id': None,
            'timestamp': None,
            'datetime': None,
            'currency': self.safe_currency_code(None, currency),
            'amount': None,
            'fromAccount': None,
            'toAccount': None,
            'status': self.parse_transfer_status(self.safe_string(transfer, 'status')),
        }

    def parse_transfer_status(self, status: Str) -> Str:
        statuses = {
            'SUCCESS': 'ok',
        }
        return self.safe_string(statuses, status, status)

    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: str, limit: Int = None, params={}) -> OrderBook:
        """
        fetches information on open orders with bid(buy) and ask(sell) prices, volumes and other data
        :see: https://docs.bitfinex.com/v1/reference/rest-public-orderbook
        :param str symbol: unified symbol of the market to fetch the order book for
        :param int [limit]: the maximum amount of order book entries to return
        :param dict [params]: extra parameters specific to the exchange API endpoint
        :returns dict: A dictionary of `order book structures <https://docs.ccxt.com/#/?id=order-book-structure>` indexed by market symbols
        """
        await self.load_markets()
        market = self.market(symbol)
        request = {
            'symbol': market['id'],
        }
        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, market['symbol'], None, 'bids', 'asks', 'price', 'amount')

    async def fetch_tickers(self, symbols: Strings = None, params={}) -> Tickers:
        """
        fetches price tickers for multiple markets, statistical information calculated over the past 24 hours for each market
        :param str[]|None symbols: unified symbols of the markets to fetch the ticker for, all market tickers are returned if not assigned
        :param dict [params]: extra parameters specific to the exchange API endpoint
        :returns dict: a dictionary of `ticker structures <https://docs.ccxt.com/#/?id=ticker-structure>`
        """
        await self.load_markets()
        symbols = self.market_symbols(symbols)
        response = await self.publicGetTickers(params)
        result = {}
        for i in range(0, len(response)):
            ticker = self.parse_ticker({'result': response[i]})
            symbol = ticker['symbol']
            result[symbol] = ticker
        return self.filter_by_array_tickers(result, 'symbol', symbols)

    async def fetch_ticker(self, symbol: str, params={}) -> Ticker:
        """
        fetches a price ticker, a statistical calculation with the information calculated over the past 24 hours for a specific market
        :see: https://docs.bitfinex.com/v1/reference/rest-public-ticker
        :param str symbol: unified symbol of the market to fetch the ticker for
        :param dict [params]: extra parameters specific to the exchange API endpoint
        :returns dict: a `ticker structure <https://docs.ccxt.com/#/?id=ticker-structure>`
        """
        await self.load_markets()
        market = self.market(symbol)
        request = {
            'symbol': market['id'],
        }
        ticker = await self.publicGetPubtickerSymbol(self.extend(request, params))
        #
        #    {
        #        mid: '63560.5',
        #        bid: '63560.0',
        #        ask: '63561.0',
        #        last_price: '63547.0',
        #        low: '62812.0',
        #        high: '64480.0',
        #        volume: '517.25634977',
        #        timestamp: '1715102384.9849467'
        #    }
        #
        return self.parse_ticker(ticker, market)

    def parse_ticker(self, ticker: dict, market: Market = None) -> Ticker:
        #
        #    {
        #        mid: '63560.5',
        #        bid: '63560.0',
        #        ask: '63561.0',
        #        last_price: '63547.0',
        #        low: '62812.0',
        #        high: '64480.0',
        #        volume: '517.25634977',
        #        timestamp: '1715102384.9849467'
        #    }
        #
        timestamp = self.safe_timestamp(ticker, 'timestamp')
        marketId = self.safe_string(ticker, 'pair')
        market = self.safe_market(marketId, market)
        symbol = market['symbol']
        last = self.safe_string(ticker, 'last_price')
        return self.safe_ticker({
            'symbol': symbol,
            'timestamp': timestamp,
            'datetime': self.iso8601(timestamp),
            'high': self.safe_string(ticker, 'high'),
            'low': self.safe_string(ticker, 'low'),
            'bid': self.safe_string(ticker, 'bid'),
            'bidVolume': None,
            'ask': self.safe_string(ticker, 'ask'),
            'askVolume': None,
            'vwap': None,
            'open': None,
            'close': last,
            'last': last,
            'previousClose': None,
            'change': None,
            'percentage': None,
            'average': self.safe_string(ticker, 'mid'),
            'baseVolume': self.safe_string(ticker, 'volume'),
            'quoteVolume': None,
            'info': ticker,
        }, market)

    def parse_trade(self, trade, market: Market = None) -> Trade:
        #
        # fetchTrades(public) v1
        #
        #     {
        #          "timestamp":1637258380,
        #          "tid":894452833,
        #          "price":"0.99941",
        #          "amount":"261.38",
        #          "exchange":"bitfinex",
        #          "type":"sell"
        #     }
        #
        # fetchMyTrades(private) v1
        #
        #     {
        #          "price":"0.99941",
        #          "amount":"261.38",
        #          "timestamp":"1637258380.0",
        #          "type":"Sell",
        #          "fee_currency":"UST",
        #          "fee_amount":"-0.52245157",
        #          "tid":894452833,
        #          "order_id":78819731373
        #     }
        #
        #     {
        #         "price":"0.99958",
        #         "amount":"261.90514",
        #         "timestamp":"1637258238.0",
        #         "type":"Buy",
        #         "fee_currency":"UDC",
        #         "fee_amount":"-0.52381028",
        #         "tid":894452800,
        #         "order_id":78819504838
        #     }
        #
        id = self.safe_string(trade, 'tid')
        timestamp = self.safe_timestamp(trade, 'timestamp')
        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')
        fee = None
        if 'fee_amount' in trade:
            feeCostString = Precise.string_neg(self.safe_string(trade, 'fee_amount'))
            feeCurrencyId = self.safe_string(trade, 'fee_currency')
            feeCurrencyCode = self.safe_currency_code(feeCurrencyId)
            fee = {
                'cost': feeCostString,
                'currency': feeCurrencyCode,
            }
        return self.safe_trade({
            'id': id,
            'info': trade,
            'timestamp': timestamp,
            'datetime': self.iso8601(timestamp),
            'symbol': market['symbol'],
            'type': type,
            'order': orderId,
            'side': side,
            'takerOrMaker': None,
            'price': priceString,
            'amount': amountString,
            'cost': None,
            'fee': fee,
        }, market)

    async def fetch_trades(self, symbol: str, since: Int = None, limit: Int = 50, params={}) -> List[Trade]:
        """
        get the list of most recent trades for a particular symbol
        :see: https://docs.bitfinex.com/v1/reference/rest-public-trades
        :param str symbol: unified symbol of the market to fetch trades for
        :param int [since]: timestamp in ms of the earliest trade to fetch
        :param int [limit]: the maximum amount of trades to fetch
        :param dict [params]: extra parameters specific to the exchange API endpoint
        :returns Trade[]: a list of `trade structures <https://docs.ccxt.com/#/?id=public-trades>`
        """
        await self.load_markets()
        market = self.market(symbol)
        request = {
            'symbol': market['id'],
            'limit_trades': limit,
        }
        if since is not None:
            request['timestamp'] = self.parse_to_int(since / 1000)
        response = await self.publicGetTradesSymbol(self.extend(request, params))
        #
        #    [
        #        {
        #            "timestamp": "1694284565",
        #            "tid": "1415415034",
        #            "price": "25862.0",
        #            "amount": "0.00020685",
        #            "exchange": "bitfinex",
        #            "type": "buy"
        #        },
        #    ]
        #
        return self.parse_trades(response, market, since, limit)

    async def fetch_my_trades(self, symbol: Str = None, since: Int = None, limit: Int = None, params={}):
        """
        fetch all trades made by the user
        :see: https://docs.bitfinex.com/v1/reference/rest-auth-past-trades
        :param str symbol: unified market symbol
        :param int [since]: the earliest time in ms to fetch trades for
        :param int [limit]: the maximum number of trades structures to retrieve
        :param dict [params]: extra parameters specific to the exchange API endpoint
        :returns Trade[]: a list of `trade structures <https://docs.ccxt.com/#/?id=trade-structure>`
        """
        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'] = self.parse_to_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: str, type: OrderType, side: OrderSide, amount: float, price: Num = None, params={}):
        """
        create a trade order
        :see: https://docs.bitfinex.com/v1/reference/rest-auth-new-order
        :param str symbol: unified symbol of the market to create an order in
        :param str type: 'market' or 'limit'
        :param str side: 'buy' or 'sell'
        :param float amount: how much of currency you want to trade in units of base currency
        :param float [price]: the price at which the order is to be fullfilled, in units of the quote currency, ignored in market orders
        :param dict [params]: extra parameters specific to the exchange API endpoint
        :returns dict: an `order structure <https://docs.ccxt.com/#/?id=order-structure>`
        """
        await self.load_markets()
        market = self.market(symbol)
        postOnly = self.safe_bool(params, 'postOnly', False)
        type = type.lower()
        params = self.omit(params, ['postOnly'])
        if market['spot']:
            # although they claim that type needs to be 'exchange limit' or 'exchange market'
            # in fact that's not the case for swap markets
            type = self.safe_string_lower(self.options['orderTypes'], type, type)
        request = {
            'symbol': market['id'],
            'side': side,
            'amount': self.amount_to_precision(symbol, amount),
            'type': type,
            'ocoorder': False,
            'buy_price_oco': 0,
            'sell_price_oco': 0,
        }
        if type.find('market') > -1:
            request['price'] = str(self.nonce())
        else:
            request['price'] = self.price_to_precision(symbol, price)
        if postOnly:
            request['is_postonly'] = True
        response = await self.privatePostOrderNew(self.extend(request, params))
        return self.parse_order(response, market)

    async def edit_order(self, id: str, symbol: str, type: OrderType, side: OrderSide, amount: Num = None, price: Num = 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: str, symbol: Str = None, params={}):
        """
        cancels an open order
        :see: https://docs.bitfinex.com/v1/reference/rest-auth-cancel-order
        :param str id: order id
        :param str symbol: not used by bitfinex cancelOrder()
        :param dict [params]: extra parameters specific to the exchange API endpoint
        :returns dict: An `order structure <https://docs.ccxt.com/#/?id=order-structure>`
        """
        await self.load_markets()
        request = {
            'order_id': int(id),
        }
        return await self.privatePostOrderCancel(self.extend(request, params))

    async def cancel_all_orders(self, symbol: Str = None, params={}):
        """
        cancel all open orders
        :see: https://docs.bitfinex.com/v1/reference/rest-auth-cancel-all-orders
        :param str symbol: unified market symbol, only orders in the market of self symbol are cancelled when symbol is not None
        :param dict [params]: extra parameters specific to the exchange API endpoint
        :returns dict: response from exchange
        """
        return await self.privatePostOrderCancelAll(params)

    def parse_order(self, order, market: Market = None) -> Order:
        #
        #     {
        #           "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_string(order, 'price'),
            'stopPrice': None,
            'triggerPrice': None,
            'average': self.safe_string(order, 'avg_execution_price'),
            'amount': self.safe_string(order, 'original_amount'),
            'remaining': self.safe_string(order, 'remaining_amount'),
            'filled': self.safe_string(order, 'executed_amount'),
            'status': status,
            'fee': None,
            'cost': None,
            'trades': None,
        }, market)

    async def fetch_open_orders(self, symbol: Str = None, since: Int = None, limit: Int = None, params={}) -> List[Order]:
        """
        fetch all unfilled currently open orders
        :see: https://docs.bitfinex.com/v1/reference/rest-auth-active-orders
        :param str symbol: unified market symbol
        :param int [since]: the earliest time in ms to fetch open orders for
        :param int [limit]: the maximum number of  open orders structures to retrieve
        :param dict [params]: extra parameters specific to the exchange API endpoint
        :returns Order[]: a list of `order structures <https://docs.ccxt.com/#/?id=order-structure>`
        """
        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: Str = None, since: Int = None, limit: Int = None, params={}) -> List[Order]:
        """
        fetches information on multiple closed orders made by the user
        :see: https://docs.bitfinex.com/v1/reference/rest-auth-orders-history
        :param str symbol: unified market symbol of the market orders were made in
        :param int [since]: the earliest time in ms to fetch orders for
        :param int [limit]: the maximum number of order structures to retrieve
        :param dict [params]: extra parameters specific to the exchange API endpoint
        :returns Order[]: a list of `order structures <https://docs.ccxt.com/#/?id=order-structure>`
        """
        await self.load_markets()
        symbol = self.symbol(symbol)
        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: str, symbol: Str = None, params={}):
        """
        fetches information on an order made by the user
        :see: https://docs.bitfinex.com/v1/reference/rest-auth-order-status
        :param str symbol: not used by bitfinex fetchOrder
        :param dict [params]: extra parameters specific to the exchange API endpoint
        :returns dict: An `order structure <https://docs.ccxt.com/#/?id=order-structure>`
        """
        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: Market = None) -> list:
        #
        #     [
        #         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: str, timeframe='1m', since: Int = None, limit: Int = None, params={}) -> List[list]:
        """
        fetches historical candlestick data containing the open, high, low, and close price, and the volume of a market
        :see: https://docs.bitfinex.com/reference/rest-public-candles#aggregate-funding-currency-candles
        :param str symbol: unified symbol of the market to fetch OHLCV data for
        :param str timeframe: the length of time each candle represents
        :param int [since]: timestamp in ms of the earliest candle to fetch
        :param int [limit]: the maximum amount of candles to fetch
        :param dict [params]: extra parameters specific to the exchange API endpoint
        :returns int[][]: A list of candles ordered, open, high, low, close, volume
        """
        await self.load_markets()
        if limit is None:
            limit = 100
        else:
            limit = min(limit, 10000)
        market = self.market(symbol)
        v2id = 't' + market['id']
        request = {
            'symbol': v2id,
            'timeframe': self.safe_string(self.timeframes, timeframe, 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: str, params={}):
        """
        create a currency deposit address
        :see: https://docs.bitfinex.com/v1/reference/rest-auth-deposit
        :param str code: unified currency code of the currency for the deposit address
        :param dict [params]: extra parameters specific to the exchange API endpoint
        :returns dict: an `address structure <https://docs.ccxt.com/#/?id=address-structure>`
        """
        await self.load_markets()
        request = {
            'renew': 1,
        }
        return await self.fetch_deposit_address(code, self.extend(request, params))

    async def fetch_deposit_address(self, code: str, params={}):
        """
        fetch the deposit address for a currency associated with self account
        :see: https://docs.bitfinex.com/v1/reference/rest-auth-deposit
        :param str code: unified currency code
        :param dict [params]: extra parameters specific to the exchange API endpoint
        :returns dict: an `address structure <https://docs.ccxt.com/#/?id=address-structure>`
        """
        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,
            'network': None,
            'info': response,
        }

    async def fetch_deposits_withdrawals(self, code: Str = None, since: Int = None, limit: Int = None, params={}) -> List[Transaction]:
        """
        fetch history of deposits and withdrawals
        :see: https://docs.bitfinex.com/v1/reference/rest-auth-deposit-withdrawal-history
        :param str code: unified currency code for the currency of the deposit/withdrawals
        :param int [since]: timestamp in ms of the earliest deposit/withdrawal, default is None
        :param int [limit]: max number of deposit/withdrawals to return, default is None
        :param dict [params]: extra parameters specific to the exchange API endpoint
        :returns dict: a list of `transaction structure <https://docs.ccxt.com/#/?id=transaction-structure>`
        """
        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 + ' fetchDepositsWithdrawals() 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'] = self.parse_to_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: Currency = None) -> Transaction:
        #
        # 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"
        #     }
        #
        # withdraw
        #
        #     {
        #         "status": "success",
        #         "message": "Your withdrawal request has been successfully submitted.",
        #         "withdrawal_id": 586829
        #     }
        #
        timestamp = self.safe_timestamp(transaction, 'timestamp_created')
        currencyId = self.safe_string(transaction, 'currency')
        code = self.safe_currency_code(currencyId, currency)
        feeCost = self.safe_string(transaction, 'fee')
        if feeCost is not None:
            feeCost = Precise.string_abs(feeCost)
        return {
            'info': transaction,
            'id': self.safe_string_2(transaction, 'id', 'withdrawal_id'),
            'txid': self.safe_string(transaction, 'txid'),
            'type': self.safe_string_lower(transaction, 'type'),  # DEPOSIT or WITHDRAWAL,
            'currency': code,
            'network': None,
            'amount': self.safe_number(transaction, 'amount'),
            'status': self.parse_transaction_status(self.safe_string(transaction, 'status')),
            '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)
            'addressFrom': None,
            'addressTo': None,
            'tag': self.safe_string(transaction, 'description'),
            'tagFrom': None,
            'tagTo': None,
            'updated': self.safe_timestamp(transaction, 'timestamp'),
            'comment': None,
            'internal': None,
            'fee': {
                'currency': code,
                'cost': self.parse_number(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: str, amount: float, address: str, tag=None, params={}):
        """
        make a withdrawal
        :see: https://docs.bitfinex.com/v1/reference/rest-auth-withdrawal
        :param str code: unified currency code
        :param float amount: the amount to withdraw
        :param str address: the address to withdraw to
        :param str tag:
        :param dict [params]: extra parameters specific to the exchange API endpoint
        :returns dict: a `transaction structure <https://docs.ccxt.com/#/?id=transaction-structure>`
        """
        tag, params = self.handle_withdraw_tag_and_params(tag, 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)
        currency = self.currency(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))
        #
        #     [
        #         {
        #             "status":"success",
        #             "message":"Your withdrawal request has been successfully submitted.",
        #             "withdrawal_id":586829
        #         }
        #     ]
        #
        response = self.safe_value(responses, 0, {})
        id = self.safe_number(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 self.parse_transaction(response, currency)

    async def fetch_positions(self, symbols: Strings = None, params={}):
        """
        fetch all open positions
        :see: https://docs.bitfinex.com/v1/reference/rest-auth-active-positions
        :param str[]|None symbols: list of unified market symbols
        :param dict [params]: extra parameters specific to the exchange API endpoint
        :returns dict[]: a list of `position structure <https://docs.ccxt.com/#/?id=position-structure>`
        """
        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(self.encode(payload), secret, hashlib.sha384)
            headers = {
                'X-BFX-APIKEY': self.apiKey,
                'X-BFX-PAYLOAD': 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 None
        throwError = False
        if code >= 400:
            if body[0] == '{':
                throwError = True
        else:
            # json response with error, i.e:
            # [{"status":"error","message":"Momentary balance check. Please wait few seconds and try the transfer again."}]
            responseObject = self.safe_value(response, 0, {})
            status = self.safe_string(responseObject, 'status', '')
            if status == 'error':
                throwError = True
        if throwError:
            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
        return None
