# -*- 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.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',
            # 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': None,
                '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': True,
                'fetchWithdrawals': None,
                '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',
                },
            },
        })

    def fetch_transaction_fees(self, codes=None, params={}):
        """
        fetch transaction 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 bitfinex api endpoint
        :returns [dict]: a list of `fees structures <https://docs.ccxt.com/en/latest/manual.html#fee-structure>`
        """
        self.load_markets()
        result = {}
        response = 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

    def fetch_trading_fees(self, params={}):
        """
        fetch the trading fees for multiple markets
        :param dict params: extra parameters specific to the bitfinex api endpoint
        :returns dict: a dictionary of `fee structures <https://docs.ccxt.com/en/latest/manual.html#fee-structure>` indexed by market symbols
        """
        self.load_markets()
        response = 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

    def fetch_markets(self, params={}):
        """
        retrieves data on all markets for bitfinex
        :param dict params: extra parameters specific to the exchange api endpoint
        :returns [dict]: an array of objects representing market data
        """
        ids = self.publicGetSymbols()
        #
        #     ["btcusd", "ltcusd", "ltcbtc"]
        #
        details = 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)
            result.append({
                'id': id,
                'symbol': base + '/' + quote,
                'base': base,
                'quote': quote,
                'settle': None,
                'baseId': baseId,
                'quoteId': quoteId,
                'settleId': None,
                'type': 'spot',
                'spot': True,
                'margin': self.safe_value(market, 'margin'),
                'swap': False,
                'future': False,
                'option': False,
                'active': True,
                'contract': False,
                '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,
                    },
                },
                '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)

    def fetch_balance(self, params={}):
        """
        query for balance and get the amount of funds available for trading or funds locked in orders
        :param dict params: extra parameters specific to the bitfinex api endpoint
        :returns dict: a `balance structure <https://docs.ccxt.com/en/latest/manual.html?#balance-structure>`
        """
        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 = 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)

    def transfer(self, code, amount, fromAccount, toAccount, params={}):
        """
        transfer currency internally between wallets on the same account
        :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 bitfinex api endpoint
        :returns dict: a `transfer structure <https://docs.ccxt.com/en/latest/manual.html#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)
        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 = 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, currency=None):
        #
        #     {
        #         status: 'success',
        #         message: '0.0001 Bitcoin transfered from Margin to Exchange'
        #     }
        #
        timestamp = self.milliseconds()
        return {
            'info': transfer,
            'id': None,
            'timestamp': timestamp,
            'datetime': self.iso8601(timestamp),
            '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):
        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

    def fetch_order_book(self, symbol, limit=None, params={}):
        """
        fetches information on open orders with bid(buy) and ask(sell) prices, volumes and other data
        :param str symbol: unified symbol of the market to fetch the order book for
        :param int|None limit: the maximum amount of order book entries to return
        :param dict params: extra parameters specific to the bitfinex api endpoint
        :returns dict: A dictionary of `order book structures <https://docs.ccxt.com/en/latest/manual.html#order-book-structure>` indexed by market symbols
        """
        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 = self.publicGetBookSymbol(self.extend(request, params))
        return self.parse_order_book(response, market['symbol'], None, 'bids', 'asks', 'price', 'amount')

    def fetch_tickers(self, symbols=None, params={}):
        """
        fetches price tickers for multiple markets, statistical calculations with the information calculated over the past 24 hours 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 bitfinex api endpoint
        :returns dict: an array of `ticker structures <https://docs.ccxt.com/en/latest/manual.html#ticker-structure>`
        """
        self.load_markets()
        symbols = self.market_symbols(symbols)
        response = 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)

    def fetch_ticker(self, symbol, params={}):
        """
        fetches a price ticker, a statistical calculation with the information calculated over the past 24 hours for a specific market
        :param str symbol: unified symbol of the market to fetch the ticker for
        :param dict params: extra parameters specific to the bitfinex api endpoint
        :returns dict: a `ticker structure <https://docs.ccxt.com/en/latest/manual.html#ticker-structure>`
        """
        self.load_markets()
        market = self.market(symbol)
        request = {
            'symbol': market['id'],
        }
        ticker = self.publicGetPubtickerSymbol(self.extend(request, params))
        return self.parse_ticker(ticker, market)

    def parse_ticker(self, ticker, market=None):
        timestamp = self.safe_timestamp(ticker, '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_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=None):
        #
        # fetchTrades(public) v1
        #
        #     {
        #          "timestamp":1637258380,
        #          "tid":894452833,
        #          "price":"0.99941",
        #          "amount":"261.38",
        #          "exchange":"bitfinex",
        #          "type":"sell"
        #     }
        #
        #     {   "timestamp":1637258238,
        #          "tid":894452800,
        #          "price":"0.99958",
        #          "amount":"261.90514",
        #          "exchange":"bitfinex",
        #          "type":"buy"
        #     }
        #
        # 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)

    def fetch_trades(self, symbol, since=None, limit=50, params={}):
        """
        get the list of most recent trades for a particular symbol
        :param str symbol: unified symbol of the market to fetch trades for
        :param int|None since: timestamp in ms of the earliest trade to fetch
        :param int|None limit: the maximum amount of trades to fetch
        :param dict params: extra parameters specific to the bitfinex api endpoint
        :returns [dict]: a list of `trade structures <https://docs.ccxt.com/en/latest/manual.html?#public-trades>`
        """
        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 = self.publicGetTradesSymbol(self.extend(request, params))
        return self.parse_trades(response, market, since, limit)

    def fetch_my_trades(self, symbol=None, since=None, limit=None, params={}):
        """
        fetch all trades made by the user
        :param str symbol: unified market symbol
        :param int|None since: the earliest time in ms to fetch trades for
        :param int|None limit: the maximum number of trades structures to retrieve
        :param dict params: extra parameters specific to the bitfinex api endpoint
        :returns [dict]: a list of `trade structures <https://docs.ccxt.com/en/latest/manual.html#trade-structure>`
        """
        if symbol is None:
            raise ArgumentsRequired(self.id + ' fetchMyTrades() requires a `symbol` argument')
        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 = self.privatePostMytrades(self.extend(request, params))
        return self.parse_trades(response, market, since, limit)

    def create_order(self, symbol, type, side, amount, price=None, params={}):
        """
        create a trade 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|None 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 bitfinex api endpoint
        :returns dict: an `order structure <https://docs.ccxt.com/en/latest/manual.html#order-structure>`
        """
        self.load_markets()
        market = self.market(symbol)
        postOnly = self.safe_value(params, 'postOnly', False)
        params = self.omit(params, ['postOnly'])
        request = {
            'symbol': market['id'],
            '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)
        if postOnly:
            request['is_postonly'] = True
        response = self.privatePostOrderNew(self.extend(request, params))
        return self.parse_order(response, market)

    def edit_order(self, id, symbol, type, side, amount=None, price=None, params={}):
        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 = self.privatePostOrderCancelReplace(self.extend(order, params))
        return self.parse_order(response)

    def cancel_order(self, id, symbol=None, params={}):
        """
        cancels an open order
        :param str id: order id
        :param str|None symbol: not used by bitfinex cancelOrder()
        :param dict params: extra parameters specific to the bitfinex api endpoint
        :returns dict: An `order structure <https://docs.ccxt.com/en/latest/manual.html#order-structure>`
        """
        self.load_markets()
        request = {
            'order_id': int(id),
        }
        return self.privatePostOrderCancel(self.extend(request, params))

    def cancel_all_orders(self, symbol=None, params={}):
        """
        cancel all open orders
        :param str|None 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 bitfinex api endpoint
        :returns dict: response from exchange
        """
        return 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_string(order, 'price'),
            'stopPrice': 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)

    def fetch_open_orders(self, symbol=None, since=None, limit=None, params={}):
        """
        fetch all unfilled currently open orders
        :param str|None symbol: unified market symbol
        :param int|None since: the earliest time in ms to fetch open orders for
        :param int|None limit: the maximum number of  open orders structures to retrieve
        :param dict params: extra parameters specific to the bitfinex api endpoint
        :returns [dict]: a list of `order structures <https://docs.ccxt.com/en/latest/manual.html#order-structure>`
        """
        self.load_markets()
        if symbol is not None:
            if not (symbol in self.markets):
                raise ExchangeError(self.id + ' has no symbol ' + symbol)
        response = 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

    def fetch_closed_orders(self, symbol=None, since=None, limit=None, params={}):
        """
        fetches information on multiple closed orders made by the user
        :param str|None symbol: unified market symbol of the market orders were made in
        :param int|None since: the earliest time in ms to fetch orders for
        :param int|None limit: the maximum number of  orde structures to retrieve
        :param dict params: extra parameters specific to the bitfinex api endpoint
        :returns [dict]: a list of `order structures <https://docs.ccxt.com/en/latest/manual.html#order-structure>`
        """
        self.load_markets()
        symbol = self.symbol(symbol)
        request = {}
        if limit is not None:
            request['limit'] = limit
        response = 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

    def fetch_order(self, id, symbol=None, params={}):
        """
        fetches information on an order made by the user
        :param str|None symbol: not used by bitfinex fetchOrder
        :param dict params: extra parameters specific to the bitfinex api endpoint
        :returns dict: An `order structure <https://docs.ccxt.com/en/latest/manual.html#order-structure>`
        """
        self.load_markets()
        request = {
            'order_id': int(id),
        }
        response = 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),
        ]

    def fetch_ohlcv(self, symbol, timeframe='1m', since=None, limit=None, params={}):
        """
        fetches historical candlestick data containing the open, high, low, and close price, and the volume of a market
        :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|None since: timestamp in ms of the earliest candle to fetch
        :param int|None limit: the maximum amount of candles to fetch
        :param dict params: extra parameters specific to the bitfinex api endpoint
        :returns [[int]]: A list of candles ordered as timestamp, open, high, low, close, volume
        """
        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 = 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')

    def create_deposit_address(self, code, params={}):
        """
        create a currency deposit address
        :param str code: unified currency code of the currency for the deposit address
        :param dict params: extra parameters specific to the bitfinex api endpoint
        :returns dict: an `address structure <https://docs.ccxt.com/en/latest/manual.html#address-structure>`
        """
        self.load_markets()
        request = {
            'renew': 1,
        }
        return self.fetch_deposit_address(code, self.extend(request, params))

    def fetch_deposit_address(self, code, params={}):
        """
        fetch the deposit address for a currency associated with self account
        :param str code: unified currency code
        :param dict params: extra parameters specific to the bitfinex api endpoint
        :returns dict: an `address structure <https://docs.ccxt.com/en/latest/manual.html#address-structure>`
        """
        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 = 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,
        }

    def fetch_transactions(self, code=None, since=None, limit=None, params={}):
        """
        fetch history of deposits and withdrawals
        :param str|None code: unified currency code for the currency of the transactions, default is None
        :param int|None since: timestamp in ms of the earliest transaction, default is None
        :param int|None limit: max number of transactions to return, default is None
        :param dict params: extra parameters specific to the bitfinex api endpoint
        :returns dict: a list of `transaction structure <https://docs.ccxt.com/en/latest/manual.html#transaction-structure>`
        """
        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 = 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"
        #     }
        #
        # withdraw
        #
        #     {
        #         "status": "success",
        #         "message": "Your withdrawal request has been successfully submitted.",
        #         "withdrawal_id": 586829
        #     }
        #
        timestamp = self.safe_timestamp(transaction, 'timestamp_created')
        updated = self.safe_timestamp(transaction, 'timestamp')
        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_string(transaction, 'fee')
        if feeCost is not None:
            feeCost = Precise.string_abs(feeCost)
        tag = self.safe_string(transaction, 'description')
        return {
            'info': transaction,
            'id': self.safe_string_2(transaction, 'id', 'withdrawal_id'),
            'txid': self.safe_string(transaction, 'txid'),
            'timestamp': timestamp,
            'datetime': self.iso8601(timestamp),
            'network': None,
            'address': self.safe_string(transaction, 'address'),  # todo: self is actually the tag for XRP transfers(the address is missing)
            'addressTo': None,
            'addressFrom': None,
            'tag': tag,
            'tagTo': None,
            'tagFrom': None,
            'type': type,
            'amount': self.safe_number(transaction, 'amount'),
            'currency': code,
            'status': status,
            'updated': updated,
            '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)

    def withdraw(self, code, amount, address, tag=None, params={}):
        """
        make a withdrawal
        :param str code: unified currency code
        :param float amount: the amount to withdraw
        :param str address: the address to withdraw to
        :param str|None tag:
        :param dict params: extra parameters specific to the bitfinex api endpoint
        :returns dict: a `transaction structure <https://docs.ccxt.com/en/latest/manual.html#transaction-structure>`
        """
        tag, params = self.handle_withdraw_tag_and_params(tag, params)
        self.check_address(address)
        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 = 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)

    def fetch_positions(self, symbols=None, params={}):
        """
        fetch all open positions
        :param [str]|None symbols: list of unified market symbols
        :param dict params: extra parameters specific to the bitfinex api endpoint
        :returns [dict]: a list of `position structure <https://docs.ccxt.com/en/latest/manual.html#position-structure>`
        """
        self.load_markets()
        response = 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
        throwError = False
        if code >= 400:
            firstChar = self.safe_string(body, 0)
            if firstChar == '{':
                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
