# -*- coding: utf-8 -*-

# PLEASE DO NOT EDIT THIS FILE, IT IS GENERATED AND WILL BE OVERWRITTEN:
# https://github.com/ccxt/ccxt/blob/master/CONTRIBUTING.md#how-to-contribute-code

from ccxt.base.exchange import Exchange
from ccxt.abstract.kraken import ImplicitAPI
import hashlib
from ccxt.base.types import Balances, Currencies, Currency, IndexType, Int, Market, Num, Order, OrderBook, OrderSide, OrderType, Str, Strings, Ticker, Tickers, Trade, TradingFeeInterface, Transaction, TransferEntry
from typing import List
from ccxt.base.errors import ExchangeError
from ccxt.base.errors import AuthenticationError
from ccxt.base.errors import PermissionDenied
from ccxt.base.errors import AccountSuspended
from ccxt.base.errors import ArgumentsRequired
from ccxt.base.errors import BadRequest
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 NotSupported
from ccxt.base.errors import DDoSProtection
from ccxt.base.errors import RateLimitExceeded
from ccxt.base.errors import ExchangeNotAvailable
from ccxt.base.errors import OnMaintenance
from ccxt.base.errors import InvalidNonce
from ccxt.base.decimal_to_precision import TRUNCATE
from ccxt.base.decimal_to_precision import TICK_SIZE
from ccxt.base.precise import Precise


