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

# -----------------------------------------------------------------------------

try:
    basestring  # Python 3
except NameError:
    basestring = str  # Python 2
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 InvalidAddress
from ccxt.base.errors import InvalidOrder
from ccxt.base.errors import OrderNotFound
from ccxt.base.errors import CancelPending
from ccxt.base.errors import DDoSProtection
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 TRUNCATE
from ccxt.base.decimal_to_precision import DECIMAL_PLACES
from ccxt.base.precise import Precise


class kraken(Exchange):

    def describe(self):
        return self.deep_extend(super(kraken, self).describe(), {
            'id': 'kraken',
            'name': 'Kraken',
            'countries': ['US'],
            'version': '0',
            'rateLimit': 3000,
            'certified': True,
            'pro': True,
            'has': {
                'cancelAllOrders': True,
                'cancelOrder': True,
                'CORS': False,
                'createDepositAddress': True,
                'createOrder': True,
                'fetchBalance': True,
                'fetchClosedOrders': True,
                'fetchCurrencies': True,
                'fetchDepositAddress': True,
                'fetchDeposits': True,
                'fetchLedger': True,
                'fetchLedgerEntry': True,
                'fetchMarkets': True,
                'fetchMyTrades': True,
                'fetchOHLCV': True,
                'fetchOpenOrders': True,
                'fetchOrder': True,
                'fetchOrderBook': True,
                'fetchOrderTrades': 'emulated',
                'fetchTicker': True,
                'fetchTickers': True,
                'fetchTime': True,
                'fetchTrades': True,
                'fetchTradingFee': True,
                'fetchTradingFees': True,
                'fetchWithdrawals': True,
                'withdraw': True,
            },
            'marketsByAltname': {},
            'timeframes': {
                '1m': 1,
                '5m': 5,
                '15m': 15,
                '30m': 30,
                '1h': 60,
                '4h': 240,
                '1d': 1440,
                '1w': 10080,
                '2w': 21600,
            },
            'urls': {
                'logo': 'https://user-images.githubusercontent.com/51840849/76173629-fc67fb00-61b1-11ea-84fe-f2de582f58a3.jpg',
                'api': {
                    'public': 'https://api.kraken.com',
                    'private': 'https://api.kraken.com',
                    'zendesk': 'https://kraken.zendesk.com/api/v2/help_center/en-us/articles',  # use the public zendesk api to receive article bodies and bypass new anti-spam protections
                },
                'www': 'https://www.kraken.com',
                'doc': 'https://www.kraken.com/features/api',
                'fees': 'https://www.kraken.com/en-us/features/fee-schedule',
            },
            'fees': {
                'trading': {
                    'tierBased': True,
                    'percentage': True,
                    'taker': 0.26 / 100,
                    'maker': 0.16 / 100,
                    'tiers': {
                        'taker': [
                            [0, 0.0026],
                            [50000, 0.0024],
                            [100000, 0.0022],
                            [250000, 0.0020],
                            [500000, 0.0018],
                            [1000000, 0.0016],
                            [2500000, 0.0014],
                            [5000000, 0.0012],
                            [10000000, 0.0001],
                        ],
                        'maker': [
                            [0, 0.0016],
                            [50000, 0.0014],
                            [100000, 0.0012],
                            [250000, 0.0010],
                            [500000, 0.0008],
                            [1000000, 0.0006],
                            [2500000, 0.0004],
                            [5000000, 0.0002],
                            [10000000, 0.0],
                        ],
                    },
                },
                # self is a bad way of hardcoding fees that change on daily basis
                # hardcoding is now considered obsolete, we will remove all of it eventually
                'funding': {
                    'tierBased': False,
                    'percentage': False,
                    'withdraw': {
                        'BTC': 0.001,
                        'ETH': 0.005,
                        'XRP': 0.02,
                        'XLM': 0.00002,
                        'LTC': 0.02,
                        'DOGE': 2,
                        'ZEC': 0.00010,
                        'ICN': 0.02,
                        'REP': 0.01,
                        'ETC': 0.005,
                        'MLN': 0.003,
                        'XMR': 0.05,
                        'DASH': 0.005,
                        'GNO': 0.01,
                        'EOS': 0.5,
                        'BCH': 0.001,
                        'XTZ': 0.05,
                        'USD': 5,  # if domestic wire
                        'EUR': 5,  # if domestic wire
                        'CAD': 10,  # CAD EFT Withdrawal
                        'JPY': 300,  # if domestic wire
                    },
                    'deposit': {
                        'BTC': 0,
                        'ETH': 0,
                        'XRP': 0,
                        'XLM': 0,
                        'LTC': 0,
                        'DOGE': 0,
                        'ZEC': 0,
                        'ICN': 0,
                        'REP': 0,
                        'ETC': 0,
                        'MLN': 0,
                        'XMR': 0,
                        'DASH': 0,
                        'GNO': 0,
                        'EOS': 0,
                        'BCH': 0,
                        'XTZ': 0.05,
                        'USD': 5,  # if domestic wire
                        'EUR': 0,  # free deposit if EUR SEPA Deposit
                        'CAD': 5,  # if domestic wire
                        'JPY': 0,  # Domestic Deposit(Free, ¥5,000 deposit minimum)
                    },
                },
            },
            'api': {
                'zendesk': {
                    'get': [
                        # we should really refrain from putting fixed fee numbers and stop hardcoding
                        # we will be using their web APIs to scrape all numbers from these articles
                        '360000292886',  # -What-are-the-deposit-fees-
                        '201893608',  # -What-are-the-withdrawal-fees-
                    ],
                },
                'public': {
                    'get': [
                        'Assets',
                        'AssetPairs',
                        'Depth',
                        'OHLC',
                        'Spread',
                        'Ticker',
                        'Time',
                        'Trades',
                    ],
                },
                'private': {
                    'post': [
                        'AddOrder',
                        'AddExport',
                        'Balance',
                        'CancelAll',
                        'CancelOrder',
                        'ClosedOrders',
                        'DepositAddresses',
                        'DepositMethods',
                        'DepositStatus',
                        'ExportStatus',
                        'GetWebSocketsToken',
                        'Ledgers',
                        'OpenOrders',
                        'OpenPositions',
                        'QueryLedgers',
                        'QueryOrders',
                        'QueryTrades',
                        'RetrieveExport',
                        'RemoveExport',
                        'TradeBalance',
                        'TradesHistory',
                        'TradeVolume',
                        'Withdraw',
                        'WithdrawCancel',
                        'WithdrawInfo',
                        'WithdrawStatus',
                    ],
                },
            },
            'commonCurrencies': {
                'XBT': 'BTC',
                'XBT.M': 'BTC.M',  # https://support.kraken.com/hc/en-us/articles/360039879471-What-is-Asset-S-and-Asset-M-
                'XDG': 'DOGE',
                'REPV2': 'REP',
                'REP': 'REPV1',
            },
            'options': {
                'cacheDepositMethodsOnFetchDepositAddress': True,  # will issue up to two calls in fetchDepositAddress
                'depositMethods': {},
                'delistedMarketsById': {},
                # cannot withdraw/deposit these
                'inactiveCurrencies': ['CAD', 'USD', 'JPY', 'GBP'],
            },
            'exceptions': {
                'EQuery:Invalid asset pair': BadSymbol,  # {"error":["EQuery:Invalid asset pair"]}
                'EAPI:Invalid key': AuthenticationError,
                'EFunding:Unknown withdraw key': ExchangeError,
                'EFunding:Invalid amount': InsufficientFunds,
                'EService:Unavailable': ExchangeNotAvailable,
                'EDatabase:Internal error': ExchangeNotAvailable,
                'EService:Busy': ExchangeNotAvailable,
                'EQuery:Unknown asset': ExchangeError,
                'EAPI:Rate limit exceeded': DDoSProtection,
                'EOrder:Rate limit exceeded': DDoSProtection,
                'EGeneral:Internal error': ExchangeNotAvailable,
                'EGeneral:Temporary lockout': DDoSProtection,
                'EGeneral:Permission denied': PermissionDenied,
                'EOrder:Unknown order': InvalidOrder,
                'EOrder:Order minimum not met': InvalidOrder,
            },
        })

    def cost_to_precision(self, symbol, cost):
        return self.decimal_to_precision(cost, TRUNCATE, self.markets[symbol]['precision']['price'], DECIMAL_PLACES)

    def fee_to_precision(self, symbol, fee):
        return self.decimal_to_precision(fee, TRUNCATE, self.markets[symbol]['precision']['amount'], DECIMAL_PLACES)

    def fetch_markets(self, params={}):
        response = self.publicGetAssetPairs(params)
        #
        #     {
        #         "error":[],
        #         "result":{
        #             "ADAETH":{
        #                 "altname":"ADAETH",
        #                 "wsname":"ADA\/ETH",
        #                 "aclass_base":"currency",
        #                 "base":"ADA",
        #                 "aclass_quote":"currency",
        #                 "quote":"XETH",
        #                 "lot":"unit",
        #                 "pair_decimals":7,
        #                 "lot_decimals":8,
        #                 "lot_multiplier":1,
        #                 "leverage_buy":[],
        #                 "leverage_sell":[],
        #                 "fees":[
        #                     [0,0.26],
        #                     [50000,0.24],
        #                     [100000,0.22],
        #                     [250000,0.2],
        #                     [500000,0.18],
        #                     [1000000,0.16],
        #                     [2500000,0.14],
        #                     [5000000,0.12],
        #                     [10000000,0.1]
        #                 ],
        #                 "fees_maker":[
        #                     [0,0.16],
        #                     [50000,0.14],
        #                     [100000,0.12],
        #                     [250000,0.1],
        #                     [500000,0.08],
        #                     [1000000,0.06],
        #                     [2500000,0.04],
        #                     [5000000,0.02],
        #                     [10000000,0]
        #                 ],
        #                 "fee_volume_currency":"ZUSD",
        #                 "margin_call":80,
        #                 "margin_stop":40,
        #                 "ordermin": "1"
        #             },
        #         }
        #     }
        #
        markets = self.safe_value(response, 'result', {})
        keys = list(markets.keys())
        result = []
        for i in range(0, len(keys)):
            id = keys[i]
            market = markets[id]
            baseId = self.safe_string(market, 'base')
            quoteId = self.safe_string(market, 'quote')
            base = self.safe_currency_code(baseId)
            quote = self.safe_currency_code(quoteId)
            darkpool = id.find('.d') >= 0
            altname = self.safe_string(market, 'altname')
            symbol = altname if darkpool else (base + '/' + quote)
            makerFees = self.safe_value(market, 'fees_maker', [])
            firstMakerFee = self.safe_value(makerFees, 0, [])
            firstMakerFeeRate = self.safe_number(firstMakerFee, 1)
            maker = None
            if firstMakerFeeRate is not None:
                maker = float(firstMakerFeeRate) / 100
            takerFees = self.safe_value(market, 'fees', [])
            firstTakerFee = self.safe_value(takerFees, 0, [])
            firstTakerFeeRate = self.safe_number(firstTakerFee, 1)
            taker = None
            if firstTakerFeeRate is not None:
                taker = float(firstTakerFeeRate) / 100
            precision = {
                'amount': self.safe_integer(market, 'lot_decimals'),
                'price': self.safe_integer(market, 'pair_decimals'),
            }
            minAmount = self.safe_number(market, 'ordermin')
            result.append({
                'id': id,
                'symbol': symbol,
                'base': base,
                'quote': quote,
                'baseId': baseId,
                'quoteId': quoteId,
                'darkpool': darkpool,
                'info': market,
                'altname': market['altname'],
                'maker': maker,
                'taker': taker,
                'active': True,
                'precision': precision,
                'limits': {
                    'amount': {
                        'min': minAmount,
                        'max': math.pow(10, precision['amount']),
                    },
                    'price': {
                        'min': math.pow(10, -precision['price']),
                        'max': None,
                    },
                    'cost': {
                        'min': 0,
                        'max': None,
                    },
                },
            })
        result = self.append_inactive_markets(result)
        self.marketsByAltname = self.index_by(result, 'altname')
        return result

    def safe_currency(self, currencyId, currency=None):
        if len(currencyId) > 3:
            if (currencyId.find('X') == 0) or (currencyId.find('Z') == 0):
                if currencyId.find('.') > 0:
                    return super(kraken, self).safe_currency(currencyId, currency)
                else:
                    currencyId = currencyId[1:]
        return super(kraken, self).safe_currency(currencyId, currency)

    def append_inactive_markets(self, result):
        # result should be an array to append to
        precision = {'amount': 8, 'price': 8}
        costLimits = {'min': 0, 'max': None}
        priceLimits = {'min': math.pow(10, -precision['price']), 'max': None}
        amountLimits = {'min': math.pow(10, -precision['amount']), 'max': math.pow(10, precision['amount'])}
        limits = {'amount': amountLimits, 'price': priceLimits, 'cost': costLimits}
        defaults = {
            'darkpool': False,
            'info': None,
            'maker': None,
            'taker': None,
            'active': False,
            'precision': precision,
            'limits': limits,
        }
        markets = [
            # {'id': 'XXLMZEUR', 'symbol': 'XLM/EUR', 'base': 'XLM', 'quote': 'EUR', 'altname': 'XLMEUR'},
        ]
        for i in range(0, len(markets)):
            result.append(self.extend(defaults, markets[i]))
        return result

    def fetch_currencies(self, params={}):
        response = self.publicGetAssets(params)
        #
        #     {
        #         "error": [],
        #         "result": {
        #             "ADA": {"aclass": "currency", "altname": "ADA", "decimals": 8, "display_decimals": 6},
        #             "BCH": {"aclass": "currency", "altname": "BCH", "decimals": 10, "display_decimals": 5},
        #             ...
        #         },
        #     }
        #
        currencies = self.safe_value(response, 'result')
        ids = list(currencies.keys())
        result = {}
        for i in range(0, len(ids)):
            id = ids[i]
            currency = currencies[id]
            # todo: will need to rethink the fees
            # see: https://support.kraken.com/hc/en-us/articles/201893608-What-are-the-withdrawal-fees-
            # to add support for multiple withdrawal/deposit methods and
            # differentiated fees for each particular method
            code = self.safe_currency_code(self.safe_string(currency, 'altname'))
            precision = self.safe_integer(currency, 'decimals')
            # assumes all currencies are active except those listed above
            active = not self.in_array(code, self.options['inactiveCurrencies'])
            result[code] = {
                'id': id,
                'code': code,
                'info': currency,
                'name': code,
                'active': active,
                'fee': None,
                'precision': precision,
                'limits': {
                    'amount': {
                        'min': math.pow(10, -precision),
                        'max': math.pow(10, precision),
                    },
                    'withdraw': {
                        'min': None,
                        'max': math.pow(10, precision),
                    },
                },
            }
        return result

    def fetch_trading_fees(self, params={}):
        self.load_markets()
        self.check_required_credentials()
        response = self.privatePostTradeVolume(params)
        tradedVolume = self.safe_number(response['result'], 'volume')
        tiers = self.fees['trading']['tiers']
        taker = tiers['taker'][1]
        maker = tiers['maker'][1]
        for i in range(0, len(tiers['taker'])):
            if tradedVolume >= tiers['taker'][i][0]:
                taker = tiers['taker'][i][1]
        for i in range(0, len(tiers['maker'])):
            if tradedVolume >= tiers['maker'][i][0]:
                maker = tiers['maker'][i][1]
        return {
            'info': response,
            'maker': maker,
            'taker': taker,
        }

    def parse_bid_ask(self, bidask, priceKey=0, amountKey=1):
        price = self.safe_number(bidask, priceKey)
        amount = self.safe_number(bidask, amountKey)
        timestamp = self.safe_integer(bidask, 2)
        return [price, amount, timestamp]

    def fetch_order_book(self, symbol, limit=None, params={}):
        self.load_markets()
        market = self.market(symbol)
        if market['darkpool']:
            raise ExchangeError(self.id + ' does not provide an order book for darkpool symbol ' + symbol)
        request = {
            'pair': market['id'],
        }
        if limit is not None:
            request['count'] = limit  # 100
        response = self.publicGetDepth(self.extend(request, params))
        #
        #     {
        #         "error":[],
        #         "result":{
        #             "XETHXXBT":{
        #                 "asks":[
        #                     ["0.023480","4.000",1586321307],
        #                     ["0.023490","50.095",1586321306],
        #                     ["0.023500","28.535",1586321302],
        #                 ],
        #                 "bids":[
        #                     ["0.023470","59.580",1586321307],
        #                     ["0.023460","20.000",1586321301],
        #                     ["0.023440","67.832",1586321306],
        #                 ]
        #             }
        #         }
        #     }
        #
        result = self.safe_value(response, 'result', {})
        orderbook = self.safe_value(result, market['id'])
        # sometimes kraken returns wsname instead of market id
        # https://github.com/ccxt/ccxt/issues/8662
        marketInfo = self.safe_value(market, 'info', {})
        wsName = self.safe_value(marketInfo, 'wsname')
        if wsName is not None:
            orderbook = self.safe_value(result, wsName, orderbook)
        return self.parse_order_book(orderbook, symbol)

    def parse_ticker(self, ticker, market=None):
        timestamp = self.milliseconds()
        symbol = None
        if market:
            symbol = market['symbol']
        baseVolume = float(ticker['v'][1])
        vwap = float(ticker['p'][1])
        quoteVolume = None
        if baseVolume is not None and vwap is not None:
            quoteVolume = baseVolume * vwap
        last = float(ticker['c'][0])
        return {
            'symbol': symbol,
            'timestamp': timestamp,
            'datetime': self.iso8601(timestamp),
            'high': float(ticker['h'][1]),
            'low': float(ticker['l'][1]),
            'bid': float(ticker['b'][0]),
            'bidVolume': None,
            'ask': float(ticker['a'][0]),
            'askVolume': None,
            'vwap': vwap,
            'open': self.safe_number(ticker, 'o'),
            'close': last,
            'last': last,
            'previousClose': None,
            'change': None,
            'percentage': None,
            'average': None,
            'baseVolume': baseVolume,
            'quoteVolume': quoteVolume,
            'info': ticker,
        }

    def fetch_tickers(self, symbols=None, params={}):
        self.load_markets()
        symbols = self.symbols if (symbols is None) else symbols
        marketIds = []
        for i in range(0, len(symbols)):
            symbol = symbols[i]
            market = self.markets[symbol]
            if market['active'] and not market['darkpool']:
                marketIds.append(market['id'])
        request = {
            'pair': ','.join(marketIds),
        }
        response = self.publicGetTicker(self.extend(request, params))
        tickers = response['result']
        ids = list(tickers.keys())
        result = {}
        for i in range(0, len(ids)):
            id = ids[i]
            market = self.markets_by_id[id]
            symbol = market['symbol']
            ticker = tickers[id]
            result[symbol] = self.parse_ticker(ticker, market)
        return self.filter_by_array(result, 'symbol', symbols)

    def fetch_ticker(self, symbol, params={}):
        self.load_markets()
        darkpool = symbol.find('.d') >= 0
        if darkpool:
            raise ExchangeError(self.id + ' does not provide a ticker for darkpool symbol ' + symbol)
        market = self.market(symbol)
        request = {
            'pair': market['id'],
        }
        response = self.publicGetTicker(self.extend(request, params))
        ticker = response['result'][market['id']]
        return self.parse_ticker(ticker, market)

    def parse_ohlcv(self, ohlcv, market=None):
        #
        #     [
        #         1591475640,
        #         "0.02500",
        #         "0.02500",
        #         "0.02500",
        #         "0.02500",
        #         "0.02500",
        #         "9.12201000",
        #         5
        #     ]
        #
        return [
            self.safe_timestamp(ohlcv, 0),
            self.safe_number(ohlcv, 1),
            self.safe_number(ohlcv, 2),
            self.safe_number(ohlcv, 3),
            self.safe_number(ohlcv, 4),
            self.safe_number(ohlcv, 6),
        ]

    def fetch_ohlcv(self, symbol, timeframe='1m', since=None, limit=None, params={}):
        self.load_markets()
        market = self.market(symbol)
        request = {
            'pair': market['id'],
            'interval': self.timeframes[timeframe],
        }
        if since is not None:
            request['since'] = int((since - 1) / 1000)
        response = self.publicGetOHLC(self.extend(request, params))
        #
        #     {
        #         "error":[],
        #         "result":{
        #             "XETHXXBT":[
        #                 [1591475580,"0.02499","0.02499","0.02499","0.02499","0.00000","0.00000000",0],
        #                 [1591475640,"0.02500","0.02500","0.02500","0.02500","0.02500","9.12201000",5],
        #                 [1591475700,"0.02499","0.02499","0.02499","0.02499","0.02499","1.28681415",2],
        #                 [1591475760,"0.02499","0.02499","0.02499","0.02499","0.02499","0.08800000",1],
        #             ],
        #             "last":1591517580
        #         }
        #     }
        result = self.safe_value(response, 'result', {})
        ohlcvs = self.safe_value(result, market['id'], [])
        return self.parse_ohlcvs(ohlcvs, market, timeframe, since, limit)

    def parse_ledger_entry_type(self, type):
        types = {
            'trade': 'trade',
            'withdrawal': 'transaction',
            'deposit': 'transaction',
            'transfer': 'transfer',
            'margin': 'margin',
        }
        return self.safe_string(types, type, type)

    def parse_ledger_entry(self, item, currency=None):
        #
        #     {
        #         'LTFK7F-N2CUX-PNY4SX': {
        #             refid: "TSJTGT-DT7WN-GPPQMJ",
        #             time:  1520102320.555,
        #             type: "trade",
        #             aclass: "currency",
        #             asset: "XETH",
        #             amount: "0.1087194600",
        #             fee: "0.0000000000",
        #             balance: "0.2855851000"
        #         },
        #         ...
        #     }
        #
        id = self.safe_string(item, 'id')
        direction = None
        account = None
        referenceId = self.safe_string(item, 'refid')
        referenceAccount = None
        type = self.parse_ledger_entry_type(self.safe_string(item, 'type'))
        code = self.safe_currency_code(self.safe_string(item, 'asset'), currency)
        amount = self.safe_number(item, 'amount')
        if amount < 0:
            direction = 'out'
            amount = abs(amount)
        else:
            direction = 'in'
        time = self.safe_number(item, 'time')
        timestamp = None
        if time is not None:
            timestamp = int(time * 1000)
        fee = {
            'cost': self.safe_number(item, 'fee'),
            'currency': code,
        }
        before = None
        after = self.safe_number(item, 'balance')
        status = 'ok'
        return {
            'info': item,
            'id': id,
            'direction': direction,
            'account': account,
            'referenceId': referenceId,
            'referenceAccount': referenceAccount,
            'type': type,
            'currency': code,
            'amount': amount,
            'before': before,
            'after': after,
            'status': status,
            'timestamp': timestamp,
            'datetime': self.iso8601(timestamp),
            'fee': fee,
        }

    def fetch_ledger(self, code=None, since=None, limit=None, params={}):
        # https://www.kraken.com/features/api#get-ledgers-info
        self.load_markets()
        request = {}
        currency = None
        if code is not None:
            currency = self.currency(code)
            request['asset'] = currency['id']
        if since is not None:
            request['start'] = int(since / 1000)
        response = self.privatePostLedgers(self.extend(request, params))
        # { error: [],
        #   result: {ledger: {'LPUAIB-TS774-UKHP7X': {  refid: "A2B4HBV-L4MDIE-JU4N3N",
        #                                                   time:  1520103488.314,
        #                                                   type: "withdrawal",
        #                                                 aclass: "currency",
        #                                                  asset: "XETH",
        #                                                 amount: "-0.2805800000",
        #                                                    fee: "0.0050000000",
        #                                                balance: "0.0000051000"           },
        result = self.safe_value(response, 'result', {})
        ledger = self.safe_value(result, 'ledger', {})
        keys = list(ledger.keys())
        items = []
        for i in range(0, len(keys)):
            key = keys[i]
            value = ledger[key]
            value['id'] = key
            items.append(value)
        return self.parse_ledger(items, currency, since, limit)

    def fetch_ledger_entries_by_ids(self, ids, code=None, params={}):
        # https://www.kraken.com/features/api#query-ledgers
        self.load_markets()
        ids = ','.join(ids)
        request = self.extend({
            'id': ids,
        }, params)
        response = self.privatePostQueryLedgers(request)
        # { error: [],
        #   result: {'LPUAIB-TS774-UKHP7X': {  refid: "A2B4HBV-L4MDIE-JU4N3N",
        #                                         time:  1520103488.314,
        #                                         type: "withdrawal",
        #                                       aclass: "currency",
        #                                        asset: "XETH",
        #                                       amount: "-0.2805800000",
        #                                          fee: "0.0050000000",
        #                                      balance: "0.0000051000"           }}}
        result = response['result']
        keys = list(result.keys())
        items = []
        for i in range(0, len(keys)):
            key = keys[i]
            value = result[key]
            value['id'] = key
            items.append(value)
        return self.parse_ledger(items)

    def fetch_ledger_entry(self, id, code=None, params={}):
        items = self.fetch_ledger_entries_by_ids([id], code, params)
        return items[0]

    def parse_trade(self, trade, market=None):
        #
        # fetchTrades(public)
        #
        #     [
        #         "0.032310",  # price
        #         "4.28169434",  # amount
        #         1541390792.763,  # timestamp
        #         "s",  # sell or buy
        #         "l",  # limit or market
        #         ""
        #     ]
        #
        # fetchOrderTrades(private)
        #
        #     {
        #         id: 'TIMIRG-WUNNE-RRJ6GT',  # injected from outside
        #         ordertxid: 'OQRPN2-LRHFY-HIFA7D',
        #         postxid: 'TKH2SE-M7IF5-CFI7LT',
        #         pair: 'USDCUSDT',
        #         time: 1586340086.457,
        #         type: 'sell',
        #         ordertype: 'market',
        #         price: '0.99860000',
        #         cost: '22.16892001',
        #         fee: '0.04433784',
        #         vol: '22.20000000',
        #         margin: '0.00000000',
        #         misc: ''
        #     }
        #
        timestamp = None
        side = None
        type = None
        priceString = None
        amountString = None
        id = None
        orderId = None
        fee = None
        symbol = None
        if isinstance(trade, list):
            timestamp = self.safe_timestamp(trade, 2)
            side = 'sell' if (trade[3] == 's') else 'buy'
            type = 'limit' if (trade[4] == 'l') else 'market'
            priceString = self.safe_string(trade, 0)
            amountString = self.safe_string(trade, 1)
            tradeLength = len(trade)
            if tradeLength > 6:
                id = self.safe_string(trade, 6)  # artificially added as per  #1794
        elif isinstance(trade, basestring):
            id = trade
        elif 'ordertxid' in trade:
            marketId = self.safe_string(trade, 'pair')
            foundMarket = self.find_market_by_altname_or_id(marketId)
            if foundMarket is not None:
                market = foundMarket
            elif marketId is not None:
                # delisted market ids go here
                market = self.get_delisted_market_by_id(marketId)
            orderId = self.safe_string(trade, 'ordertxid')
            id = self.safe_string_2(trade, 'id', 'postxid')
            timestamp = self.safe_timestamp(trade, 'time')
            side = self.safe_string(trade, 'type')
            type = self.safe_string(trade, 'ordertype')
            priceString = self.safe_string(trade, 'price')
            amountString = self.safe_string(trade, 'vol')
            if 'fee' in trade:
                currency = None
                if market is not None:
                    currency = market['quote']
                fee = {
                    'cost': self.safe_number(trade, 'fee'),
                    'currency': currency,
                }
        if market is not None:
            symbol = market['symbol']
        price = self.parse_number(priceString)
        amount = self.parse_number(amountString)
        cost = self.parse_number(Precise.string_mul(priceString, amountString))
        return {
            'id': id,
            'order': orderId,
            'info': trade,
            'timestamp': timestamp,
            'datetime': self.iso8601(timestamp),
            'symbol': symbol,
            'type': type,
            'side': side,
            'takerOrMaker': None,
            'price': price,
            'amount': amount,
            'cost': cost,
            'fee': fee,
        }

    def fetch_trades(self, symbol, since=None, limit=None, params={}):
        self.load_markets()
        market = self.market(symbol)
        id = market['id']
        request = {
            'pair': id,
        }
        # https://support.kraken.com/hc/en-us/articles/218198197-How-to-pull-all-trade-data-using-the-Kraken-REST-API
        # https://github.com/ccxt/ccxt/issues/5677
        if since is not None:
            # php does not format it properly
            # therefore we use string concatenation here
            request['since'] = since * 1e6
            request['since'] = str(since) + '000000'  # expected to be in nanoseconds
        # https://github.com/ccxt/ccxt/issues/5698
        if limit is not None and limit != 1000:
            fetchTradesWarning = self.safe_value(self.options, 'fetchTradesWarning', True)
            if fetchTradesWarning:
                raise ExchangeError(self.id + ' fetchTrades() cannot serve ' + str(limit) + " trades without breaking the pagination, see https://github.com/ccxt/ccxt/issues/5698 for more details. Set exchange.options['fetchTradesWarning'] to acknowledge self warning and silence it.")
        response = self.publicGetTrades(self.extend(request, params))
        #
        #     {
        #         "error": [],
        #         "result": {
        #             "XETHXXBT": [
        #                 ["0.032310","4.28169434",1541390792.763,"s","l",""]
        #             ],
        #             "last": "1541439421200678657"
        #         }
        #     }
        #
        result = response['result']
        trades = result[id]
        # trades is a sorted array: last(most recent trade) goes last
        length = len(trades)
        if length <= 0:
            return []
        lastTrade = trades[length - 1]
        lastTradeId = self.safe_string(result, 'last')
        lastTrade.append(lastTradeId)
        return self.parse_trades(trades, market, since, limit)

    def fetch_balance(self, params={}):
        self.load_markets()
        response = self.privatePostBalance(params)
        #
        #     {
        #         "error":[],
        #         "result":{
        #             "ZUSD":"58.8649",
        #             "KFEE":"4399.43",
        #             "XXBT":"0.0000034506",
        #         }
        #     }
        #
        balances = self.safe_value(response, 'result', {})
        result = {
            'info': response,
            'timestamp': None,
            'datetime': None,
        }
        currencyIds = list(balances.keys())
        for i in range(0, len(currencyIds)):
            currencyId = currencyIds[i]
            code = self.safe_currency_code(currencyId)
            account = self.account()
            account['total'] = self.safe_string(balances, currencyId)
            result[code] = account
        return self.parse_balance(result, False)

    def create_order(self, symbol, type, side, amount, price=None, params={}):
        self.load_markets()
        market = self.market(symbol)
        request = {
            'pair': market['id'],
            'type': side,
            'ordertype': type,
            'volume': self.amount_to_precision(symbol, amount),
        }
        clientOrderId = self.safe_string_2(params, 'userref', 'clientOrderId')
        params = self.omit(params, ['userref', 'clientOrderId'])
        if clientOrderId is not None:
            request['userref'] = clientOrderId
        #
        #     market
        #     limit(price = limit price)
        #     stop-loss(price = stop loss price)
        #     take-profit(price = take profit price)
        #     stop-loss-limit(price = stop loss trigger price, price2 = triggered limit price)
        #     take-profit-limit(price = take profit trigger price, price2 = triggered limit price)
        #     settle-position
        #
        if type == 'limit':
            request['price'] = self.price_to_precision(symbol, price)
        elif (type == 'stop-loss') or (type == 'take-profit'):
            stopPrice = self.safe_number_2(params, 'price', 'stopPrice', price)
            if stopPrice is None:
                raise ArgumentsRequired(self.id + ' createOrder() requires a price argument or a price/stopPrice parameter for a ' + type + ' order')
            else:
                request['price'] = self.price_to_precision(symbol, stopPrice)
        elif (type == 'stop-loss-limit') or (type == 'take-profit-limit'):
            stopPrice = self.safe_number_2(params, 'price', 'stopPrice')
            limitPrice = self.safe_number(params, 'price2')
            stopPriceDefined = (stopPrice is not None)
            limitPriceDefined = (limitPrice is not None)
            if stopPriceDefined and limitPriceDefined:
                request['price'] = self.price_to_precision(symbol, stopPrice)
                request['price2'] = self.price_to_precision(symbol, limitPrice)
            elif (price is None) or (not(stopPriceDefined or limitPriceDefined)):
                raise ArgumentsRequired(self.id + ' createOrder() requires a price argument and/or price/stopPrice/price2 parameters for a ' + type + ' order')
            else:
                if stopPriceDefined:
                    request['price'] = self.price_to_precision(symbol, stopPrice)
                    request['price2'] = self.price_to_precision(symbol, price)
                elif limitPriceDefined:
                    request['price'] = self.price_to_precision(symbol, price)
                    request['price2'] = self.price_to_precision(symbol, limitPrice)
        params = self.omit(params, ['price', 'stopPrice', 'price2'])
        response = self.privatePostAddOrder(self.extend(request, params))
        #
        #     {
        #         error: [],
        #         result: {
        #             descr: {order: 'buy 0.02100000 ETHUSDT @ limit 330.00'},
        #             txid: ['OEKVV2-IH52O-TPL6GZ']
        #         }
        #     }
        #
        result = self.safe_value(response, 'result')
        return self.parse_order(result)

    def find_market_by_altname_or_id(self, id):
        if id in self.marketsByAltname:
            return self.marketsByAltname[id]
        elif id in self.markets_by_id:
            return self.markets_by_id[id]
        return None

    def get_delisted_market_by_id(self, id):
        if id is None:
            return id
        market = self.safe_value(self.options['delistedMarketsById'], id)
        if market is not None:
            return market
        baseIdStart = 0
        baseIdEnd = 3
        quoteIdStart = 3
        quoteIdEnd = 6
        if len(id) == 8:
            baseIdEnd = 4
            quoteIdStart = 4
            quoteIdEnd = 8
        elif len(id) == 7:
            baseIdEnd = 4
            quoteIdStart = 4
            quoteIdEnd = 7
        baseId = id[baseIdStart:baseIdEnd]
        quoteId = id[quoteIdStart:quoteIdEnd]
        base = self.safe_currency_code(baseId)
        quote = self.safe_currency_code(quoteId)
        symbol = base + '/' + quote
        market = {
            'symbol': symbol,
            'base': base,
            'quote': quote,
            'baseId': baseId,
            'quoteId': quoteId,
        }
        self.options['delistedMarketsById'][id] = market
        return market

    def parse_order_status(self, status):
        statuses = {
            'pending': 'open',  # order pending book entry
            'open': 'open',
            'closed': 'closed',
            'canceled': 'canceled',
            'expired': 'expired',
        }
        return self.safe_string(statuses, status, status)

    def parse_order(self, order, market=None):
        #
        # createOrder
        #
        #     {
        #         descr: {order: 'buy 0.02100000 ETHUSDT @ limit 330.00'},
        #         txid: ['OEKVV2-IH52O-TPL6GZ']
        #     }
        #
        description = self.safe_value(order, 'descr', {})
        orderDescription = self.safe_string(description, 'order')
        side = None
        type = None
        marketId = None
        price = None
        amount = None
        if orderDescription is not None:
            parts = orderDescription.split(' ')
            side = self.safe_string(parts, 0)
            amount = self.safe_number(parts, 1)
            marketId = self.safe_string(parts, 2)
            type = self.safe_string(parts, 4)
            price = self.safe_number(parts, 5)
        side = self.safe_string(description, 'type', side)
        type = self.safe_string(description, 'ordertype', type)
        marketId = self.safe_string(description, 'pair', marketId)
        foundMarket = self.find_market_by_altname_or_id(marketId)
        symbol = None
        if foundMarket is not None:
            market = foundMarket
        elif marketId is not None:
            # delisted market ids go here
            market = self.get_delisted_market_by_id(marketId)
        timestamp = self.safe_timestamp(order, 'opentm')
        amount = self.safe_number(order, 'vol', amount)
        filled = self.safe_number(order, 'vol_exec')
        fee = None
        cost = self.safe_number(order, 'cost')
        price = self.safe_number(description, 'price', price)
        if (price is None) or (price == 0.0):
            price = self.safe_number(description, 'price2')
        if (price is None) or (price == 0.0):
            price = self.safe_number(order, 'price', price)
        average = self.safe_number(order, 'price')
        if market is not None:
            symbol = market['symbol']
            if 'fee' in order:
                flags = order['oflags']
                feeCost = self.safe_number(order, 'fee')
                fee = {
                    'cost': feeCost,
                    'rate': None,
                }
                if flags.find('fciq') >= 0:
                    fee['currency'] = market['quote']
                elif flags.find('fcib') >= 0:
                    fee['currency'] = market['base']
        status = self.parse_order_status(self.safe_string(order, 'status'))
        id = self.safe_string(order, 'id')
        if id is None:
            txid = self.safe_value(order, 'txid')
            id = self.safe_string(txid, 0)
        clientOrderId = self.safe_string(order, 'userref')
        rawTrades = self.safe_value(order, 'trades')
        trades = None
        if rawTrades is not None:
            trades = self.parse_trades(rawTrades, market, None, None, {'order': id})
        stopPrice = self.safe_number(order, 'stopprice')
        return self.safe_order({
            'id': id,
            'clientOrderId': clientOrderId,
            'info': order,
            'timestamp': timestamp,
            'datetime': self.iso8601(timestamp),
            'lastTradeTimestamp': None,
            'status': status,
            'symbol': symbol,
            'type': type,
            'timeInForce': None,
            'postOnly': None,
            'side': side,
            'price': price,
            'stopPrice': stopPrice,
            'cost': cost,
            'amount': amount,
            'filled': filled,
            'average': average,
            'remaining': None,
            'fee': fee,
            'trades': trades,
        })

    def fetch_order(self, id, symbol=None, params={}):
        self.load_markets()
        clientOrderId = self.safe_value_2(params, 'userref', 'clientOrderId')
        request = {
            'trades': True,  # whether or not to include trades in output(optional, default False)
            # 'txid': id,  # do not comma separate a list of ids - use fetchOrdersByIds instead
            # 'userref': 'optional',  # restrict results to given user reference id(optional)
        }
        query = params
        if clientOrderId is not None:
            request['userref'] = clientOrderId
            query = self.omit(params, ['userref', 'clientOrderId'])
        else:
            request['txid'] = id
        response = self.privatePostQueryOrders(self.extend(request, query))
        #
        #     {
        #         "error":[],
        #         "result":{
        #             "OTLAS3-RRHUF-NDWH5A":{
        #                 "refid":null,
        #                 "userref":null,
        #                 "status":"closed",
        #                 "reason":null,
        #                 "opentm":1586822919.3342,
        #                 "closetm":1586822919.365,
        #                 "starttm":0,
        #                 "expiretm":0,
        #                 "descr":{
        #                     "pair":"XBTUSDT",
        #                     "type":"sell",
        #                     "ordertype":"market",
        #                     "price":"0",
        #                     "price2":"0",
        #                     "leverage":"none",
        #                     "order":"sell 0.21804000 XBTUSDT @ market",
        #                     "close":""
        #                 },
        #                 "vol":"0.21804000",
        #                 "vol_exec":"0.21804000",
        #                 "cost":"1493.9",
        #                 "fee":"3.8",
        #                 "price":"6851.5",
        #                 "stopprice":"0.00000",
        #                 "limitprice":"0.00000",
        #                 "misc":"",
        #                 "oflags":"fciq",
        #                 "trades":["TT5UC3-GOIRW-6AZZ6R"]
        #             }
        #         }
        #     }
        #
        result = self.safe_value(response, 'result', [])
        if not (id in result):
            raise OrderNotFound(self.id + ' fetchOrder() could not find order id ' + id)
        order = self.parse_order(self.extend({'id': id}, result[id]))
        return self.extend({'info': response}, order)

    def fetch_order_trades(self, id, symbol=None, since=None, limit=None, params={}):
        orderTrades = self.safe_value(params, 'trades')
        tradeIds = []
        if orderTrades is None:
            raise ArgumentsRequired(self.id + " fetchOrderTrades() requires a unified order structure in the params argument or a 'trades' param(an array of trade id strings)")
        else:
            for i in range(0, len(orderTrades)):
                orderTrade = orderTrades[i]
                if isinstance(orderTrade, basestring):
                    tradeIds.append(orderTrade)
                else:
                    tradeIds.append(orderTrade['id'])
        self.load_markets()
        options = self.safe_value(self.options, 'fetchOrderTrades', {})
        batchSize = self.safe_integer(options, 'batchSize', 20)
        numBatches = int(tradeIds / batchSize)
        numBatches = self.sum(numBatches, 1)
        numTradeIds = len(tradeIds)
        result = []
        for j in range(0, numBatches):
            requestIds = []
            for k in range(0, batchSize):
                index = self.sum(j * batchSize, k)
                if index < numTradeIds:
                    requestIds.append(tradeIds[index])
            request = {
                'txid': ','.join(requestIds),
            }
            response = self.privatePostQueryTrades(request)
            #
            #     {
            #         error: [],
            #         result: {
            #             'TIMIRG-WUNNE-RRJ6GT': {
            #                 ordertxid: 'OQRPN2-LRHFY-HIFA7D',
            #                 postxid: 'TKH2SE-M7IF5-CFI7LT',
            #                 pair: 'USDCUSDT',
            #                 time: 1586340086.457,
            #                 type: 'sell',
            #                 ordertype: 'market',
            #                 price: '0.99860000',
            #                 cost: '22.16892001',
            #                 fee: '0.04433784',
            #                 vol: '22.20000000',
            #                 margin: '0.00000000',
            #                 misc: ''
            #             }
            #         }
            #     }
            #
            rawTrades = self.safe_value(response, 'result')
            ids = list(rawTrades.keys())
            for i in range(0, len(ids)):
                rawTrades[ids[i]]['id'] = ids[i]
            trades = self.parse_trades(rawTrades, None, since, limit)
            tradesFilteredBySymbol = self.filter_by_symbol(trades, symbol)
            result = self.array_concat(result, tradesFilteredBySymbol)
        return result

    def fetch_orders_by_ids(self, ids, symbol=None, params={}):
        self.load_markets()
        response = self.privatePostQueryOrders(self.extend({
            'trades': True,  # whether or not to include trades in output(optional, default False)
            'txid': ','.join(ids),  # comma delimited list of transaction ids to query info about(20 maximum)
        }, params))
        result = self.safe_value(response, 'result', {})
        orders = []
        orderIds = list(result.keys())
        for i in range(0, len(orderIds)):
            id = orderIds[i]
            item = result[id]
            order = self.parse_order(self.extend({'id': id}, item))
            orders.append(order)
        return orders

    def fetch_my_trades(self, symbol=None, since=None, limit=None, params={}):
        self.load_markets()
        request = {
            # 'type': 'all',  # any position, closed position, closing position, no position
            # 'trades': False,  # whether or not to include trades related to position in output
            # 'start': 1234567890,  # starting unix timestamp or trade tx id of results(exclusive)
            # 'end': 1234567890,  # ending unix timestamp or trade tx id of results(inclusive)
            # 'ofs' = result offset
        }
        if since is not None:
            request['start'] = int(since / 1000)
        response = self.privatePostTradesHistory(self.extend(request, params))
        #
        #     {
        #         "error": [],
        #         "result": {
        #             "trades": {
        #                 "GJ3NYQ-XJRTF-THZABF": {
        #                     "ordertxid": "TKH2SE-ZIF5E-CFI7LT",
        #                     "postxid": "OEN3VX-M7IF5-JNBJAM",
        #                     "pair": "XICNXETH",
        #                     "time": 1527213229.4491,
        #                     "type": "sell",
        #                     "ordertype": "limit",
        #                     "price": "0.001612",
        #                     "cost": "0.025792",
        #                     "fee": "0.000026",
        #                     "vol": "16.00000000",
        #                     "margin": "0.000000",
        #                     "misc": ""
        #                 },
        #                 ...
        #             },
        #             "count": 9760,
        #         },
        #     }
        #
        trades = response['result']['trades']
        ids = list(trades.keys())
        for i in range(0, len(ids)):
            trades[ids[i]]['id'] = ids[i]
        market = None
        if symbol is not None:
            market = self.market(symbol)
        return self.parse_trades(trades, market, since, limit)

    def cancel_order(self, id, symbol=None, params={}):
        self.load_markets()
        response = None
        try:
            response = self.privatePostCancelOrder(self.extend({
                'txid': id,
            }, params))
        except Exception as e:
            if self.last_http_response:
                if self.last_http_response.find('EOrder:Unknown order') >= 0:
                    raise OrderNotFound(self.id + ' cancelOrder() error ' + self.last_http_response)
            raise e
        return response

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

    def fetch_open_orders(self, symbol=None, since=None, limit=None, params={}):
        self.load_markets()
        request = {}
        if since is not None:
            request['start'] = int(since / 1000)
        response = self.privatePostOpenOrders(self.extend(request, params))
        market = None
        if symbol is not None:
            market = self.market(symbol)
        result = self.safe_value(response, 'result', {})
        orders = self.safe_value(result, 'open', [])
        return self.parse_orders(orders, market, since, limit)

    def fetch_closed_orders(self, symbol=None, since=None, limit=None, params={}):
        self.load_markets()
        request = {}
        if since is not None:
            request['start'] = int(since / 1000)
        response = self.privatePostClosedOrders(self.extend(request, params))
        #
        #     {
        #         "error":[],
        #         "result":{
        #             "closed":{
        #                 "OETZYO-UL524-QJMXCT":{
        #                     "refid":null,
        #                     "userref":null,
        #                     "status":"canceled",
        #                     "reason":"User requested",
        #                     "opentm":1601489313.3898,
        #                     "closetm":1601489346.5507,
        #                     "starttm":0,
        #                     "expiretm":0,
        #                     "descr":{
        #                         "pair":"ETHUSDT",
        #                         "type":"buy",
        #                         "ordertype":"limit",
        #                         "price":"330.00",
        #                         "price2":"0",
        #                         "leverage":"none",
        #                         "order":"buy 0.02100000 ETHUSDT @ limit 330.00",
        #                         "close":""
        #                     },
        #                     "vol":"0.02100000",
        #                     "vol_exec":"0.00000000",
        #                     "cost":"0.00000",
        #                     "fee":"0.00000",
        #                     "price":"0.00000",
        #                     "stopprice":"0.00000",
        #                     "limitprice":"0.00000",
        #                     "misc":"",
        #                     "oflags":"fciq"
        #                 },
        #             },
        #             "count":16
        #         }
        #     }
        #
        market = None
        if symbol is not None:
            market = self.market(symbol)
        result = self.safe_value(response, 'result', {})
        orders = self.safe_value(result, 'closed', [])
        return self.parse_orders(orders, market, since, limit)

    def fetch_deposit_methods(self, code, params={}):
        self.load_markets()
        currency = self.currency(code)
        request = {
            'asset': currency['id'],
        }
        response = self.privatePostDepositMethods(self.extend(request, params))
        return self.safe_value(response, 'result')

    def parse_transaction_status(self, status):
        # IFEX transaction states
        statuses = {
            'Initial': 'pending',
            'Pending': 'pending',
            'Success': 'ok',
            'Settled': 'pending',
            'Failure': 'failed',
            'Partial': 'ok',
        }
        return self.safe_string(statuses, status, status)

    def parse_transaction(self, transaction, currency=None):
        #
        # fetchDeposits
        #
        #     {method: "Ether(Hex)",
        #       aclass: "currency",
        #        asset: "XETH",
        #        refid: "Q2CANKL-LBFVEE-U4Y2WQ",
        #         txid: "0x57fd704dab1a73c20e24c8696099b695d596924b401b261513cfdab23…",
        #         info: "0x615f9ba7a9575b0ab4d571b2b36b1b324bd83290",
        #       amount: "7.9999257900",
        #          fee: "0.0000000000",
        #         time:  1529223212,
        #       status: "Success"                                                       }
        #
        # fetchWithdrawals
        #
        #     {method: "Ether",
        #       aclass: "currency",
        #        asset: "XETH",
        #        refid: "A2BF34S-O7LBNQ-UE4Y4O",
        #         txid: "0x288b83c6b0904d8400ef44e1c9e2187b5c8f7ea3d838222d53f701a15b5c274d",
        #         info: "0x7cb275a5e07ba943fee972e165d80daa67cb2dd0",
        #       amount: "9.9950000000",
        #          fee: "0.0050000000",
        #         time:  1530481750,
        #       status: "Success"                                                             }
        #
        id = self.safe_string(transaction, 'refid')
        txid = self.safe_string(transaction, 'txid')
        timestamp = self.safe_timestamp(transaction, 'time')
        currencyId = self.safe_string(transaction, 'asset')
        code = self.safe_currency_code(currencyId, currency)
        address = self.safe_string(transaction, 'info')
        amount = self.safe_number(transaction, 'amount')
        status = self.parse_transaction_status(self.safe_string(transaction, 'status'))
        type = self.safe_string(transaction, 'type')  # injected from the outside
        feeCost = self.safe_number(transaction, 'fee')
        if feeCost is None:
            if type == 'deposit':
                feeCost = 0
        return {
            'info': transaction,
            'id': id,
            'currency': code,
            'amount': amount,
            'address': address,
            'tag': None,
            'status': status,
            'type': type,
            'updated': None,
            'txid': txid,
            'timestamp': timestamp,
            'datetime': self.iso8601(timestamp),
            'fee': {
                'currency': code,
                'cost': feeCost,
            },
        }

    def parse_transactions_by_type(self, type, transactions, code=None, since=None, limit=None):
        result = []
        for i in range(0, len(transactions)):
            transaction = self.parse_transaction(self.extend({
                'type': type,
            }, transactions[i]))
            result.append(transaction)
        return self.filter_by_currency_since_limit(result, code, since, limit)

    def fetch_deposits(self, code=None, since=None, limit=None, params={}):
        # https://www.kraken.com/en-us/help/api#deposit-status
        if code is None:
            raise ArgumentsRequired(self.id + ' fetchDeposits() requires a currency code argument')
        self.load_markets()
        currency = self.currency(code)
        request = {
            'asset': currency['id'],
        }
        response = self.privatePostDepositStatus(self.extend(request, params))
        #
        #     { error: [],
        #       result: [{method: "Ether(Hex)",
        #                   aclass: "currency",
        #                    asset: "XETH",
        #                    refid: "Q2CANKL-LBFVEE-U4Y2WQ",
        #                     txid: "0x57fd704dab1a73c20e24c8696099b695d596924b401b261513cfdab23…",
        #                     info: "0x615f9ba7a9575b0ab4d571b2b36b1b324bd83290",
        #                   amount: "7.9999257900",
        #                      fee: "0.0000000000",
        #                     time:  1529223212,
        #                   status: "Success"                                                       }]}
        #
        return self.parse_transactions_by_type('deposit', response['result'], code, since, limit)

    def fetch_time(self, params={}):
        # https://www.kraken.com/en-us/features/api#get-server-time
        response = self.publicGetTime(params)
        #
        #    {
        #        "error": [],
        #        "result": {
        #            "unixtime": 1591502873,
        #            "rfc1123": "Sun,  7 Jun 20 04:07:53 +0000"
        #        }
        #    }
        #
        result = self.safe_value(response, 'result', {})
        return self.safe_timestamp(result, 'unixtime')

    def fetch_withdrawals(self, code=None, since=None, limit=None, params={}):
        # https://www.kraken.com/en-us/help/api#withdraw-status
        if code is None:
            raise ArgumentsRequired(self.id + ' fetchWithdrawals() requires a currency code argument')
        self.load_markets()
        currency = self.currency(code)
        request = {
            'asset': currency['id'],
        }
        response = self.privatePostWithdrawStatus(self.extend(request, params))
        #
        #     { error: [],
        #       result: [{method: "Ether",
        #                   aclass: "currency",
        #                    asset: "XETH",
        #                    refid: "A2BF34S-O7LBNQ-UE4Y4O",
        #                     txid: "0x298c83c7b0904d8400ef43e1c9e2287b518f7ea3d838822d53f704a1565c274d",
        #                     info: "0x7cb275a5e07ba943fee972e165d80daa67cb2dd0",
        #                   amount: "9.9950000000",
        #                      fee: "0.0050000000",
        #                     time:  1530481750,
        #                   status: "Success"                                                             }]}
        #
        return self.parse_transactions_by_type('withdrawal', response['result'], code, since, limit)

    def create_deposit_address(self, code, params={}):
        request = {
            'new': 'true',
        }
        response = self.fetch_deposit_address(code, self.extend(request, params))
        address = self.safe_string(response, 'address')
        self.check_address(address)
        return {
            'currency': code,
            'address': address,
            'info': response,
        }

    def fetch_deposit_address(self, code, params={}):
        self.load_markets()
        currency = self.currency(code)
        # eslint-disable-next-line quotes
        method = self.safe_string(params, 'method')
        if method is None:
            if self.options['cacheDepositMethodsOnFetchDepositAddress']:
                # cache depositMethods
                if not (code in self.options['depositMethods']):
                    self.options['depositMethods'][code] = self.fetch_deposit_methods(code)
                method = self.options['depositMethods'][code][0]['method']
            else:
                raise ArgumentsRequired(self.id + ' fetchDepositAddress() requires an extra `method` parameter. Use fetchDepositMethods("' + code + '") to get a list of available deposit methods or enable the exchange property .options["cacheDepositMethodsOnFetchDepositAddress"] = True')
        request = {
            'asset': currency['id'],
            'method': method,
        }
        response = self.privatePostDepositAddresses(self.extend(request, params))  # overwrite methods
        result = response['result']
        numResults = len(result)
        if numResults < 1:
            raise InvalidAddress(self.id + ' privatePostDepositAddresses() returned no addresses')
        address = self.safe_string(result[0], 'address')
        tag = self.safe_string_2(result[0], 'tag', 'memo')
        self.check_address(address)
        return {
            'currency': code,
            'address': address,
            'tag': tag,
            'info': response,
        }

    def withdraw(self, code, amount, address, tag=None, params={}):
        self.check_address(address)
        if 'key' in params:
            self.load_markets()
            currency = self.currency(code)
            request = {
                'asset': currency['id'],
                'amount': amount,
                # 'address': address,  # they don't allow withdrawals to direct addresses
            }
            response = self.privatePostWithdraw(self.extend(request, params))
            return {
                'info': response,
                'id': response['result'],
            }
        raise ExchangeError(self.id + " withdraw() requires a 'key' parameter(withdrawal key name, as set up on your account)")

    def fetch_positions(self, symbols=None, params={}):
        self.load_markets()
        request = {
            # 'txid': 'comma delimited list of transaction ids to restrict output to',
            # 'docalcs': False,  # whether or not to include profit/loss calculations
            # 'consolidation': 'market',  # what to consolidate the positions data around, market will consolidate positions based on market pair
        }
        response = self.privatePostOpenPositions(self.extend(request, params))
        #
        # no consolidation
        #
        #     {
        #         error: [],
        #         result: {
        #             'TGUFMY-FLESJ-VYIX3J': {
        #                 ordertxid: "O3LRNU-ZKDG5-XNCDFR",
        #                 posstatus: "open",
        #                 pair: "ETHUSDT",
        #                 time:  1611557231.4584,
        #                 type: "buy",
        #                 ordertype: "market",
        #                 cost: "28.49800",
        #                 fee: "0.07979",
        #                 vol: "0.02000000",
        #                 vol_closed: "0.00000000",
        #                 margin: "14.24900",
        #                 terms: "0.0200% per 4 hours",
        #                 rollovertm: "1611571631",
        #                 misc: "",
        #                 oflags: ""
        #             }
        #         }
        #     }
        #
        # consolidation by market
        #
        #     {
        #         error: [],
        #         result: [
        #             {
        #                 pair: "ETHUSDT",
        #                 positions: "1",
        #                 type: "buy",
        #                 leverage: "2.00000",
        #                 cost: "28.49800",
        #                 fee: "0.07979",
        #                 vol: "0.02000000",
        #                 vol_closed: "0.00000000",
        #                 margin: "14.24900"
        #             }
        #         ]
        #     }
        #
        result = self.safe_value(response, 'result')
        # todo unify parsePosition/parsePositions
        return result

    def sign(self, path, api='public', method='GET', params={}, headers=None, body=None):
        url = '/' + self.version + '/' + api + '/' + path
        if api == 'public':
            if params:
                url += '?' + self.urlencode(params)
        elif api == 'private':
            self.check_required_credentials()
            nonce = str(self.nonce())
            body = self.urlencode(self.extend({'nonce': nonce}, params))
            auth = self.encode(nonce + body)
            hash = self.hash(auth, 'sha256', 'binary')
            binary = self.encode(url)
            binhash = self.binary_concat(binary, hash)
            secret = self.base64_to_binary(self.secret)
            signature = self.hmac(binhash, secret, hashlib.sha512, 'base64')
            headers = {
                'API-Key': self.apiKey,
                'API-Sign': signature,
                'Content-Type': 'application/x-www-form-urlencoded',
            }
        else:
            url = '/' + path
        url = self.urls['api'][api] + url
        return {'url': url, 'method': method, 'body': body, 'headers': headers}

    def nonce(self):
        return self.milliseconds()

    def handle_errors(self, code, reason, url, method, headers, body, response, requestHeaders, requestBody):
        if code == 520:
            raise ExchangeNotAvailable(self.id + ' ' + str(code) + ' ' + reason)
        # todo: rewrite self for "broad" exceptions matching
        if body.find('Invalid order') >= 0:
            raise InvalidOrder(self.id + ' ' + body)
        if body.find('Invalid nonce') >= 0:
            raise InvalidNonce(self.id + ' ' + body)
        if body.find('Insufficient funds') >= 0:
            raise InsufficientFunds(self.id + ' ' + body)
        if body.find('Cancel pending') >= 0:
            raise CancelPending(self.id + ' ' + body)
        if body.find('Invalid arguments:volume') >= 0:
            raise InvalidOrder(self.id + ' ' + body)
        if body.find('Rate limit exceeded') >= 0:
            raise RateLimitExceeded(self.id + ' ' + body)
        if response is None:
            return
        if body[0] == '{':
            if not isinstance(response, basestring):
                if 'error' in response:
                    numErrors = len(response['error'])
                    if numErrors:
                        message = self.id + ' ' + body
                        for i in range(0, len(response['error'])):
                            error = response['error'][i]
                            self.throw_exactly_matched_exception(self.exceptions, error, message)
                        raise ExchangeError(message)
