# -*- 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
from ccxt.abstract.bitfinex1 import ImplicitAPI
import hashlib
from ccxt.base.types import Any, Balances, Currency, DepositAddress, 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 bitfinex1(Exchange, ImplicitAPI):

    def describe(self) -> Any:
        return self.deep_extend(super(bitfinex1, self).describe(), {
            'id': 'bitfinex1',
            '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,
                'fetchDepositAddresses': False,
                'fetchDepositAddressesByNetwork': False,
                'fetchDeposits': False,
                'fetchDepositsWithdrawals': True,
                'fetchDepositWithdrawFee': 'emulated',
                'fetchDepositWithdrawFees': True,
                'fetchFundingHistory': False,
                'fetchFundingRate': False,  # Endpoint 'lendbook/{currency}' is related to interest rates on spot margin lending
                'fetchFundingRateHistory': False,
                'fetchFundingRates': False,
                '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://github.com/user-attachments/assets/9147c6c5-7197-481e-827b-7483672bb0e9',
                '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: Strings = None, params={}):
        """
 @deprecated
        please use fetchDepositWithdrawFees instead

        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>`
        """
        self.load_markets()
        result: dict = {}
        response = self.privatePostAccountFees(params)
        #
        # {
        #     "withdraw": {
        #         "BTC": "0.0004",
        #     }
        # }
        #
        fees = self.safe_dict(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_deposit_withdraw_fees(self, codes: Strings = None, params={}):
        """
        fetch deposit and withdraw fees

        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>`
        """
        self.load_markets()
        response = 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,
        }

    def fetch_trading_fees(self, params={}) -> TradingFees:
        """
        fetch the trading fees for multiple markets

        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
        """
        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: dict = {}
        fiat = self.safe_dict(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={}) -> List[Market]:
        """
        retrieves data on all markets for bitfinex

        https://docs.bitfinex.com/v1/reference/rest-public-symbols
        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
        """
        idsPromise = self.publicGetSymbols()
        #
        #     ["btcusd", "ltcusd", "ltcbtc"]
        #
        detailsPromise = 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
        #         },
        #     ]
        #
        ids, details = [idsPromise, detailsPromise]
        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_bool(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)

    def fetch_balance(self, params={}) -> Balances:
        """
        query for balance and get the amount of funds available for trading or funds locked in orders

        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>`
        """
        self.load_markets()
        accountsByType = self.safe_dict(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: dict = {'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: str, amount: float, fromAccount: str, toAccount: str, params={}) -> TransferEntry:
        """
        transfer currency internally between wallets on the same account

        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)
        self.load_markets()
        accountsByType = self.safe_dict(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: dict = {
            '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: 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: dict = {
            '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: str, limit: Int = None, params={}) -> OrderBook:
        """
        fetches information on open orders with bid(buy) and ask(sell) prices, volumes and other data

        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
        """
        self.load_markets()
        market = self.market(symbol)
        request: dict = {
            '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: Strings = None, params={}) -> Tickers:
        """
        fetches price tickers for multiple markets, statistical information calculated over the past 24 hours for each market
        :param str[] [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>`
        """
        self.load_markets()
        symbols = self.market_symbols(symbols)
        response = self.publicGetTickers(params)
        result: dict = {}
        for i in range(0, len(response)):
            ticker = self.parse_ticker(response[i])
            symbol = ticker['symbol']
            result[symbol] = ticker
        return self.filter_by_array_tickers(result, 'symbol', symbols)

    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

        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>`
        """
        self.load_markets()
        market = self.market(symbol)
        request: dict = {
            'symbol': market['id'],
        }
        ticker = 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: dict, 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)

    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

        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>`
        """
        self.load_markets()
        market = self.market(symbol)
        request: dict = {
            'symbol': market['id'],
            'limit_trades': limit,
        }
        if since is not None:
            request['timestamp'] = self.parse_to_int(since / 1000)
        response = 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)

    def fetch_my_trades(self, symbol: Str = None, since: Int = None, limit: Int = None, params={}):
        """
        fetch all trades made by the user

        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')
        self.load_markets()
        market = self.market(symbol)
        request: dict = {
            '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 = self.privatePostMytrades(self.extend(request, params))
        return self.parse_trades(response, market, since, limit)

    def create_order(self, symbol: str, type: OrderType, side: OrderSide, amount: float, price: Num = None, params={}):
        """
        create a trade order

        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 fulfilled, 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>`
        """
        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: dict = {
            '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 = self.privatePostOrderNew(self.extend(request, params))
        return self.parse_order(response, market)

    def edit_order(self, id: str, symbol: str, type: OrderType, side: OrderSide, amount: Num = None, price: Num = None, params={}):
        self.load_markets()
        order: dict = {
            '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: str, symbol: Str = None, params={}):
        """
        cancels an open order

        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>`
        """
        self.load_markets()
        request: dict = {
            'order_id': int(id),
        }
        response = self.privatePostOrderCancel(self.extend(request, params))
        #
        #    {
        #        id: '161236928925',
        #        cid: '1720172026812',
        #        cid_date: '2024-07-05',
        #        gid: null,
        #        symbol: 'adaust',
        #        exchange: 'bitfinex',
        #        price: '0.33',
        #        avg_execution_price: '0.0',
        #        side: 'buy',
        #        type: 'exchange limit',
        #        timestamp: '1720172026.813',
        #        is_live: True,
        #        is_cancelled: False,
        #        is_hidden: False,
        #        oco_order: null,
        #        was_forced: False,
        #        original_amount: '10.0',
        #        remaining_amount: '10.0',
        #        executed_amount: '0.0',
        #        src: 'api',
        #        meta: {}
        #    }
        #
        return self.parse_order(response)

    def cancel_all_orders(self, symbol: Str = None, params={}):
        """
        cancel all open orders

        https://docs.bitfinex.com/v1/reference/rest-auth-cancel-all-orders

        :param str symbol: not used by bitfinex cancelAllOrders
        :param dict [params]: extra parameters specific to the exchange API endpoint
        :returns dict: response from exchange
        """
        response = self.privatePostOrderCancelAll(params)
        #
        #    {result: 'Submitting 1 order cancellations.'}
        #
        return [
            self.safe_order({
                'info': response,
            }),
        ]

    def parse_order(self, order: dict, 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_bool(order, 'is_live')
        canceled = self.safe_bool(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'),
            '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)

    def fetch_open_orders(self, symbol: Str = None, since: Int = None, limit: Int = None, params={}) -> List[Order]:
        """
        fetch all unfilled currently open orders

        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>`
        """
        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: Str = None, since: Int = None, limit: Int = None, params={}) -> List[Order]:
        """
        fetches information on multiple closed orders made by the user

        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>`
        """
        self.load_markets()
        symbol = self.symbol(symbol)
        request: dict = {}
        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: str, symbol: Str = None, params={}):
        """
        fetches information on an order made by the user

        https://docs.bitfinex.com/v1/reference/rest-auth-order-status

        :param str id: the order id
        :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>`
        """
        self.load_markets()
        request: dict = {
            'order_id': int(id),
        }
        response = 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),
        ]

    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

        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
        :param int [params.until]: timestamp in ms of the latest candle to fetch
        :returns int[][]: A list of candles ordered, open, high, low, close, volume
        """
        self.load_markets()
        if limit is None:
            limit = 100
        else:
            limit = min(limit, 10000)
        market = self.market(symbol)
        v2id = 't' + market['id']
        request: dict = {
            'symbol': v2id,
            'timeframe': self.safe_string(self.timeframes, timeframe, timeframe),
            'sort': 1,
            'limit': limit,
        }
        until = self.safe_integer(params, 'until')
        if since is not None:
            request['start'] = since
        elif until is not None:
            duration = self.parse_timeframe(timeframe)
            request['start'] = until - ((limit - 1) * duration * 1000)
        if until is not None:
            request['end'] = until
        params = self.omit(params, 'until')
        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: str, params={}) -> DepositAddress:
        """
        create a currency deposit address

        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>`
        """
        self.load_markets()
        request: dict = {
            'renew': 1,
        }
        return self.fetch_deposit_address(code, self.extend(request, params))

    def fetch_deposit_address(self, code: str, params={}) -> DepositAddress:
        """
        fetch the deposit address for a currency associated with self account

        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>`
        """
        self.load_markets()
        # todo rewrite for https://api-pub.bitfinex.com//v2/conf/pub:map:tx:method
        name = self.get_currency_name(code)
        request: dict = {
            '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_deposits_withdrawals(self, code: Str = None, since: Int = None, limit: Int = None, params={}) -> List[Transaction]:
        """
        fetch history of deposits and withdrawals

        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>`
        """
        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 = 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: dict, 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: Str):
        statuses: dict = {
            '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: str, amount: float, address: str, tag=None, params={}) -> Transaction:
        """
        make a withdrawal

        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)
        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: dict = {
            '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_dict(responses, 0, {})
        id = self.safe_integer(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: Strings = None, params={}):
        """
        fetch all open positions

        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>`
        """
        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.microseconds()

    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: int, reason: str, url: str, method: str, headers: dict, body: str, 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_dict(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