class kraken(Exchange, ImplicitAPI):

    def describe(self):
        return self.deep_extend(super(kraken, self).describe(), {
            'id': 'kraken',
            'name': 'Kraken',
            'countries': ['US'],
            'version': '0',
            # rate-limits: https://support.kraken.com/hc/en-us/articles/206548367-What-are-the-API-rate-limits-#1
            # for public: 1 req/s
            # for private: every second 0.33 weight added to your allowed capacity(some private endpoints need 1 weight, some need 2)
            'rateLimit': 1000,
            'certified': False,
            'pro': True,
            'has': {
                'CORS': None,
                'spot': True,
                'margin': True,
                'swap': False,
                'future': False,
                'option': False,
                'addMargin': False,
                'cancelAllOrders': True,
                'cancelOrder': True,
                'cancelOrders': True,
                'createDepositAddress': True,
                'createOrder': True,
                'createStopLimitOrder': True,
                'createStopMarketOrder': True,
                'createStopOrder': True,
                'createTrailingAmountOrder': True,
                'editOrder': True,
                'fetchBalance': True,
                'fetchBorrowInterest': False,
                'fetchBorrowRateHistories': False,
                'fetchBorrowRateHistory': False,
                'fetchClosedOrders': True,
                'fetchCrossBorrowRate': False,
                'fetchCrossBorrowRates': False,
                'fetchCurrencies': True,
                'fetchDepositAddress': True,
                'fetchDeposits': True,
                'fetchFundingHistory': False,
                'fetchFundingRate': False,
                'fetchFundingRateHistory': False,
                'fetchFundingRates': False,
                'fetchIndexOHLCV': False,
                'fetchIsolatedBorrowRate': False,
                'fetchIsolatedBorrowRates': False,
                'fetchLedger': True,
                'fetchLedgerEntry': True,
                'fetchLeverageTiers': False,
                'fetchMarkets': True,
                'fetchMarkOHLCV': False,
                'fetchMyTrades': True,
                'fetchOHLCV': True,
                'fetchOpenInterestHistory': False,
                'fetchOpenOrders': True,
                'fetchOrder': True,
                'fetchOrderBook': True,
                'fetchOrderTrades': 'emulated',
                'fetchPositions': True,
                'fetchPremiumIndexOHLCV': False,
                'fetchTicker': True,
                'fetchTickers': True,
                'fetchTime': True,
                'fetchTrades': True,
                'fetchTradingFee': True,
                'fetchTradingFees': False,
                'fetchWithdrawals': True,
                'setLeverage': False,
                'setMarginMode': False,  # Kraken only supports cross margin
                'transfer': True,
                'withdraw': True,
            },
            '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://docs.kraken.com/rest/',
                'fees': 'https://www.kraken.com/en-us/features/fee-schedule',
            },
            'fees': {
                'trading': {
                    'tierBased': True,
                    'percentage': True,
                    'taker': self.parse_number('0.0026'),
                    'maker': self.parse_number('0.0016'),
                    'tiers': {
                        'taker': [
                            [self.parse_number('0'), self.parse_number('0.0026')],
                            [self.parse_number('50000'), self.parse_number('0.0024')],
                            [self.parse_number('100000'), self.parse_number('0.0022')],
                            [self.parse_number('250000'), self.parse_number('0.0020')],
                            [self.parse_number('500000'), self.parse_number('0.0018')],
                            [self.parse_number('1000000'), self.parse_number('0.0016')],
                            [self.parse_number('2500000'), self.parse_number('0.0014')],
                            [self.parse_number('5000000'), self.parse_number('0.0012')],
                            [self.parse_number('10000000'), self.parse_number('0.0001')],
                        ],
                        'maker': [
                            [self.parse_number('0'), self.parse_number('0.0016')],
                            [self.parse_number('50000'), self.parse_number('0.0014')],
                            [self.parse_number('100000'), self.parse_number('0.0012')],
                            [self.parse_number('250000'), self.parse_number('0.0010')],
                            [self.parse_number('500000'), self.parse_number('0.0008')],
                            [self.parse_number('1000000'), self.parse_number('0.0006')],
                            [self.parse_number('2500000'), self.parse_number('0.0004')],
                            [self.parse_number('5000000'), self.parse_number('0.0002')],
                            [self.parse_number('10000000'), self.parse_number('0.0')],
                        ],
                    },
                },
            },
            'handleContentTypeApplicationZip': True,
            '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': {
                        # rate-limits explained in comment in the top of self file
                        'Assets': 1,
                        'AssetPairs': 1,
                        'Depth': 1,
                        'OHLC': 1,
                        'Spread': 1,
                        'SystemStatus': 1,
                        'Ticker': 1,
                        'Time': 1,
                        'Trades': 1,
                    },
                },
                'private': {
                    'post': {
                        'AddOrder': 0,
                        'AddOrderBatch': 0,
                        'AddExport': 3,
                        'Balance': 3,
                        'CancelAll': 3,
                        'CancelAllOrdersAfter': 3,
                        'CancelOrder': 0,
                        'CancelOrderBatch': 0,
                        'ClosedOrders': 3,
                        'DepositAddresses': 3,
                        'DepositMethods': 3,
                        'DepositStatus': 3,
                        'EditOrder': 0,
                        'ExportStatus': 3,
                        'GetWebSocketsToken': 3,
                        'Ledgers': 6,
                        'OpenOrders': 3,
                        'OpenPositions': 3,
                        'QueryLedgers': 3,
                        'QueryOrders': 3,
                        'QueryTrades': 3,
                        'RetrieveExport': 3,
                        'RemoveExport': 3,
                        'BalanceEx': 3,
                        'TradeBalance': 3,
                        'TradesHistory': 6,
                        'TradeVolume': 3,
                        'Withdraw': 3,
                        'WithdrawCancel': 3,
                        'WithdrawInfo': 3,
                        'WithdrawMethods': 3,
                        'WithdrawAddresses': 3,
                        'WithdrawStatus': 3,
                        'WalletTransfer': 3,
                        # sub accounts
                        'CreateSubaccount': 3,
                        'AccountTransfer': 3,
                        # earn
                        'Earn/Allocate': 3,
                        'Earn/Deallocate': 3,
                        'Earn/AllocateStatus': 3,
                        'Earn/DeallocateStatus': 3,
                        'Earn/Strategies': 3,
                        'Earn/Allocations': 3,
                    },
                },
            },
            'commonCurrencies': {
                'LUNA': 'LUNC',
                'LUNA2': 'LUNA',
                'REPV2': 'REP',
                'REP': 'REPV1',
                'UST': 'USTC',
                'XBT': 'BTC',
                'XBT.M': 'BTC.M',  # https://support.kraken.com/hc/en-us/articles/360039879471-What-is-Asset-S-and-Asset-M-
                'XDG': 'DOGE',
            },
            'options': {
                'marketsByAltname': {},
                'delistedMarketsById': {},
                # cannot withdraw/deposit these
                'inactiveCurrencies': ['CAD', 'USD', 'JPY', 'GBP'],
                'networks': {
                    'ETH': 'ERC20',
                    'TRX': 'TRC20',
                },
                'depositMethods': {
                    '1INCH': '1inch(1INCH)',
                    'AAVE': 'Aave',
                    'ADA': 'ADA',
                    'ALGO': 'Algorand',
                    'ANKR': 'ANKR(ANKR)',
                    'ANT': 'Aragon(ANT)',
                    'ATOM': 'Cosmos',
                    'AXS': 'Axie Infinity Shards(AXS)',
                    'BADGER': 'Bager DAO(BADGER)',
                    'BAL': 'Balancer(BAL)',
                    'BAND': 'Band Protocol(BAND)',
                    'BAT': 'BAT',
                    'BCH': 'Bitcoin Cash',
                    'BNC': 'Bifrost(BNC)',
                    'BNT': 'Bancor(BNT)',
                    'BTC': 'Bitcoin',
                    'CHZ': 'Chiliz(CHZ)',
                    'COMP': 'Compound(COMP)',
                    'CQT': '\tCovalent Query Token(CQT)',
                    'CRV': 'Curve DAO Token(CRV)',
                    'CTSI': 'Cartesi(CTSI)',
                    'DAI': 'Dai',
                    'DASH': 'Dash',
                    'DOGE': 'Dogecoin',
                    'DOT': 'Polkadot',
                    'DYDX': 'dYdX(DYDX)',
                    'ENJ': 'Enjin Coin(ENJ)',
                    'EOS': 'EOS',
                    'ETC': 'Ether Classic(Hex)',
                    'ETH': 'Ether(Hex)',
                    'EWT': 'Energy Web Token',
                    'FEE': 'Kraken Fee Credit',
                    'FIL': 'Filecoin',
                    'FLOW': 'Flow',
                    'GHST': 'Aavegotchi(GHST)',
                    'GNO': 'GNO',
                    'GRT': 'GRT',
                    'ICX': 'Icon',
                    'INJ': 'Injective Protocol(INJ)',
                    'KAR': 'Karura(KAR)',
                    'KAVA': 'Kava',
                    'KEEP': 'Keep Token(KEEP)',
                    'KNC': 'Kyber Network(KNC)',
                    'KSM': 'Kusama',
                    'LINK': 'Link',
                    'LPT': 'Livepeer Token(LPT)',
                    'LRC': 'Loopring(LRC)',
                    'LSK': 'Lisk',
                    'LTC': 'Litecoin',
                    'MANA': 'MANA',
                    'MATIC': 'Polygon(MATIC)',
                    'MINA': 'Mina',  # inspected from webui
                    'MIR': 'Mirror Protocol(MIR)',
                    'MKR': 'Maker(MKR)',
                    'MLN': 'MLN',
                    'MOVR': 'Moonriver(MOVR)',
                    'NANO': 'NANO',
                    'OCEAN': 'OCEAN',
                    'OGN': 'Origin Protocol(OGN)',
                    'OMG': 'OMG',
                    'OXT': 'Orchid(OXT)',
                    'OXY': 'Oxygen(OXY)',
                    'PAXG': 'PAX(Gold)',
                    'PERP': 'Perpetual Protocol(PERP)',
                    'PHA': 'Phala(PHA)',
                    'QTUM': 'QTUM',
                    'RARI': 'Rarible(RARI)',
                    'RAY': 'Raydium(RAY)',
                    'REN': 'Ren Protocol(REN)',
                    'REP': 'REPv2',
                    'REPV1': 'REP',
                    'SAND': 'The Sandbox(SAND)',
                    'SC': 'Siacoin',
                    'SDN': 'Shiden(SDN)',
                    'SOL': 'Solana',  # their deposit method api doesn't work for SOL - was guessed
                    'SNX': 'Synthetix  Network(SNX)',
                    'SRM': 'Serum',  # inspected from webui
                    'STORJ': 'Storj(STORJ)',
                    'SUSHI': 'Sushiswap(SUSHI)',
                    'TBTC': 'tBTC',
                    'TRX': 'Tron',
                    'UNI': 'UNI',
                    'USDC': 'USDC',
                    'USDT': 'Tether USD(ERC20)',
                    'USDT-TRC20': 'Tether USD(TRC20)',
                    'WAVES': 'Waves',
                    'WBTC': 'Wrapped Bitcoin(WBTC)',
                    'XLM': 'Stellar XLM',
                    'XMR': 'Monero',
                    'XRP': 'Ripple XRP',
                    'XTZ': 'XTZ',
                    'YFI': 'YFI',
                    'ZEC': 'Zcash(Transparent)',
                    'ZRX': '0x(ZRX)',
                },
                'withdrawMethods': {  # keeping it here because deposit and withdraw return different networks codes
                    'Lightning': 'Lightning',
                    'Bitcoin': 'BTC',
                    'Ripple': 'XRP',
                    'Litecoin': 'LTC',
                    'Dogecoin': 'DOGE',
                    'Stellar': 'XLM',
                    'Ethereum': 'ERC20',
                    'Arbitrum One': 'Arbitrum',
                    'Polygon': 'MATIC',
                    'Arbitrum Nova': 'Arbitrum',
                    'Optimism': 'Optimism',
                    'zkSync Era': 'zkSync',
                    'Ethereum Classic': 'ETC',
                    'Zcash': 'ZEC',
                    'Monero': 'XMR',
                    'Tron': 'TRC20',
                    'Solana': 'SOL',
                    'EOS': 'EOS',
                    'Bitcoin Cash': 'BCH',
                    'Cardano': 'ADA',
                    'Qtum': 'QTUM',
                    'Tezos': 'XTZ',
                    'Cosmos': 'ATOM',
                    'Nano': 'NANO',
                    'Siacoin': 'SC',
                    'Lisk': 'LSK',
                    'Waves': 'WAVES',
                    'ICON': 'ICX',
                    'Algorand': 'ALGO',
                    'Polygon - USDC.e': 'MATIC',
                    'Arbitrum One - USDC.e': 'Arbitrum',
                    'Polkadot': 'DOT',
                    'Kava': 'KAVA',
                    'Filecoin': 'FIL',
                    'Kusama': 'KSM',
                    'Flow': 'FLOW',
                    'Energy Web': 'EW',
                    'Mina': 'MINA',
                    'Centrifuge': 'CFG',
                    'Karura': 'KAR',
                    'Moonriver': 'MOVR',
                    'Shiden': 'SDN',
                    'Khala': 'PHA',
                    'Bifrost Kusama': 'BNC',
                    'Songbird': 'SGB',
                    'Terra classic': 'LUNC',
                    'KILT': 'KILT',
                    'Basilisk': 'BSX',
                    'Flare': 'FLR',
                    'Avalanche C-Chain': 'AVAX',
                    'Kintsugi': 'KINT',
                    'Altair': 'AIR',
                    'Moonbeam': 'GLMR',
                    'Acala': 'ACA',
                    'Astar': 'ASTR',
                    'Akash': 'AKT',
                    'Robonomics': 'XRT',
                    'Fantom': 'FTM',
                    'Elrond': 'EGLD',
                    'THORchain': 'RUNE',
                    'Secret': 'SCRT',
                    'Near': 'NEAR',
                    'Internet Computer Protocol': 'ICP',
                    'Picasso': 'PICA',
                    'Crust Shadow': 'CSM',
                    'Integritee': 'TEER',
                    'Parallel Finance': 'PARA',
                    'HydraDX': 'HDX',
                    'Interlay': 'INTR',
                    'Fetch.ai': 'FET',
                    'NYM': 'NYM',
                    'Terra 2.0': 'LUNA2',
                    'Juno': 'JUNO',
                    'Nodle': 'NODL',
                    'Stacks': 'STX',
                    'Ethereum PoW': 'ETHW',
                    'Aptos': 'APT',
                    'Sui': 'SUI',
                    'Genshiro': 'GENS',
                    'Aventus': 'AVT',
                    'Sei': 'SEI',
                    'OriginTrail': 'OTP',
                    'Celestia': 'TIA',
                },
            },
            'precisionMode': TICK_SIZE,
            'exceptions': {
                'EQuery:Invalid asset pair': BadSymbol,  # {"error":["EQuery:Invalid asset pair"]}
                'EAPI:Invalid key': AuthenticationError,
                'EFunding:Unknown withdraw key': InvalidAddress,  # {"error":["EFunding:Unknown withdraw key"]}
                'EFunding:Invalid amount': InsufficientFunds,
                'EService:Unavailable': ExchangeNotAvailable,
                'EDatabase:Internal error': ExchangeNotAvailable,
                'EService:Busy': ExchangeNotAvailable,
                'EQuery:Unknown asset': BadSymbol,  # {"error":["EQuery:Unknown asset"]}
                '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,
                'EGeneral:Invalid arguments': BadRequest,
                'ESession:Invalid session': AuthenticationError,
                'EAPI:Invalid nonce': InvalidNonce,
                'EFunding:No funding method': BadRequest,  # {"error":"EFunding:No funding method"}
                'EFunding:Unknown asset': BadSymbol,  # {"error":["EFunding:Unknown asset"]}
                'EService:Market in post_only mode': OnMaintenance,  # {"error":["EService:Market in post_only mode"]}
                'EGeneral:Too many requests': DDoSProtection,  # {"error":["EGeneral:Too many requests"]}
                'ETrade:User Locked': AccountSuspended,  # {"error":["ETrade:User Locked"]}
            },
        })

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

    def fetch_markets(self, params={}) -> List[Market]:
        """
        retrieves data on all markets for kraken
        :see: https://docs.kraken.com/rest/#tag/Spot-Market-Data/operation/getTradableAssetPairs
        :param dict [params]: extra parameters specific to the exchange API endpoint
        :returns dict[]: an array of objects representing market data
        """
        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')
            makerFees = self.safe_value(market, 'fees_maker', [])
            firstMakerFee = self.safe_value(makerFees, 0, [])
            firstMakerFeeRate = self.safe_string(firstMakerFee, 1)
            maker = None
            if firstMakerFeeRate is not None:
                maker = self.parse_number(Precise.string_div(firstMakerFeeRate, '100'))
            takerFees = self.safe_value(market, 'fees', [])
            firstTakerFee = self.safe_value(takerFees, 0, [])
            firstTakerFeeRate = self.safe_string(firstTakerFee, 1)
            taker = None
            if firstTakerFeeRate is not None:
                taker = self.parse_number(Precise.string_div(firstTakerFeeRate, '100'))
            leverageBuy = self.safe_value(market, 'leverage_buy', [])
            leverageBuyLength = len(leverageBuy)
            precisionPrice = self.parse_number(self.parse_precision(self.safe_string(market, 'pair_decimals')))
            result.append({
                'id': id,
                'wsId': self.safe_string(market, 'wsname'),
                'symbol': altname if darkpool else (base + '/' + quote),
                'base': base,
                'quote': quote,
                'settle': None,
                'baseId': baseId,
                'quoteId': quoteId,
                'settleId': None,
                'darkpool': darkpool,
                'altname': market['altname'],
                'type': 'spot',
                'spot': True,
                'margin': (leverageBuyLength > 0),
                'swap': False,
                'future': False,
                'option': False,
                'active': True,
                'contract': False,
                'linear': None,
                'inverse': None,
                'taker': taker,
                'maker': maker,
                'contractSize': None,
                'expiry': None,
                'expiryDatetime': None,
                'strike': None,
                'optionType': None,
                'precision': {
                    'amount': self.parse_number(self.parse_precision(self.safe_string(market, 'lot_decimals'))),
                    'price': precisionPrice,
                },
                'limits': {
                    'leverage': {
                        'min': self.parse_number('1'),
                        'max': self.safe_number(leverageBuy, leverageBuyLength - 1, 1),
                    },
                    'amount': {
                        'min': self.safe_number(market, 'ordermin'),
                        'max': None,
                    },
                    'price': {
                        'min': precisionPrice,
                        'max': None,
                    },
                    'cost': {
                        'min': self.safe_number(market, 'costmin'),
                        'max': None,
                    },
                },
                'created': None,
                'info': market,
            })
        result = self.append_inactive_markets(result)
        self.options['marketsByAltname'] = self.index_by(result, 'altname')
        return result

    def safe_currency(self, currencyId, currency: Currency = None):
        if currencyId is not None:
            if len(currencyId) > 3:
                if (currencyId.find('X') == 0) or (currencyId.find('Z') == 0):
                    if not (currencyId.find('.') > 0):
                        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': self.parse_number('1e-8'),
            'price': self.parse_number('1e-8'),
        }
        costLimits = {'min': None, 'max': None}
        priceLimits = {'min': precision['price'], 'max': None}
        amountLimits = {'min': precision['amount'], 'max': None}
        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={}) -> Currencies:
        """
        fetches all available currencies on an exchange
        :see: https://docs.kraken.com/rest/#tag/Spot-Market-Data/operation/getAssetInfo
        :param dict [params]: extra parameters specific to the exchange API endpoint
        :returns dict: an associative dictionary of currencies
        """
        response = self.publicGetAssets(params)
        #
        #     {
        #         "error": [],
        #         "result": {
        #             "BCH": {
        #                 "aclass": "currency",
        #                 "altname": "BCH",
        #                 "decimals": 10,
        #                 "display_decimals": 5
        #                 "status": "enabled",
        #             },
        #             ...
        #         },
        #     }
        #
        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(id)
            precision = self.parse_number(self.parse_precision(self.safe_string(currency, 'decimals')))
            # assumes all currencies are active except those listed above
            active = self.safe_string(currency, 'status') == 'enabled'
            result[code] = {
                'id': id,
                'code': code,
                'info': currency,
                'name': self.safe_string(currency, 'altname'),
                'active': active,
                'deposit': None,
                'withdraw': None,
                'fee': None,
                'precision': precision,
                'limits': {
                    'amount': {
                        'min': precision,
                        'max': None,
                    },
                    'withdraw': {
                        'min': None,
                        'max': None,
                    },
                },
                'networks': {},
            }
        return result

    def fetch_trading_fee(self, symbol: str, params={}) -> TradingFeeInterface:
        """
        fetch the trading fees for a market
        :see: https://docs.kraken.com/rest/#tag/Account-Data/operation/getTradeVolume
        :param str symbol: unified market symbol
        :param dict [params]: extra parameters specific to the exchange API endpoint
        :returns dict: a `fee structure <https://docs.ccxt.com/#/?id=fee-structure>`
        """
        self.load_markets()
        market = self.market(symbol)
        request = {
            'pair': market['id'],
            'fee-info': True,
        }
        response = self.privatePostTradeVolume(self.extend(request, params))
        #
        #     {
        #        "error": [],
        #        "result": {
        #          "currency": 'ZUSD',
        #          "volume": '0.0000',
        #          "fees": {
        #            "XXBTZUSD": {
        #              "fee": '0.2600',
        #              "minfee": '0.1000',
        #              "maxfee": '0.2600',
        #              "nextfee": '0.2400',
        #              "tiervolume": '0.0000',
        #              "nextvolume": '50000.0000'
        #            }
        #          },
        #          "fees_maker": {
        #            "XXBTZUSD": {
        #              "fee": '0.1600',
        #              "minfee": '0.0000',
        #              "maxfee": '0.1600',
        #              "nextfee": '0.1400',
        #              "tiervolume": '0.0000',
        #              "nextvolume": '50000.0000'
        #            }
        #          }
        #        }
        #     }
        #
        result = self.safe_value(response, 'result', {})
        return self.parse_trading_fee(result, market)

    def parse_trading_fee(self, response, market):
        makerFees = self.safe_value(response, 'fees_maker', {})
        takerFees = self.safe_value(response, 'fees', {})
        symbolMakerFee = self.safe_value(makerFees, market['id'], {})
        symbolTakerFee = self.safe_value(takerFees, market['id'], {})
        return {
            'info': response,
            'symbol': market['symbol'],
            'maker': self.safe_number(symbolMakerFee, 'fee'),
            'taker': self.safe_number(symbolTakerFee, 'fee'),
            'percentage': True,
            'tierBased': True,
        }

    def parse_bid_ask(self, bidask, priceKey: IndexType = 0, amountKey: IndexType = 1, countOrIdKey: IndexType = 2):
        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: str, limit: Int = None, params={}) -> OrderBook:
        """
        fetches information on open orders with bid(buy) and ask(sell) prices, volumes and other data
        :see: https://docs.kraken.com/rest/#tag/Spot-Market-Data/operation/getOrderBook
        :param str symbol: unified symbol of the market to fetch the order book for
        :param int [limit]: the maximum amount of order book entries to return
        :param dict [params]: extra parameters specific to the exchange API endpoint
        :returns dict: A dictionary of `order book structures <https://docs.ccxt.com/#/?id=order-book-structure>` indexed by market symbols
        """
        self.load_markets()
        market = self.market(symbol)
        if market['darkpool']:
            raise ExchangeError(self.id + ' fetchOrderBook() 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: Market = None) -> Ticker:
        #
        #     {
        #         "a":["2432.77000","1","1.000"],
        #         "b":["2431.37000","2","2.000"],
        #         "c":["2430.58000","0.04408910"],
        #         "v":["4147.94474901","8896.96086304"],
        #         "p":["2456.22239","2568.63032"],
        #         "t":[3907,10056],
        #         "l":["2302.18000","2302.18000"],
        #         "h":["2621.14000","2860.01000"],
        #         "o":"2571.56000"
        #     }
        #
        symbol = self.safe_symbol(None, market)
        v = self.safe_value(ticker, 'v', [])
        baseVolume = self.safe_string(v, 1)
        p = self.safe_value(ticker, 'p', [])
        vwap = self.safe_string(p, 1)
        quoteVolume = Precise.string_mul(baseVolume, vwap)
        c = self.safe_value(ticker, 'c', [])
        last = self.safe_string(c, 0)
        high = self.safe_value(ticker, 'h', [])
        low = self.safe_value(ticker, 'l', [])
        bid = self.safe_value(ticker, 'b', [])
        ask = self.safe_value(ticker, 'a', [])
        return self.safe_ticker({
            'symbol': symbol,
            'timestamp': None,
            'datetime': None,
            'high': self.safe_string(high, 1),
            'low': self.safe_string(low, 1),
            'bid': self.safe_string(bid, 0),
            'bidVolume': None,
            'ask': self.safe_string(ask, 0),
            'askVolume': None,
            'vwap': vwap,
            'open': self.safe_string(ticker, 'o'),
            'close': last,
            'last': last,
            'previousClose': None,
            'change': None,
            'percentage': None,
            'average': None,
            'baseVolume': baseVolume,
            'quoteVolume': quoteVolume,
            'info': ticker,
        }, market)

    def fetch_tickers(self, symbols: Strings = None, params={}) -> Tickers:
        """
        fetches price tickers for multiple markets, statistical information calculated over the past 24 hours for each market
        :see: https://docs.kraken.com/rest/#tag/Spot-Market-Data/operation/getTickerInformation
        :param str[]|None symbols: unified symbols of the markets to fetch the ticker for, all market tickers are returned if not assigned
        :param dict [params]: extra parameters specific to the exchange API endpoint
        :returns dict: a dictionary of `ticker structures <https://docs.ccxt.com/#/?id=ticker-structure>`
        """
        self.load_markets()
        request = {}
        if symbols is not None:
            symbols = self.market_symbols(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.safe_market(id)
            symbol = market['symbol']
            ticker = tickers[id]
            result[symbol] = self.parse_ticker(ticker, market)
        return self.filter_by_array_tickers(result, 'symbol', symbols)

    def fetch_ticker(self, symbol: str, params={}) -> Ticker:
        """
        fetches a price ticker, a statistical calculation with the information calculated over the past 24 hours for a specific market
        :see: https://docs.kraken.com/rest/#tag/Spot-Market-Data/operation/getTickerInformation
        :param str symbol: unified symbol of the market to fetch the ticker for
        :param dict [params]: extra parameters specific to the exchange API endpoint
        :returns dict: a `ticker structure <https://docs.ccxt.com/#/?id=ticker-structure>`
        """
        self.load_markets()
        darkpool = symbol.find('.d') >= 0
        if darkpool:
            raise ExchangeError(self.id + ' fetchTicker() 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: Market = None) -> list:
        #
        #     [
        #         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: str, timeframe='1m', since: Int = None, limit: Int = None, params={}) -> List[list]:
        """
        fetches historical candlestick data containing the open, high, low, and close price, and the volume of a market
        :see: https://docs.kraken.com/rest/#tag/Spot-Market-Data/operation/getOHLCData
        :param str symbol: unified symbol of the market to fetch OHLCV data for
        :param str timeframe: the length of time each candle represents
        :param int [since]: timestamp in ms of the earliest candle to fetch
        :param int [limit]: the maximum amount of candles to fetch
        :param dict [params]: extra parameters specific to the exchange API endpoint
        :param boolean [params.paginate]: default False, when True will automatically paginate by calling self endpoint multiple times. See in the docs all the [availble parameters](https://github.com/ccxt/ccxt/wiki/Manual#pagination-params)
        :returns int[][]: A list of candles ordered, open, high, low, close, volume
        """
        self.load_markets()
        paginate = False
        paginate, params = self.handle_option_and_params(params, 'fetchOHLCV', 'paginate')
        if paginate:
            return self.fetch_paginated_call_deterministic('fetchOHLCV', symbol, since, limit, timeframe, params, 720)
        market = self.market(symbol)
        parsedTimeframe = self.safe_integer(self.timeframes, timeframe)
        request = {
            'pair': market['id'],
        }
        if parsedTimeframe is not None:
            request['interval'] = parsedTimeframe
        else:
            request['interval'] = timeframe
        if since is not None:
            # contrary to kraken's api documentation, the since parameter must be passed in nanoseconds
            # the adding of '000000' is copied from the fetchTrades function
            request['since'] = self.number_to_string(since) + '000000'  # expected to be in nanoseconds
        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_list(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: 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_string(item, 'amount')
        if Precise.string_lt(amount, '0'):
            direction = 'out'
            amount = Precise.string_abs(amount)
        else:
            direction = 'in'
        timestamp = self.safe_timestamp(item, 'time')
        return {
            'info': item,
            'id': id,
            'direction': direction,
            'account': account,
            'referenceId': referenceId,
            'referenceAccount': referenceAccount,
            'type': type,
            'currency': code,
            'amount': self.parse_number(amount),
            'before': None,
            'after': self.safe_number(item, 'balance'),
            'status': 'ok',
            'timestamp': timestamp,
            'datetime': self.iso8601(timestamp),
            'fee': {
                'cost': self.safe_number(item, 'fee'),
                'currency': code,
            },
        }

    def fetch_ledger(self, code: Str = None, since: Int = None, limit: Int = None, params={}):
        """
        fetch the history of changes, actions done by the user or operations that altered balance of the user
        :see: https://docs.kraken.com/rest/#tag/Account-Data/operation/getLedgers
        :param str code: unified currency code, default is None
        :param int [since]: timestamp in ms of the earliest ledger entry, default is None
        :param int [limit]: max number of ledger entrys to return, default is None
        :param dict [params]: extra parameters specific to the exchange API endpoint
        :param int [params.until]: timestamp in ms of the latest ledger entry
        :returns dict: a `ledger structure <https://docs.ccxt.com/#/?id=ledger-structure>`
        """
        # 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'] = self.parse_to_int(since / 1000)
        request, params = self.handle_until_option('end', request, params)
        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: Str = 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: str, code: Str = None, params={}):
        items = self.fetch_ledger_entries_by_ids([id], code, params)
        return items[0]

    def parse_trade(self, trade, market: Market = None) -> Trade:
        #
        # 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
        price = None
        amount = 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'
            price = self.safe_string(trade, 0)
            amount = self.safe_string(trade, 1)
            tradeLength = len(trade)
            if tradeLength > 6:
                id = self.safe_string(trade, 6)  # artificially added  #1794
        elif isinstance(trade, str):
            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')
            price = self.safe_string(trade, 'price')
            amount = self.safe_string(trade, 'vol')
            if 'fee' in trade:
                currency = None
                if market is not None:
                    currency = market['quote']
                fee = {
                    'cost': self.safe_string(trade, 'fee'),
                    'currency': currency,
                }
        if market is not None:
            symbol = market['symbol']
        cost = self.safe_string(trade, 'cost')
        return self.safe_trade({
            '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,
        }, market)

    def fetch_trades(self, symbol: str, since: Int = None, limit: Int = None, params={}) -> List[Trade]:
        """
        get the list of most recent trades for a particular symbol
        :see: https://docs.kraken.com/rest/#tag/Spot-Market-Data/operation/getRecentTrades
        :param str symbol: unified symbol of the market to fetch trades for
        :param int [since]: timestamp in ms of the earliest trade to fetch
        :param int [limit]: the maximum amount of trades to fetch
        :param dict [params]: extra parameters specific to the exchange API endpoint
        :returns Trade[]: a list of `trade structures <https://docs.ccxt.com/#/?id=public-trades>`
        """
        self.load_markets()
        market = self.market(symbol)
        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
        if limit is not None:
            request['count'] = limit
        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)
        trades[length - 1] = lastTrade
        return self.parse_trades(trades, market, since, limit)

    def parse_balance(self, response) -> Balances:
        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)
            balance = self.safe_value(balances, currencyId, {})
            account = self.account()
            account['used'] = self.safe_string(balance, 'hold_trade')
            account['total'] = self.safe_string(balance, 'balance')
            result[code] = account
        return self.safe_balance(result)

    def fetch_balance(self, params={}) -> Balances:
        """
        query for balance and get the amount of funds available for trading or funds locked in orders
        :see: https://docs.kraken.com/rest/#tag/Account-Data/operation/getExtendedBalance
        :param dict [params]: extra parameters specific to the exchange API endpoint
        :returns dict: a `balance structure <https://docs.ccxt.com/#/?id=balance-structure>`
        """
        self.load_markets()
        response = self.privatePostBalanceEx(params)
        #
        #     {
        #         "error": [],
        #         "result": {
        #             "ZUSD": {
        #                 "balance": 25435.21,
        #                 "hold_trade": 8249.76
        #             },
        #             "XXBT": {
        #                 "balance": 1.2435,
        #                 "hold_trade": 0.8423
        #             }
        #         }
        #     }
        #
        return self.parse_balance(response)

    def create_order(self, symbol: str, type: OrderType, side: OrderSide, amount: float, price: Num = None, params={}):
        """
        :see: https://docs.kraken.com/rest/#tag/Trading/operation/addOrder
        create a trade order
        :param str symbol: unified symbol of the market to create an order in
        :param str type: 'market' or 'limit'
        :param str side: 'buy' or 'sell'
        :param float amount: how much of currency you want to trade in units of base currency
        :param float [price]: the price at which the order is to be fullfilled, in units of the quote currency, ignored in market orders
        :param dict [params]: extra parameters specific to the exchange API endpoint
        :param bool [params.postOnly]: if True, the order will only be posted to the order book and not executed immediately
        :param bool [params.reduceOnly]: *margin only* indicates if self order is to reduce the size of a position
        :param float [params.stopLossPrice]: *margin only* the price that a stop loss order is triggered at
        :param float [params.takeProfitPrice]: *margin only* the price that a take profit order is triggered at
        :param str [params.trailingAmount]: *margin only* the quote amount to trail away from the current market price
        :param str [params.trailingLimitAmount]: *margin only* the quote amount away from the trailingAmount
        :param str [params.offset]: *margin only* '+' or '-' whether you want the trailingLimitAmount value to be positive or negative, default is negative '-'
        :param str [params.trigger]: *margin only* the activation price type, 'last' or 'index', default is 'last'
        :returns dict: an `order structure <https://docs.ccxt.com/#/?id=order-structure>`
        """
        self.load_markets()
        market = self.market(symbol)
        request = {
            'pair': market['id'],
            'type': side,
            'ordertype': type,
            'volume': self.amount_to_precision(symbol, amount),
        }
        orderRequest = self.order_request('createOrder()', symbol, type, request, price, params)
        response = self.privatePostAddOrder(self.extend(orderRequest[0], orderRequest[1]))
        #
        #     {
        #         "error": [],
        #         "result": {
        #             "descr": {order: 'buy 0.02100000 ETHUSDT @ limit 330.00'},
        #             "txid": ['OEKVV2-IH52O-TPL6GZ']
        #         }
        #     }
        #
        result = self.safe_dict(response, 'result')
        return self.parse_order(result)

    def find_market_by_altname_or_id(self, id):
        marketsByAltname = self.safe_value(self.options, 'marketsByAltname', {})
        if id in marketsByAltname:
            return marketsByAltname[id]
        else:
            return self.safe_market(id)

    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_type(self, status):
        statuses = {
            'take-profit': 'market',
            'stop-loss-limit': 'limit',
            'stop-loss': 'market',
            'take-profit-limit': 'limit',
            'trailing-stop-limit': 'limit',
        }
        return self.safe_string(statuses, status, status)

    def parse_order(self, order, market: Market = None) -> Order:
        #
        # createOrder for regular orders
        #
        #     {
        #         "descr": {order: 'buy 0.02100000 ETHUSDT @ limit 330.00'},
        #         "txid": ['OEKVV2-IH52O-TPL6GZ']
        #     }
        #     {
        #         "txid": ["TX_ID_HERE"],
        #         "descr": {"order":"buy 0.12345678 ETHEUR @ market"},
        #     }
        #
        #
        # createOrder for stop orders
        #
        #     {
        #         "txid":["OSILNC-VQI5Q-775ZDQ"],
        #         "descr":{"order":"sell 167.28002676 ADAXBT @ stop loss 0.00003280 -> limit 0.00003212"}
        #     }
        #
        #
        #     {
        #         "txid":["OVHMJV-BZW2V-6NZFWF"],
        #         "descr":{"order":"sell 0.00100000 ETHUSD @ stop loss 2677.00 -> limit 2577.00 with 5:1 leverage"}
        #     }
        #
        # editOrder
        #
        #     {
        #         "status": "ok",
        #         "txid": "OAW2BO-7RWEK-PZY5UO",
        #         "originaltxid": "OXL6SS-UPNMC-26WBE7",
        #         "volume": "0.00075000",
        #         "price": "13500.0",
        #         "orders_cancelled": 1,
        #         "descr": {
        #             "order": "buy 0.00075000 XBTUSDT @ limit 13500.0"
        #         }
        #     }
        #  ws - createOrder
        #    {
        #        "descr": 'sell 0.00010000 XBTUSDT @ market',
        #        "event": 'addOrderStatus',
        #        "reqid": 1,
        #        "status": 'ok',
        #        "txid": 'OAVXZH-XIE54-JCYYDG'
        #    }
        #  ws - editOrder
        #    {
        #        "descr": "order edited price = 9000.00000000",
        #        "event": "editOrderStatus",
        #        "originaltxid": "O65KZW-J4AW3-VFS74A",
        #        "reqid": 3,
        #        "status": "ok",
        #        "txid": "OTI672-HJFAO-XOIPPK"
        #    }
        #
        #  {
        #      "error": [],
        #      "result": {
        #          "open": {
        #              "OXVPSU-Q726F-L3SDEP": {
        #                  "refid": null,
        #                  "userref": 0,
        #                  "status": "open",
        #                  "opentm": 1706893367.4656649,
        #                  "starttm": 0,
        #                  "expiretm": 0,
        #                  "descr": {
        #                      "pair": "XRPEUR",
        #                      "type": "sell",
        #                      "ordertype": "trailing-stop",
        #                      "price": "+50.0000%",
        #                      "price2": "0",
        #                      "leverage": "none",
        #                      "order": "sell 10.00000000 XRPEUR @ trailing stop +50.0000%",
        #                      "close": ""
        #                  },
        #                  "vol": "10.00000000",
        #                  "vol_exec": "0.00000000",
        #                  "cost": "0.00000000",
        #                  "fee": "0.00000000",
        #                  "price": "0.00000000",
        #                  "stopprice": "0.23424000",
        #                  "limitprice": "0.46847000",
        #                  "misc": "",
        #                  "oflags": "fciq",
        #                  "trigger": "index"
        #              }
        #      }
        #  }
        #
        description = self.safe_dict(order, 'descr', {})
        orderDescriptionObj = self.safe_dict(order, 'descr')  # can be null
        orderDescription = None
        if orderDescriptionObj is not None:
            orderDescription = self.safe_string(orderDescriptionObj, 'order')
        else:
            orderDescription = self.safe_string(order, 'descr')
        side = None
        type = None
        marketId = None
        price = None
        amount = None
        stopPrice = None
        if orderDescription is not None:
            parts = orderDescription.split(' ')
            side = self.safe_string(parts, 0)
            amount = self.safe_string(parts, 1)
            marketId = self.safe_string(parts, 2)
            type = self.safe_string(parts, 4)
            if type == 'stop':
                stopPrice = self.safe_string(parts, 6)
                price = self.safe_string(parts, 9)
            elif type == 'limit':
                price = self.safe_string(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_string(order, 'vol', amount)
        filled = self.safe_string(order, 'vol_exec')
        fee = None
        # kraken truncates the cost in the api response so we will ignore it and calculate it from average & filled
        # cost = self.safe_string(order, 'cost')
        price = self.safe_string(description, 'price', price)
        # when type = trailling stop returns price = '+50.0000%'
        if (price is not None) and price.endswith('%'):
            price = None  # self is not the price we want
        if (price is None) or Precise.string_equals(price, '0'):
            price = self.safe_string(description, 'price2')
        if (price is None) or Precise.string_equals(price, '0'):
            price = self.safe_string(order, 'price', price)
        flags = self.safe_string(order, 'oflags', '')
        isPostOnly = flags.find('post') > -1
        average = self.safe_number(order, 'price')
        if market is not None:
            symbol = market['symbol']
            if 'fee' in order:
                feeCost = self.safe_string(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_2(order, 'id', 'txid')
        if (id is None) or (id.startswith('[')):
            txid = self.safe_list(order, 'txid')
            id = self.safe_string(txid, 0)
        clientOrderId = self.safe_string(order, 'userref')
        rawTrades = self.safe_value(order, 'trades', [])
        trades = []
        for i in range(0, len(rawTrades)):
            rawTrade = rawTrades[i]
            if isinstance(rawTrade, str):
                trades.append(self.safe_trade({'id': rawTrade, 'orderId': id, 'symbol': symbol, 'info': {}}))
            else:
                trades.append(rawTrade)
        stopPrice = self.omit_zero(self.safe_string(order, 'stopprice', stopPrice))
        stopLossPrice = None
        takeProfitPrice = None
        if type.startswith('take-profit'):
            takeProfitPrice = self.safe_string(description, 'price')
            price = self.omit_zero(self.safe_string(description, 'price2'))
        elif type.startswith('stop-loss'):
            stopLossPrice = self.safe_string(description, 'price')
            price = self.omit_zero(self.safe_string(description, 'price2'))
        return self.safe_order({
            'id': id,
            'clientOrderId': clientOrderId,
            'info': order,
            'timestamp': timestamp,
            'datetime': self.iso8601(timestamp),
            'lastTradeTimestamp': None,
            'status': status,
            'symbol': symbol,
            'type': self.parse_order_type(type),
            'timeInForce': None,
            'postOnly': isPostOnly,
            'side': side,
            'price': price,
            'stopPrice': stopPrice,
            'triggerPrice': stopPrice,
            'takeProfitPrice': takeProfitPrice,
            'stopLossPrice': stopLossPrice,
            'cost': None,
            'amount': amount,
            'filled': filled,
            'average': average,
            'remaining': None,
            'fee': fee,
            'trades': trades,
        }, market)

    def order_request(self, method, symbol, type, request, price=None, params={}):
        clientOrderId = self.safe_string_2(params, 'userref', 'clientOrderId')
        params = self.omit(params, ['userref', 'clientOrderId'])
        if clientOrderId is not None:
            request['userref'] = clientOrderId
        stopLossTriggerPrice = self.safe_string(params, 'stopLossPrice')
        takeProfitTriggerPrice = self.safe_string(params, 'takeProfitPrice')
        isStopLossTriggerOrder = stopLossTriggerPrice is not None
        isTakeProfitTriggerOrder = takeProfitTriggerPrice is not None
        isStopLossOrTakeProfitTrigger = isStopLossTriggerOrder or isTakeProfitTriggerOrder
        trailingAmount = self.safe_string(params, 'trailingAmount')
        trailingLimitAmount = self.safe_string(params, 'trailingLimitAmount')
        isTrailingAmountOrder = trailingAmount is not None
        isLimitOrder = type.endswith('limit')  # supporting limit, stop-loss-limit, take-profit-limit, etc
        if isLimitOrder and not isTrailingAmountOrder:
            request['price'] = self.price_to_precision(symbol, price)
        reduceOnly = self.safe_value_2(params, 'reduceOnly', 'reduce_only')
        if isStopLossOrTakeProfitTrigger:
            if isStopLossTriggerOrder:
                request['price'] = self.price_to_precision(symbol, stopLossTriggerPrice)
                if isLimitOrder:
                    request['ordertype'] = 'stop-loss-limit'
                else:
                    request['ordertype'] = 'stop-loss'
            elif isTakeProfitTriggerOrder:
                request['price'] = self.price_to_precision(symbol, takeProfitTriggerPrice)
                if isLimitOrder:
                    request['ordertype'] = 'take-profit-limit'
                else:
                    request['ordertype'] = 'take-profit'
            if isLimitOrder:
                request['price2'] = self.price_to_precision(symbol, price)
        elif isTrailingAmountOrder:
            trailingActivationPriceType = self.safe_string(params, 'trigger', 'last')
            trailingAmountString = '+' + trailingAmount
            request['trigger'] = trailingActivationPriceType
            if isLimitOrder or (trailingLimitAmount is not None):
                offset = self.safe_string(params, 'offset', '-')
                trailingLimitAmountString = offset + self.number_to_string(trailingLimitAmount)
                request['price'] = trailingAmountString
                request['price2'] = trailingLimitAmountString
                request['ordertype'] = 'trailing-stop-limit'
            else:
                request['price'] = trailingAmountString
                request['ordertype'] = 'trailing-stop'
        if reduceOnly:
            request['reduce_only'] = 'true'  # not using hasattr(self, boolean) case, because the urlencodedNested transforms it into 'True' string
        close = self.safe_value(params, 'close')
        if close is not None:
            close = self.extend({}, close)
            closePrice = self.safe_value(close, 'price')
            if closePrice is not None:
                close['price'] = self.price_to_precision(symbol, closePrice)
            closePrice2 = self.safe_value(close, 'price2')  # stopPrice
            if closePrice2 is not None:
                close['price2'] = self.price_to_precision(symbol, closePrice2)
            request['close'] = close
        timeInForce = self.safe_string_2(params, 'timeInForce', 'timeinforce')
        if timeInForce is not None:
            request['timeinforce'] = timeInForce
        isMarket = (type == 'market')
        postOnly = None
        postOnly, params = self.handle_post_only(isMarket, False, params)
        if postOnly:
            request['oflags'] = 'post'
        params = self.omit(params, ['timeInForce', 'reduceOnly', 'stopLossPrice', 'takeProfitPrice', 'trailingAmount', 'trailingLimitAmount', 'offset'])
        return [request, params]

    def edit_order(self, id: str, symbol: str, type: OrderType, side: OrderSide, amount: Num = None, price: Num = None, params={}):
        """
        edit a trade order
        :see: https://docs.kraken.com/rest/#tag/Trading/operation/editOrder
        :param str id: order id
        :param str symbol: unified symbol of the market to create an order in
        :param str type: 'market' or 'limit'
        :param str side: 'buy' or 'sell'
        :param float amount: how much of the currency you want to trade in units of the base currency
        :param float [price]: the price at which the order is to be fullfilled, in units of the quote currency, ignored in market orders
        :param dict [params]: extra parameters specific to the exchange API endpoint
        :param float [params.stopLossPrice]: *margin only* the price that a stop loss order is triggered at
        :param float [params.takeProfitPrice]: *margin only* the price that a take profit order is triggered at
        :param str [params.trailingAmount]: *margin only* the quote price away from the current market price
        :param str [params.trailingLimitAmount]: *margin only* the quote amount away from the trailingAmount
        :param str [params.offset]: *margin only* '+' or '-' whether you want the trailingLimitAmount value to be positive or negative, default is negative '-'
        :param str [params.trigger]: *margin only* the activation price type, 'last' or 'index', default is 'last'
        :returns dict: an `order structure <https://docs.ccxt.com/#/?id=order-structure>`
        """
        self.load_markets()
        market = self.market(symbol)
        if not market['spot']:
            raise NotSupported(self.id + ' editOrder() does not support ' + market['type'] + ' orders, only spot orders are accepted')
        request = {
            'txid': id,
            'pair': market['id'],
        }
        if amount is not None:
            request['volume'] = self.amount_to_precision(symbol, amount)
        orderRequest = self.order_request('editOrder()', symbol, type, request, price, params)
        response = self.privatePostEditOrder(self.extend(orderRequest[0], orderRequest[1]))
        #
        #     {
        #         "error": [],
        #         "result": {
        #             "status": "ok",
        #             "txid": "OAW2BO-7RWEK-PZY5UO",
        #             "originaltxid": "OXL6SS-UPNMC-26WBE7",
        #             "volume": "0.00075000",
        #             "price": "13500.0",
        #             "orders_cancelled": 1,
        #             "descr": {
        #                 "order": "buy 0.00075000 XBTUSDT @ limit 13500.0"
        #             }
        #         }
        #     }
        #
        data = self.safe_dict(response, 'result', {})
        return self.parse_order(data, market)

    def fetch_order(self, id: str, symbol: Str = None, params={}):
        """
        fetches information on an order made by the user
        :see: https://docs.kraken.com/rest/#tag/Account-Data/operation/getOrdersInfo
        :param str symbol: not used by kraken fetchOrder
        :param dict [params]: extra parameters specific to the exchange API endpoint
        :returns dict: An `order structure <https://docs.ccxt.com/#/?id=order-structure>`
        """
        self.load_markets()
        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)
        return self.parse_order(self.extend({'id': id}, result[id]))

    def fetch_order_trades(self, id: str, symbol: Str = None, since: Int = None, limit: Int = None, params={}):
        """
        fetch all the trades made from a single order
        :see: https://docs.kraken.com/rest/#tag/Account-Data/operation/getTradesInfo
        :param str id: order id
        :param str symbol: unified market symbol
        :param int [since]: the earliest time in ms to fetch trades for
        :param int [limit]: the maximum number of trades to retrieve
        :param dict [params]: extra parameters specific to the exchange API endpoint
        :returns dict[]: a list of `trade structures <https://docs.ccxt.com/#/?id=trade-structure>`
        """
        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, str):
                    tradeIds.append(orderTrade)
                else:
                    tradeIds.append(orderTrade['id'])
        self.load_markets()
        if symbol is not None:
            symbol = self.symbol(symbol)
        options = self.safe_value(self.options, 'fetchOrderTrades', {})
        batchSize = self.safe_integer(options, 'batchSize', 20)
        numTradeIds = len(tradeIds)
        numBatches = self.parse_to_int(numTradeIds / batchSize)
        numBatches = self.sum(numBatches, 1)
        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: Str = None, params={}):
        """
        fetch orders by the list of order id
        :see: https://docs.kraken.com/rest/#tag/Account-Data/operation/getClosedOrders
        :param str[]|None ids: list of order id
        :param dict [params]: extra parameters specific to the kraken api endpoint
        :returns dict[]: a list of `order structure <https://docs.ccxt.com/#/?id=order-structure>`
        """
        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: Str = None, since: Int = None, limit: Int = None, params={}):
        """
        fetch all trades made by the user
        :see: https://docs.kraken.com/rest/#tag/Account-Data/operation/getTradeHistory
        :param str symbol: unified market symbol
        :param int [since]: the earliest time in ms to fetch trades for
        :param int [limit]: the maximum number of trades structures to retrieve
        :param dict [params]: extra parameters specific to the exchange API endpoint
        :returns Trade[]: a list of `trade structures <https://docs.ccxt.com/#/?id=trade-structure>`
        """
        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'] = self.parse_to_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: str, symbol: Str = None, params={}):
        """
        cancels an open order
        :see: https://docs.kraken.com/rest/#tag/Trading/operation/cancelOrder
        :param str id: order id
        :param str symbol: unified symbol of the market the order was made in
        :param dict [params]: extra parameters specific to the exchange API endpoint
        :returns dict: An `order structure <https://docs.ccxt.com/#/?id=order-structure>`
        """
        self.load_markets()
        response = None
        clientOrderId = self.safe_value_2(params, 'userref', 'clientOrderId', id)
        request = {
            'txid': clientOrderId,  # order id or userref
        }
        params = self.omit(params, ['userref', 'clientOrderId'])
        try:
            response = self.privatePostCancelOrder(self.extend(request, 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_orders(self, ids, symbol: Str = None, params={}):
        """
        cancel multiple orders
        :see: https://docs.kraken.com/rest/#tag/Trading/operation/cancelOrderBatch
        :param str[] ids: open orders transaction ID(txid) or user reference(userref)
        :param str symbol: unified market symbol
        :param dict [params]: extra parameters specific to the exchange API endpoint
        :returns dict: an list of `order structures <https://docs.ccxt.com/#/?id=order-structure>`
        """
        request = {
            'orders': ids,
        }
        response = self.privatePostCancelOrderBatch(self.extend(request, params))
        #
        #     {
        #         "error": [],
        #         "result": {
        #           "count": 2
        #         }
        #     }
        #
        return response

    def cancel_all_orders(self, symbol: Str = None, params={}):
        """
        cancel all open orders
        :see: https://docs.kraken.com/rest/#tag/Trading/operation/cancelAllOrders
        :param str symbol: unified market symbol, only orders in the market of self symbol are cancelled when symbol is not None
        :param dict [params]: extra parameters specific to the exchange API endpoint
        :returns dict[]: a list of `order structures <https://docs.ccxt.com/#/?id=order-structure>`
        """
        self.load_markets()
        return self.privatePostCancelAll(params)

    def fetch_open_orders(self, symbol: Str = None, since: Int = None, limit: Int = None, params={}) -> List[Order]:
        """
        fetch all unfilled currently open orders
        :see: https://docs.kraken.com/rest/#tag/Account-Data/operation/getOpenOrders
        :param str symbol: unified market symbol
        :param int [since]: the earliest time in ms to fetch open orders for
        :param int [limit]: the maximum number of  open orders structures to retrieve
        :param dict [params]: extra parameters specific to the exchange API endpoint
        :returns Order[]: a list of `order structures <https://docs.ccxt.com/#/?id=order-structure>`
        """
        self.load_markets()
        request = {}
        if since is not None:
            request['start'] = self.parse_to_int(since / 1000)
        query = params
        clientOrderId = self.safe_value_2(params, 'userref', 'clientOrderId')
        if clientOrderId is not None:
            request['userref'] = clientOrderId
            query = self.omit(params, ['userref', 'clientOrderId'])
        response = self.privatePostOpenOrders(self.extend(request, query))
        market = None
        if symbol is not None:
            market = self.market(symbol)
        result = self.safe_dict(response, 'result', {})
        orders = self.safe_dict(result, 'open', {})
        return self.parse_orders(orders, market, since, limit)

    def fetch_closed_orders(self, symbol: Str = None, since: Int = None, limit: Int = None, params={}) -> List[Order]:
        """
        fetches information on multiple closed orders made by the user
        :see: https://docs.kraken.com/rest/#tag/Account-Data/operation/getClosedOrders
        :param str symbol: unified market symbol of the market orders were made in
        :param int [since]: the earliest time in ms to fetch orders for
        :param int [limit]: the maximum number of order structures to retrieve
        :param dict [params]: extra parameters specific to the exchange API endpoint
        :param int [params.until]: timestamp in ms of the latest entry
        :returns Order[]: a list of `order structures <https://docs.ccxt.com/#/?id=order-structure>`
        """
        self.load_markets()
        request = {}
        if since is not None:
            request['start'] = self.parse_to_int(since / 1000)
        query = params
        clientOrderId = self.safe_value_2(params, 'userref', 'clientOrderId')
        if clientOrderId is not None:
            request['userref'] = clientOrderId
            query = self.omit(params, ['userref', 'clientOrderId'])
        request, params = self.handle_until_option('end', request, params)
        response = self.privatePostClosedOrders(self.extend(request, query))
        #
        #     {
        #         "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_dict(response, 'result', {})
        orders = self.safe_dict(result, 'closed', {})
        return self.parse_orders(orders, market, since, limit)

    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_network(self, network):
        withdrawMethods = self.safe_value(self.options, 'withdrawMethods', {})
        return self.safe_string(withdrawMethods, network, network)

    def parse_transaction(self, transaction, currency: Currency = None) -> Transaction:
        #
        # 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"
        #     }
        #
        # there can be an additional 'status-prop' field present
        # deposit pending review by exchange => 'on-hold'
        # the deposit is initiated by the exchange => 'return'
        #
        #      {
        #          "type": 'deposit',
        #          "method": 'Fidor Bank AG(Wire Transfer)',
        #          "aclass": 'currency',
        #          "asset": 'ZEUR',
        #          "refid": 'xxx-xxx-xxx',
        #          "txid": '12341234',
        #          "info": 'BANKCODEXXX',
        #          "amount": '38769.08',
        #          "fee": '0.0000',
        #          "time": 1644306552,
        #          "status": 'Success',
        #          status-prop: 'on-hold'
        #      }
        #
        #
        # 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"
        #         "key":"Huobi wallet",
        #         "network":"Tron"
        #         status-prop: 'on-hold'  # self field might not be present in some cases
        #     }
        #
        # withdraw
        #
        #     {
        #         "refid": "AGBSO6T-UFMTTQ-I7KGS6"
        #     }
        #
        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'))
        statusProp = self.safe_string(transaction, 'status-prop')
        isOnHoldDeposit = statusProp == 'on-hold'
        isCancellationRequest = statusProp == 'cancel-pending'
        isOnHoldWithdrawal = statusProp == 'onhold'
        if isOnHoldDeposit or isCancellationRequest or isOnHoldWithdrawal:
            status = 'pending'
        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,
            'network': self.parse_network(self.safe_string(transaction, 'network')),
            'address': address,
            'addressTo': None,
            'addressFrom': None,
            'tag': None,
            'tagTo': None,
            'tagFrom': None,
            'status': status,
            'type': type,
            'updated': None,
            'txid': txid,
            'timestamp': timestamp,
            'datetime': self.iso8601(timestamp),
            'comment': None,
            'internal': None,
            'fee': {
                'currency': code,
                'cost': feeCost,
            },
        }

    def parse_transactions_by_type(self, type, transactions, code: Str = None, since: Int = None, limit: Int = 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: Str = None, since: Int = None, limit: Int = None, params={}) -> List[Transaction]:
        """
        fetch all deposits made to an account
        :see: https://docs.kraken.com/rest/#tag/Funding/operation/getStatusRecentDeposits
        :param str code: unified currency code
        :param int [since]: the earliest time in ms to fetch deposits for
        :param int [limit]: the maximum number of deposits structures to retrieve
        :param dict [params]: extra parameters specific to the exchange API endpoint
        :returns dict[]: a list of `transaction structures <https://docs.ccxt.com/#/?id=transaction-structure>`
        """
        # 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'],
        }
        if since is not None:
            request['start'] = since
        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={}):
        """
        fetches the current integer timestamp in milliseconds from the exchange server
        :see: https://docs.kraken.com/rest/#tag/Spot-Market-Data/operation/getServerTime
        :param dict [params]: extra parameters specific to the exchange API endpoint
        :returns int: the current integer timestamp in milliseconds from the exchange server
        """
        # 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: Str = None, since: Int = None, limit: Int = None, params={}) -> List[Transaction]:
        """
        fetch all withdrawals made from an account
        :see: https://docs.kraken.com/rest/#tag/Funding/operation/getStatusRecentWithdrawals
        :param str code: unified currency code
        :param int [since]: the earliest time in ms to fetch withdrawals for
        :param int [limit]: the maximum number of withdrawals structures to retrieve
        :param dict [params]: extra parameters specific to the exchange API endpoint
        :param dict [params.end]: End timestamp, withdrawals created strictly after will be not be included in the response
        :param boolean [params.paginate]:  default False, when True will automatically paginate by calling self endpoint multiple times
        :returns dict[]: a list of `transaction structures <https://docs.ccxt.com/#/?id=transaction-structure>`
        """
        self.load_markets()
        paginate = False
        paginate, params = self.handle_option_and_params(params, 'fetchWithdrawals', 'paginate')
        if paginate:
            params['cursor'] = True
            return self.fetch_paginated_call_cursor('fetchWithdrawals', code, since, limit, params, 'next_cursor', 'cursor')
        request = {}
        if code is not None:
            currency = self.currency(code)
            request['asset'] = currency['id']
        if since is not None:
            request['since'] = str(since)
        response = self.privatePostWithdrawStatus(self.extend(request, params))
        #
        # with no pagination
        #     { 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"                                                             }]}
        # with pagination
        #    {
        #        "error":[],
        #        "result":{
        #           "withdrawals":[
        #              {
        #                 "method":"Tether USD(TRC20)",
        #                 "aclass":"currency",
        #                 "asset":"USDT",
        #                 "refid":"BSNFZU2-MEFN4G-J3NEZV",
        #                 "txid":"1c7a642fb7387bbc2c6a2c509fd1ae146937f4cf793b4079a4f0715e3a02615a",
        #                 "info":"TQmdxSuC16EhFg8FZWtYgrfFRosoRF7bCp",
        #                 "amount":"1996.50000000",
        #                 "fee":"2.50000000",
        #                 "time":1669126657,
        #                 "status":"Success",
        #                 "key":"poloniex",
        #                 "network":"Tron"
        #              },
        #             ...
        #           ],
        #           "next_cursor":"HgAAAAAAAABGVFRSd3k1LVF4Y0JQY05Gd0xRY0NxenFndHpybkwBAQH2AwEBAAAAAQAAAAAAAAABAAAAAAAZAAAAAAAAAA=="
        #        }
        #     }
        #
        rawWithdrawals = None
        result = self.safe_value(response, 'result')
        if not isinstance(result, list):
            rawWithdrawals = self.add_pagination_cursor_to_result(result)
        else:
            rawWithdrawals = result
        return self.parse_transactions_by_type('withdrawal', rawWithdrawals, code, since, limit)

    def add_pagination_cursor_to_result(self, result):
        cursor = self.safe_string(result, 'next_cursor')
        data = self.safe_value(result, 'withdrawals')
        dataLength = len(data)
        if cursor is not None and dataLength > 0:
            last = data[dataLength - 1]
            last['next_cursor'] = cursor
            data[dataLength - 1] = last
        return data

    def create_deposit_address(self, code: str, params={}):
        """
        create a currency deposit address
        :see: https://docs.kraken.com/rest/#tag/Funding/operation/getDepositAddresses
        :param str code: unified currency code of the currency for the deposit address
        :param dict [params]: extra parameters specific to the exchange API endpoint
        :returns dict: an `address structure <https://docs.ccxt.com/#/?id=address-structure>`
        """
        request = {
            'new': 'true',
        }
        return self.fetch_deposit_address(code, self.extend(request, params))

    def fetch_deposit_methods(self, code: str, params={}):
        """
        fetch deposit methods for a currency associated with self account
        :see: https://docs.kraken.com/rest/#tag/Funding/operation/getDepositMethods
        :param str code: unified currency code
        :param dict [params]: extra parameters specific to the kraken api endpoint
        :returns dict: of deposit methods
        """
        self.load_markets()
        currency = self.currency(code)
        request = {
            'asset': currency['id'],
        }
        response = self.privatePostDepositMethods(self.extend(request, params))
        #
        #     {
        #         "error":[],
        #         "result":[
        #             {"method":"Ether(Hex)","limit":false,"gen-address":true}
        #         ]
        #     }
        #
        #     {
        #         "error":[],
        #         "result":[
        #             {"method":"Tether USD(ERC20)","limit":false,"address-setup-fee":"0.00000000","gen-address":true},
        #             {"method":"Tether USD(TRC20)","limit":false,"address-setup-fee":"0.00000000","gen-address":true}
        #         ]
        #     }
        #
        #     {
        #         "error":[],
        #         "result":[
        #             {"method":"Bitcoin","limit":false,"fee":"0.0000000000","gen-address":true}
        #         ]
        #     }
        #
        return self.safe_value(response, 'result')

    def fetch_deposit_address(self, code: str, params={}):
        """
        fetch the deposit address for a currency associated with self account
        :see: https://docs.kraken.com/rest/#tag/Funding/operation/getDepositAddresses
        :param str code: unified currency code
        :param dict [params]: extra parameters specific to the exchange API endpoint
        :returns dict: an `address structure <https://docs.ccxt.com/#/?id=address-structure>`
        """
        self.load_markets()
        currency = self.currency(code)
        network = self.safe_string_upper(params, 'network')
        networks = self.safe_value(self.options, 'networks', {})
        network = self.safe_string(networks, network, network)  # support ETH > ERC20 aliases
        params = self.omit(params, 'network')
        if (code == 'USDT') and (network == 'TRC20'):
            code = code + '-' + network
        defaultDepositMethods = self.safe_value(self.options, 'depositMethods', {})
        defaultDepositMethod = self.safe_string(defaultDepositMethods, code)
        depositMethod = self.safe_string(params, 'method', defaultDepositMethod)
        # if the user has specified an exchange-specific method in params
        # we pass it, otherwise we take the 'network' unified param
        if depositMethod is None:
            depositMethods = self.fetch_deposit_methods(code)
            if network is not None:
                # find best matching deposit method, or fallback to the first one
                for i in range(0, len(depositMethods)):
                    entry = self.safe_string(depositMethods[i], 'method')
                    if entry.find(network) >= 0:
                        depositMethod = entry
                        break
            # if depositMethod was not specified, fallback to the first available deposit method
            if depositMethod is None:
                firstDepositMethod = self.safe_value(depositMethods, 0, {})
                depositMethod = self.safe_string(firstDepositMethod, 'method')
        request = {
            'asset': currency['id'],
            'method': depositMethod,
        }
        response = self.privatePostDepositAddresses(self.extend(request, params))
        #
        #     {
        #         "error":[],
        #         "result":[
        #             {"address":"0x77b5051f97efa9cc52c9ad5b023a53fc15c200d3","expiretm":"0"}
        #         ]
        #     }
        #
        result = self.safe_value(response, 'result', [])
        firstResult = self.safe_value(result, 0, {})
        if firstResult is None:
            raise InvalidAddress(self.id + ' privatePostDepositAddresses() returned no addresses for ' + code)
        return self.parse_deposit_address(firstResult, currency)

    def parse_deposit_address(self, depositAddress, currency: Currency = None):
        #
        #     {
        #         "address":"0x77b5051f97efa9cc52c9ad5b023a53fc15c200d3",
        #         "expiretm":"0"
        #     }
        #
        address = self.safe_string(depositAddress, 'address')
        tag = self.safe_string(depositAddress, 'tag')
        currency = self.safe_currency(None, currency)
        code = currency['code']
        self.check_address(address)
        return {
            'currency': code,
            'address': address,
            'tag': tag,
            'network': None,
            'info': depositAddress,
        }

    def withdraw(self, code: str, amount: float, address, tag=None, params={}):
        """
        make a withdrawal
        :see: https://docs.kraken.com/rest/#tag/Funding/operation/withdrawFunds
        :param str code: unified currency code
        :param float amount: the amount to withdraw
        :param str address: the address to withdraw to
        :param str tag:
        :param dict [params]: extra parameters specific to the exchange API endpoint
        :returns dict: a `transaction structure <https://docs.ccxt.com/#/?id=transaction-structure>`
        """
        tag, params = self.handle_withdraw_tag_and_params(tag, params)
        self.check_address(address)
        if 'key' in params:
            self.load_markets()
            currency = self.currency(code)
            request = {
                'asset': currency['id'],
                'amount': amount,
                'address': address,
            }
            response = self.privatePostWithdraw(self.extend(request, params))
            #
            #     {
            #         "error": [],
            #         "result": {
            #             "refid": "AGBSO6T-UFMTTQ-I7KGS6"
            #         }
            #     }
            #
            result = self.safe_dict(response, 'result', {})
            return self.parse_transaction(result, currency)
        raise ExchangeError(self.id + " withdraw() requires a 'key' parameter(withdrawal key name, up on your account)")

    def fetch_positions(self, symbols: Strings = None, params={}):
        """
        fetch all open positions
        :see: https://docs.kraken.com/rest/#tag/Account-Data/operation/getOpenPositions
        :param str[] [symbols]: not used by kraken fetchPositions()
        :param dict [params]: extra parameters specific to the exchange API endpoint
        :returns dict[]: a list of `position structure <https://docs.ccxt.com/#/?id=position-structure>`
        """
        self.load_markets()
        request = {
            # 'txid': 'comma delimited list of transaction ids to restrict output to',
            'docalcs': 'true',  # 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"
        #             }
        #         ]
        #     }
        #
        symbols = self.market_symbols(symbols)
        result = self.safe_list(response, 'result')
        results = self.parse_positions(result, symbols)
        return self.filter_by_array_positions(results, 'symbol', symbols, False)

    def parse_position(self, position, market: Market = None):
        #
        #             {
        #                 "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"
        #             }
        #
        marketId = self.safe_string(position, 'pair')
        rawSide = self.safe_string(position, 'type')
        side = 'long' if (rawSide == 'buy') else 'short'
        return self.safe_position({
            'info': position,
            'id': None,
            'symbol': self.safe_symbol(marketId, market),
            'notional': None,
            'marginMode': None,
            'liquidationPrice': None,
            'entryPrice': None,
            'unrealizedPnl': self.safe_number(position, 'net'),
            'realizedPnl': None,
            'percentage': None,
            'contracts': self.safe_number(position, 'vol'),
            'contractSize': None,
            'markPrice': None,
            'lastPrice': None,
            'side': side,
            'hedged': None,
            'timestamp': None,
            'datetime': None,
            'lastUpdateTimestamp': None,
            'maintenanceMargin': None,
            'maintenanceMarginPercentage': None,
            'collateral': None,
            'initialMargin': self.safe_number(position, 'margin'),
            'initialMarginPercentage': None,
            'leverage': self.safe_number(position, 'leverage'),
            'marginRatio': None,
            'stopLossPrice': None,
            'takeProfitPrice': None,
        })

    def parse_account_type(self, account):
        accountByType = {
            'spot': 'Spot Wallet',
            'swap': 'Futures Wallet',
            'future': 'Futures Wallet',
        }
        return self.safe_string(accountByType, account, account)

    def transfer_out(self, code: str, amount, params={}):
        """
        transfer from spot wallet to futures wallet
        :see: https://docs.kraken.com/rest/#tag/User-Funding/operation/walletTransfer
        :param str code: Unified currency code
        :param float amount: Size of the transfer
        :param dict [params]: Exchange specific parameters
        :returns: a `transfer structure <https://docs.ccxt.com/#/?id=transfer-structure>`
        """
        return self.transfer(code, amount, 'spot', 'swap', params)

    def transfer(self, code: str, amount: float, fromAccount: str, toAccount: str, params={}) -> TransferEntry:
        """
        :see: https://docs.kraken.com/rest/#tag/User-Funding/operation/walletTransfer
        transfers currencies between sub-accounts(only spot->swap direction is supported)
        :param str code: Unified currency code
        :param float amount: Size of the transfer
        :param str fromAccount: 'spot' or 'Spot Wallet'
        :param str toAccount: 'swap' or 'Futures Wallet'
        :param dict [params]: Exchange specific parameters
        :returns: a `transfer structure <https://docs.ccxt.com/#/?id=transfer-structure>`
        """
        self.load_markets()
        currency = self.currency(code)
        fromAccount = self.parse_account_type(fromAccount)
        toAccount = self.parse_account_type(toAccount)
        request = {
            'amount': self.currency_to_precision(code, amount),
            'from': fromAccount,
            'to': toAccount,
            'asset': currency['id'],
        }
        if fromAccount != 'Spot Wallet':
            raise BadRequest(self.id + ' transfer cannot transfer from ' + fromAccount + ' to ' + toAccount + '. Use krakenfutures instead to transfer from the futures account.')
        response = self.privatePostWalletTransfer(self.extend(request, params))
        #
        #   {
        #       "error":[
        #       ],
        #       "result":{
        #          "refid":"BOIUSIF-M7DLMN-UXZ3P5"
        #       }
        #   }
        #
        transfer = self.parse_transfer(response, currency)
        return self.extend(transfer, {
            'amount': amount,
            'fromAccount': fromAccount,
            'toAccount': toAccount,
        })

    def parse_transfer(self, transfer, currency: Currency = None):
        #
        # transfer
        #
        #    {
        #        "error":[
        #        ],
        #        "result":{
        #           "refid":"BOIUSIF-M7DLMN-UXZ3P5"
        #        }
        #    }
        #
        result = self.safe_value(transfer, 'result', {})
        refid = self.safe_string(result, 'refid')
        return {
            'info': transfer,
            'id': refid,
            'timestamp': None,
            'datetime': None,
            'currency': self.safe_string(currency, 'code'),
            'amount': None,
            'fromAccount': None,
            'toAccount': None,
            'status': 'sucess',
        }

    def sign(self, path, api='public', method='GET', params={}, headers=None, body=None):
        url = '/' + self.version + '/' + api + '/' + path
        if api == 'public':
            if params:
                # urlencodeNested is used to address https://github.com/ccxt/ccxt/issues/12872
                url += '?' + self.urlencode_nested(params)
        elif api == 'private':
            isCancelOrderBatch = (path == 'CancelOrderBatch')
            self.check_required_credentials()
            nonce = str(self.nonce())
            # urlencodeNested is used to address https://github.com/ccxt/ccxt/issues/12872
            if isCancelOrderBatch:
                body = self.json(self.extend({'nonce': nonce}, params))
            else:
                body = self.urlencode_nested(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',
            }
            if isCancelOrderBatch:
                headers['Content-Type'] = 'application/json'
            else:
                headers['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 None
        if body[0] == '{':
            if not isinstance(response, str):
                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)
        return None
