# -*- 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
import math
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


class bitfinex(Exchange):

    def describe(self):
        return self.deep_extend(super(bitfinex, self).describe(), {
            'id': 'bitfinex',
            'name': 'Bitfinex',
            'countries': ['VG'],
            'version': 'v1',
            'rateLimit': 1500,
            'certified': True,
            'pro': True,
            # new metainfo interface
            'has': {
                'cancelAllOrders': True,
                'cancelOrder': True,
                'CORS': False,
                'createDepositAddress': True,
                'createOrder': True,
                'deposit': True,
                'editOrder': True,
                'fetchBalance': True,
                'fetchClosedOrders': True,
                'fetchDepositAddress': True,
                'fetchDeposits': False,
                'fetchFundingFees': True,
                'fetchMarkets': True,
                'fetchMyTrades': True,
                'fetchOHLCV': True,
                'fetchOpenOrders': True,
                'fetchOrder': True,
                'fetchOrderBook': True,
                'fetchTicker': True,
                'fetchTickers': True,
                'fetchTrades': True,
                'fetchTradingFee': True,
                'fetchTradingFees': True,
                'fetchTransactions': True,
                'fetchWithdrawals': False,
                'withdraw': True,
            },
            'timeframes': {
                '1m': '1m',
                '5m': '5m',
                '15m': '15m',
                '30m': '30m',
                '1h': '1h',
                '3h': '3h',
                '4h': '4h',
                '6h': '6h',
                '12h': '12h',
                '1d': '1D',
                '1w': '7D',
                '2w': '14D',
                '1M': '1M',
            },
            'urls': {
                'logo': 'https://user-images.githubusercontent.com/1294454/27766244-e328a50c-5ed2-11e7-947b-041416579bb3.jpg',
                'api': {
                    'v2': 'https://api-pub.bitfinex.com',  # https://github.com/ccxt/ccxt/issues/5109
                    'public': 'https://api.bitfinex.com',
                    'private': 'https://api.bitfinex.com',
                },
                'www': 'https://www.bitfinex.com',
                'referral': 'https://www.bitfinex.com/?refcode=P61eYxFL',
                'doc': [
                    'https://docs.bitfinex.com/v1/docs',
                    'https://github.com/bitfinexcom/bitfinex-api-node',
                ],
            },
            'api': {
                # v2 symbol ids require a 't' prefix
                # just the public part of it(use bitfinex2 for everything else)
                'v2': {
                    'get': [
                        'platform/status',
                        'tickers',
                        'ticker/{symbol}',
                        'trades/{symbol}/hist',
                        'book/{symbol}/{precision}',
                        'book/{symbol}/P0',
                        'book/{symbol}/P1',
                        'book/{symbol}/P2',
                        'book/{symbol}/P3',
                        'book/{symbol}/R0',
                        'stats1/{key}:{size}:{symbol}:{side}/{section}',
                        'stats1/{key}:{size}:{symbol}/{section}',
                        'stats1/{key}:{size}:{symbol}:long/last',
                        'stats1/{key}:{size}:{symbol}:long/hist',
                        'stats1/{key}:{size}:{symbol}:short/last',
                        'stats1/{key}:{size}:{symbol}:short/hist',
                        'candles/trade:{timeframe}:{symbol}/{section}',
                        'candles/trade:{timeframe}:{symbol}/last',
                        'candles/trade:{timeframe}:{symbol}/hist',
                    ],
                },
                'public': {
                    'get': [
                        'book/{symbol}',
                        # 'candles/{symbol}',
                        'lendbook/{currency}',
                        'lends/{currency}',
                        'pubticker/{symbol}',
                        'stats/{symbol}',
                        'symbols',
                        'symbols_details',
                        'tickers',
                        'trades/{symbol}',
                    ],
                },
                'private': {
                    'post': [
                        'account_fees',
                        'account_infos',
                        'balances',
                        'basket_manage',
                        'credits',
                        'deposit/new',
                        'funding/close',
                        'history',
                        'history/movements',
                        'key_info',
                        'margin_infos',
                        'mytrades',
                        'mytrades_funding',
                        'offer/cancel',
                        'offer/new',
                        'offer/status',
                        'offers',
                        'offers/hist',
                        'order/cancel',
                        'order/cancel/all',
                        'order/cancel/multi',
                        'order/cancel/replace',
                        'order/new',
                        'order/new/multi',
                        'order/status',
                        'orders',
                        'orders/hist',
                        'position/claim',
                        'position/close',
                        'positions',
                        'summary',
                        'taken_funds',
                        'total_taken_funds',
                        'transfer',
                        'unused_taken_funds',
                        'withdraw',
                    ],
                },
            },
            'fees': {
                'trading': {
                    'tierBased': True,
                    'percentage': True,
                    'maker': 0.1 / 100,
                    'taker': 0.2 / 100,
                    'tiers': {
                        'taker': [
                            [0, 0.2 / 100],
                            [500000, 0.2 / 100],
                            [1000000, 0.2 / 100],
                            [2500000, 0.2 / 100],
                            [5000000, 0.2 / 100],
                            [7500000, 0.2 / 100],
                            [10000000, 0.18 / 100],
                            [15000000, 0.16 / 100],
                            [20000000, 0.14 / 100],
                            [25000000, 0.12 / 100],
                            [30000000, 0.1 / 100],
                        ],
                        'maker': [
                            [0, 0.1 / 100],
                            [500000, 0.08 / 100],
                            [1000000, 0.06 / 100],
                            [2500000, 0.04 / 100],
                            [5000000, 0.02 / 100],
                            [7500000, 0],
                            [10000000, 0],
                            [15000000, 0],
                            [20000000, 0],
                            [25000000, 0],
                            [30000000, 0],
                        ],
                    },
                },
                'funding': {
                    'tierBased': False,  # True for tier-based/progressive
                    'percentage': False,  # fixed commission
                    # Actually deposit fees are free for larger deposits(> $1000 USD equivalent)
                    # these values below are deprecated, we should not hardcode fees and limits anymore
                    # to be reimplemented with bitfinex funding fees from their API or web endpoints
                    'deposit': {
                        'BTC': 0.0004,
                        'IOTA': 0.5,
                        'ETH': 0.0027,
                        'BCH': 0.0001,
                        'LTC': 0.001,
                        'EOS': 0.24279,
                        'XMR': 0.04,
                        'SAN': 0.99269,
                        'DASH': 0.01,
                        'ETC': 0.01,
                        'XRP': 0.02,
                        'YYW': 16.915,
                        'NEO': 0,
                        'ZEC': 0.001,
                        'BTG': 0,
                        'OMG': 0.14026,
                        'DATA': 20.773,
                        'QASH': 1.9858,
                        'ETP': 0.01,
                        'QTUM': 0.01,
                        'EDO': 0.95001,
                        'AVT': 1.3045,
                        'USDT': 0,
                        'TRX': 28.184,
                        'ZRX': 1.9947,
                        'RCN': 10.793,
                        'TNB': 31.915,
                        'SNT': 14.976,
                        'RLC': 1.414,
                        'GNT': 5.8952,
                        'SPK': 10.893,
                        'REP': 0.041168,
                        'BAT': 6.1546,
                        'ELF': 1.8753,
                        'FUN': 32.336,
                        'SNG': 18.622,
                        'AID': 8.08,
                        'MNA': 16.617,
                        'NEC': 1.6504,
                        'XTZ': 0.2,
                    },
                    'withdraw': {
                        'BTC': 0.0004,
                        'IOTA': 0.5,
                        'ETH': 0.0027,
                        'BCH': 0.0001,
                        'LTC': 0.001,
                        'EOS': 0.24279,
                        'XMR': 0.04,
                        'SAN': 0.99269,
                        'DASH': 0.01,
                        'ETC': 0.01,
                        'XRP': 0.02,
                        'YYW': 16.915,
                        'NEO': 0,
                        'ZEC': 0.001,
                        'BTG': 0,
                        'OMG': 0.14026,
                        'DATA': 20.773,
                        'QASH': 1.9858,
                        'ETP': 0.01,
                        'QTUM': 0.01,
                        'EDO': 0.95001,
                        'AVT': 1.3045,
                        'USDT': 20,
                        'TRX': 28.184,
                        'ZRX': 1.9947,
                        'RCN': 10.793,
                        'TNB': 31.915,
                        'SNT': 14.976,
                        'RLC': 1.414,
                        'GNT': 5.8952,
                        'SPK': 10.893,
                        'REP': 0.041168,
                        'BAT': 6.1546,
                        'ELF': 1.8753,
                        'FUN': 32.336,
                        'SNG': 18.622,
                        'AID': 8.08,
                        'MNA': 16.617,
                        'NEC': 1.6504,
                        'XTZ': 0.2,
                    },
                },
            },
            # todo rewrite for https://api-pub.bitfinex.com//v2/conf/pub:map:tx:method
            'commonCurrencies': {
                'ABS': 'ABYSS',
                'AIO': 'AION',
                'ALG': 'ALGO',  # https://github.com/ccxt/ccxt/issues/6034
                'AMP': 'AMPL',
                'ATM': 'ATMI',
                'ATO': 'ATOM',  # https://github.com/ccxt/ccxt/issues/5118
                'BAB': 'BCH',
                'CTX': 'CTXC',
                'DAD': 'DADI',
                'DAT': 'DATA',
                'DSH': 'DASH',
                'DRK': 'DRK',
                # https://github.com/ccxt/ccxt/issues/7399
                # https://coinmarketcap.com/currencies/pnetwork/
                # https://en.cryptonomist.ch/blog/eidoo/the-edo-to-pnt-upgrade-what-you-need-to-know-updated/
                'EDO': 'PNT',
                'GSD': 'GUSD',
                'HOT': 'Hydro Protocol',
                'IOS': 'IOST',
                'IOT': 'IOTA',
                'IQX': 'IQ',
                'MIT': 'MITH',
                'MNA': 'MANA',
                'NCA': 'NCASH',
                'ORS': 'ORS Group',  # conflict with Origin Sport  #3230
                'POY': 'POLY',
                'QSH': 'QASH',
                'QTM': 'QTUM',
                'RBT': 'RBTC',
                'SEE': 'SEER',
                'SNG': 'SNGLS',
                'SPK': 'SPANK',
                'STJ': 'STORJ',
                'TRI': 'TRIO',
                'TSD': 'TUSD',
                'YYW': 'YOYOW',
                'UDC': 'USDC',
                'UST': 'USDT',
                'UTN': 'UTNP',
                'VSY': 'VSYS',
                'WAX': 'WAXP',
                'XCH': 'XCHF',
                'ZBT': 'ZB',
            },
            'exceptions': {
                'exact': {
                    'temporarily_unavailable': ExchangeNotAvailable,  # Sorry, the service is temporarily unavailable. See https://www.bitfinex.com/ for more info.
                    'Order could not be cancelled.': OrderNotFound,  # non-existent order
                    'No such order found.': OrderNotFound,  # ?
                    'Order price must be positive.': InvalidOrder,  # on price <= 0
                    'Could not find a key matching the given X-BFX-APIKEY.': AuthenticationError,
                    'Key price should be a decimal number, e.g. "123.456"': InvalidOrder,  # on isNaN(price)
                    'Key amount should be a decimal number, e.g. "123.456"': InvalidOrder,  # on isNaN(amount)
                    'ERR_RATE_LIMIT': RateLimitExceeded,
                    'Ratelimit': RateLimitExceeded,
                    'Nonce is too small.': InvalidNonce,
                    'No summary found.': ExchangeError,  # fetchTradingFees(summary) endpoint can give self vague error message
                    'Cannot evaluate your available balance, please try again': ExchangeNotAvailable,
                    'Unknown symbol': BadSymbol,
                },
                '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',
                },
            },
        })

    def fetch_funding_fees(self, params={}):
        self.load_markets()
        response = self.privatePostAccountFees(params)
        fees = response['withdraw']
        withdraw = {}
        ids = list(fees.keys())
        for i in range(0, len(ids)):
            id = ids[i]
            code = self.safe_currency_code(id)
            withdraw[code] = self.safe_float(fees, id)
        return {
            'info': response,
            'withdraw': withdraw,
            'deposit': withdraw,  # only for deposits of less than $1000
        }

    def fetch_trading_fees(self, params={}):
        self.load_markets()
        response = self.privatePostSummary(params)
        #
        #     {
        #         time: '2019-02-20T15:50:19.152000Z',
        #         trade_vol_30d: [
        #             {
        #                 curr: 'Total(USD)',
        #                 vol: 0,
        #                 vol_maker: 0,
        #                 vol_BFX: 0,
        #                 vol_BFX_maker: 0,
        #                 vol_ETHFX: 0,
        #                 vol_ETHFX_maker: 0
        #             }
        #         ],
        #         fees_funding_30d: {},
        #         fees_funding_total_30d: 0,
        #         fees_trading_30d: {},
        #         fees_trading_total_30d: 0,
        #         maker_fee: 0.001,
        #         taker_fee: 0.002
        #     }
        #
        return {
            'info': response,
            'maker': self.safe_float(response, 'maker_fee'),
            'taker': self.safe_float(response, 'taker_fee'),
        }

    def fetch_markets(self, params={}):
        ids = self.publicGetSymbols()
        details = self.publicGetSymbolsDetails()
        result = []
        for i in range(0, len(details)):
            market = details[i]
            id = self.safe_string(market, 'pair')
            if not self.in_array(id, ids):
                continue
            id = id.upper()
            baseId = None
            quoteId = None
            if id.find(':') >= 0:
                parts = id.split(':')
                baseId = parts[0]
                quoteId = parts[1]
            else:
                baseId = id[0:3]
                quoteId = id[3:6]
            base = self.safe_currency_code(baseId)
            quote = self.safe_currency_code(quoteId)
            symbol = base + '/' + quote
            precision = {
                'price': self.safe_integer(market, 'price_precision'),
                # https://docs.bitfinex.com/docs/introduction#amount-precision
                # The amount field allows up to 8 decimals.
                # Anything exceeding self will be rounded to the 8th decimal.
                'amount': 8,
            }
            limits = {
                'amount': {
                    'min': self.safe_float(market, 'minimum_order_size'),
                    'max': self.safe_float(market, 'maximum_order_size'),
                },
                'price': {
                    'min': math.pow(10, -precision['price']),
                    'max': math.pow(10, precision['price']),
                },
            }
            limits['cost'] = {
                'min': limits['amount']['min'] * limits['price']['min'],
                'max': None,
            }
            result.append({
                'id': id,
                'symbol': symbol,
                'base': base,
                'quote': quote,
                'baseId': baseId,
                'quoteId': quoteId,
                'active': True,
                'precision': precision,
                'limits': limits,
                'info': market,
            })
        return result

    def amount_to_precision(self, symbol, amount):
        # https://docs.bitfinex.com/docs/introduction#amount-precision
        # The amount field allows up to 8 decimals.
        # Anything exceeding self will be rounded to the 8th decimal.
        return self.decimal_to_precision(amount, TRUNCATE, self.markets[symbol]['precision']['amount'], DECIMAL_PLACES)

    def price_to_precision(self, symbol, price):
        price = self.decimal_to_precision(price, ROUND, self.markets[symbol]['precision']['price'], self.precisionMode)
        # https://docs.bitfinex.com/docs/introduction#price-precision
        # The precision level of all trading prices is based on significant figures.
        # All pairs on Bitfinex use up to 5 significant digits and up to 8 decimals(e.g. 1.2345, 123.45, 1234.5, 0.00012345).
        # Prices submit with a precision larger than 5 will be cut by the API.
        return self.decimal_to_precision(price, TRUNCATE, 8, DECIMAL_PLACES)

    def calculate_fee(self, symbol, type, side, amount, price, takerOrMaker='taker', params={}):
        market = self.markets[symbol]
        rate = market[takerOrMaker]
        cost = amount * rate
        key = 'quote'
        if side == 'sell':
            cost *= price
        else:
            key = 'base'
        code = market[key]
        currency = self.safe_value(self.currencies, code)
        if currency is not None:
            precision = self.safe_integer(currency, 'precision')
            if precision is not None:
                cost = float(self.currency_to_precision(code, cost))
        return {
            'type': takerOrMaker,
            'currency': market[key],
            'rate': rate,
            'cost': cost,
        }

    def fetch_balance(self, params={}):
        self.load_markets()
        types = {
            'exchange': 'exchange',
            'deposit': 'funding',
            'trading': 'margin',
        }
        balanceType = self.safe_string(params, 'type', 'exchange')
        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}
        for i in range(0, len(response)):
            balance = response[i]
            type = self.safe_string(balance, 'type')
            parsedType = self.safe_string(types, type)
            if (parsedType == balanceType) or (type == balanceType):
                currencyId = self.safe_string(balance, 'currency')
                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_float(balance, 'available')
                    account['total'] = self.safe_float(balance, 'amount')
                    result[code] = account
        return self.parse_balance(result)

    def fetch_order_book(self, symbol, limit=None, params={}):
        self.load_markets()
        request = {
            'symbol': self.market_id(symbol),
        }
        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, None, 'bids', 'asks', 'price', 'amount')

    def fetch_tickers(self, symbols=None, params={}):
        self.load_markets()
        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={}):
        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_float(ticker, 'timestamp')
        if timestamp is not None:
            timestamp *= 1000
        timestamp = int(timestamp)
        symbol = None
        if market is not None:
            symbol = market['symbol']
        elif 'pair' in ticker:
            marketId = self.safe_string(ticker, 'pair')
            if marketId is not None:
                if marketId in self.markets_by_id:
                    market = self.markets_by_id[marketId]
                    symbol = market['symbol']
                else:
                    baseId = marketId[0:3]
                    quoteId = marketId[3:6]
                    base = self.safe_currency_code(baseId)
                    quote = self.safe_currency_code(quoteId)
                    symbol = base + '/' + quote
        last = self.safe_float(ticker, 'last_price')
        return {
            'symbol': symbol,
            'timestamp': timestamp,
            'datetime': self.iso8601(timestamp),
            'high': self.safe_float(ticker, 'high'),
            'low': self.safe_float(ticker, 'low'),
            'bid': self.safe_float(ticker, 'bid'),
            'bidVolume': None,
            'ask': self.safe_float(ticker, 'ask'),
            'askVolume': None,
            'vwap': None,
            'open': None,
            'close': last,
            'last': last,
            'previousClose': None,
            'change': None,
            'percentage': None,
            'average': self.safe_float(ticker, 'mid'),
            'baseVolume': self.safe_float(ticker, 'volume'),
            'quoteVolume': None,
            'info': ticker,
        }

    def parse_trade(self, trade, market):
        id = self.safe_string(trade, 'tid')
        timestamp = self.safe_float(trade, 'timestamp')
        if timestamp is not None:
            timestamp = int(timestamp) * 1000
        type = None
        side = self.safe_string_lower(trade, 'type')
        orderId = self.safe_string(trade, 'order_id')
        price = self.safe_float(trade, 'price')
        amount = self.safe_float(trade, 'amount')
        cost = None
        if price is not None:
            if amount is not None:
                cost = price * amount
        fee = None
        if 'fee_amount' in trade:
            feeCost = -self.safe_float(trade, 'fee_amount')
            feeCurrencyId = self.safe_string(trade, 'fee_currency')
            feeCurrencyCode = self.safe_currency_code(feeCurrencyId)
            fee = {
                'cost': feeCost,
                'currency': feeCurrencyCode,
            }
        return {
            'id': id,
            'info': trade,
            'timestamp': timestamp,
            'datetime': self.iso8601(timestamp),
            'symbol': market['symbol'],
            'type': type,
            'order': orderId,
            'side': side,
            'takerOrMaker': None,
            'price': price,
            'amount': amount,
            'cost': cost,
            'fee': fee,
        }

    def fetch_trades(self, symbol, since=None, limit=50, params={}):
        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={}):
        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={}):
        self.load_markets()
        request = {
            'symbol': self.market_id(symbol),
            'side': side,
            'amount': self.amount_to_precision(symbol, amount),
            'type': self.safe_string(self.options['orderTypes'], type, type),
            'ocoorder': False,
            'buy_price_oco': 0,
            'sell_price_oco': 0,
        }
        if type == 'market':
            request['price'] = str(self.nonce())
        else:
            request['price'] = self.price_to_precision(symbol, price)
        response = self.privatePostOrderNew(self.extend(request, params))
        return self.parse_order(response)

    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={}):
        self.load_markets()
        request = {
            'order_id': int(id),
        }
        return self.privatePostOrderCancel(self.extend(request, params))

    def cancel_all_orders(self, symbol=None, params={}):
        return self.privatePostOrderCancelAll(params)

    def parse_order(self, order, market=None):
        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'
        symbol = None
        if market is None:
            marketId = self.safe_string_upper(order, 'symbol')
            if marketId is not None:
                if marketId in self.markets_by_id:
                    market = self.markets_by_id[marketId]
        if market is not None:
            symbol = market['symbol']
        orderType = order['type']
        exchange = orderType.find('exchange ') >= 0
        if exchange:
            parts = order['type'].split(' ')
            orderType = parts[1]
        timestamp = self.safe_float(order, 'timestamp')
        if timestamp is not None:
            timestamp = int(timestamp) * 1000
        id = self.safe_string(order, 'id')
        return {
            '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_float(order, 'price'),
            'stopPrice': None,
            'average': self.safe_float(order, 'avg_execution_price'),
            'amount': self.safe_float(order, 'original_amount'),
            'remaining': self.safe_float(order, 'remaining_amount'),
            'filled': self.safe_float(order, 'executed_amount'),
            'status': status,
            'fee': None,
            'cost': None,
            'trades': None,
        }

    def fetch_open_orders(self, symbol=None, since=None, limit=None, params={}):
        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={}):
        self.load_markets()
        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={}):
        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_float(ohlcv, 1),
            self.safe_float(ohlcv, 3),
            self.safe_float(ohlcv, 4),
            self.safe_float(ohlcv, 2),
            self.safe_float(ohlcv, 5),
        ]

    def fetch_ohlcv(self, symbol, timeframe='1m', since=None, limit=None, params={}):
        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={}):
        self.load_markets()
        request = {
            'renew': 1,
        }
        response = self.fetch_deposit_address(code, self.extend(request, params))
        return response

    def fetch_deposit_address(self, code, params={}):
        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,
            'info': response,
        }

    def fetch_transactions(self, code=None, since=None, limit=None, params={}):
        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"
        #     }
        #
        timestamp = self.safe_float(transaction, 'timestamp_created')
        if timestamp is not None:
            timestamp = int(timestamp * 1000)
        updated = self.safe_float(transaction, 'timestamp')
        if updated is not None:
            updated = int(updated * 1000)
        currencyId = self.safe_string(transaction, 'currency')
        code = self.safe_currency_code(currencyId, currency)
        type = self.safe_string_lower(transaction, 'type')  # DEPOSIT or WITHDRAWAL
        status = self.parse_transaction_status(self.safe_string(transaction, 'status'))
        feeCost = self.safe_float(transaction, 'fee')
        if feeCost is not None:
            feeCost = abs(feeCost)
        return {
            'info': transaction,
            'id': self.safe_string(transaction, 'id'),
            'txid': self.safe_string(transaction, 'txid'),
            'timestamp': timestamp,
            'datetime': self.iso8601(timestamp),
            'address': self.safe_string(transaction, 'address'),  # todo: self is actually the tag for XRP transfers(the address is missing)
            'tag': None,  # refix it properly for the tag from description
            'type': type,
            'amount': self.safe_float(transaction, 'amount'),
            'currency': code,
            'status': status,
            'updated': updated,
            'fee': {
                'currency': code,
                'cost': feeCost,
                'rate': None,
            },
        }

    def parse_transaction_status(self, status):
        statuses = {
            'SENDING': 'pending',
            'CANCELED': 'canceled',
            'ZEROCONFIRMED': 'failed',  # ZEROCONFIRMED happens e.g. in a double spend attempt(I had one in my movementsnot )
            'COMPLETED': 'ok',
        }
        return self.safe_string(statuses, status, status)

    def withdraw(self, code, amount, address, tag=None, 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)
        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))
        response = responses[0]
        id = self.safe_string(response, 'withdrawal_id')
        message = self.safe_string(response, 'message')
        errorMessage = self.find_broadly_matched_key(self.exceptions['broad'], message)
        if id == 0:
            if errorMessage is not None:
                ExceptionClass = self.exceptions['broad'][errorMessage]
                raise ExceptionClass(self.id + ' ' + message)
            raise ExchangeError(self.id + ' withdraw returned an id of zero: ' + self.json(response))
        return {
            'info': response,
            'id': id,
        }

    def fetch_positions(self, symbols=None, since=None, limit=None, params={}):
        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
        if code >= 400:
            if body[0] == '{':
                feedback = self.id + ' ' + body
                message = self.safe_string_2(response, 'message', 'error')
                self.throw_exactly_matched_exception(self.exceptions['exact'], message, feedback)
                self.throw_broadly_matched_exception(self.exceptions['broad'], message, feedback)
                raise ExchangeError(feedback)  # unknown message
