# -*- 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 Any, Balances, Currencies, Currency, DepositAddress, IndexType, Int, LedgerEntry, Market, Num, Order, OrderBook, OrderSide, OrderType, Position, 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 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.errors import CancelPending
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) -> Any:
        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,
                'cancelAllOrdersAfter': True,
                'cancelOrder': True,
                'cancelOrders': True,
                'createDepositAddress': True,
                'createMarketBuyOrderWithCost': True,
                'createMarketOrderWithCost': False,
                'createMarketSellOrderWithCost': False,
                'createOrder': True,
                'createStopLimitOrder': True,
                'createStopMarketOrder': True,
                'createStopOrder': True,
                'createTrailingAmountOrder': True,
                'createTrailingPercentOrder': True,
                'editOrder': True,
                'fetchBalance': True,
                'fetchBorrowInterest': False,
                'fetchBorrowRateHistories': False,
                'fetchBorrowRateHistory': False,
                'fetchClosedOrders': True,
                'fetchCrossBorrowRate': False,
                'fetchCrossBorrowRates': False,
                'fetchCurrencies': True,
                'fetchDepositAddress': True,
                'fetchDepositAddresses': False,
                'fetchDepositAddressesByNetwork': False,
                '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,
                'fetchStatus': True,
                '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.2,
                        'OHLC': 1.2,  # 1.2 because 1 triggers too many requests immediately
                        'Spread': 1,
                        'SystemStatus': 1,
                        'Ticker': 1,
                        'Time': 1,
                        'Trades': 1.2,
                    },
                },
                'private': {
                    'post': {
                        'AddOrder': 0,
                        'AddOrderBatch': 0,
                        'AddExport': 3,
                        'AmendOrder': 0,
                        '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': {
                # about X & Z prefixes and .S & .M suffixes, see comment under fetchCurrencies
                'LUNA': 'LUNC',
                'LUNA2': 'LUNA',
                'REPV2': 'REP',
                'REP': 'REPV1',
                'UST': 'USTC',
                'XBT': 'BTC',
                'XDG': 'DOGE',
            },
            'options': {
                'timeDifference': 0,  # the difference between system clock and Binance clock
                'adjustForTimeDifference': False,  # controls the adjustment logic upon instantiation
                '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',
                },
            },
            'features': {
                'spot': {
                    'sandbox': False,
                    'createOrder': {
                        'marginMode': False,
                        'triggerPrice': False,  # todo
                        'triggerPriceType': None,
                        'triggerDirection': False,
                        'stopLossPrice': True,
                        'takeProfitPrice': True,
                        'attachedStopLossTakeProfit': None,
                        'timeInForce': {
                            'IOC': True,
                            'FOK': True,
                            'PO': True,
                            'GTD': False,
                        },
                        'hedged': False,
                        'trailing': True,
                        'leverage': False,
                        'marketBuyByCost': True,
                        'marketBuyRequiresPrice': False,
                        'selfTradePrevention': True,  # todo implement
                        'iceberg': True,  # todo implement
                    },
                    'createOrders': None,
                    'fetchMyTrades': {
                        'marginMode': False,
                        'limit': None,
                        'daysBack': None,
                        'untilDays': None,
                        'symbolRequired': False,
                    },
                    'fetchOrder': {
                        'marginMode': False,
                        'trigger': False,
                        'trailing': False,
                        'symbolRequired': False,
                    },
                    'fetchOpenOrders': {
                        'marginMode': False,
                        'limit': None,
                        'trigger': False,
                        'trailing': False,
                        'symbolRequired': False,
                    },
                    'fetchOrders': None,
                    'fetchClosedOrders': {
                        'marginMode': False,
                        'limit': None,
                        'daysBack': None,
                        'daysBackCanceled': None,
                        'untilDays': 100000,
                        'trigger': False,
                        'trailing': False,
                        'symbolRequired': False,
                    },
                    'fetchOHLCV': {
                        'limit': 720,
                    },
                },
                'swap': {
                    'linear': None,
                    'inverse': None,
                },
                'future': {
                    'linear': None,
                    'inverse': None,
                },
            },
            'precisionMode': TICK_SIZE,
            'exceptions': {
                'exact': {
                    '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,
                    'EGeneral:Invalid arguments:price': InvalidOrder,
                    'EOrder:Unknown order': InvalidOrder,
                    'EOrder:Invalid price:Invalid price argument': InvalidOrder,
                    'EOrder:Order minimum not met': InvalidOrder,
                    'EOrder:Insufficient funds': InsufficientFunds,
                    '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"]}
                },
                'broad': {
                    ':Invalid order': InvalidOrder,
                    ':Invalid arguments:volume': InvalidOrder,
                    ':Invalid arguments:viqc': InvalidOrder,
                    ':Invalid nonce': InvalidNonce,
                    ':IInsufficient funds': InsufficientFunds,
                    ':Cancel pending': CancelPending,
                    ':Rate limit exceeded': RateLimitExceeded,
                },
            },
        })

    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

        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
        """
        promises = []
        promises.append(self.publicGetAssetPairs(params))
        if self.options['adjustForTimeDifference']:
            promises.append(self.load_time_difference())
        responses = promises
        assetsResponse = responses[0]
        #
        #     {
        #         "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_dict(assetsResponse, 'result', {})
        cachedCurrencies = self.safe_dict(self.options, 'cachedCurrencies', {})
        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)
            makerFees = self.safe_list(market, 'fees_maker', [])
            firstMakerFee = self.safe_list(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_list(market, 'fees', [])
            firstTakerFee = self.safe_list(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_list(market, 'leverage_buy', [])
            leverageBuyLength = len(leverageBuy)
            precisionPrice = self.parse_number(self.parse_precision(self.safe_string(market, 'pair_decimals')))
            precisionAmount = self.parse_number(self.parse_precision(self.safe_string(market, 'lot_decimals')))
            spot = True
            # fix https://github.com/freqtrade/freqtrade/issues/11765#issuecomment-2894224103
            if spot and (base in cachedCurrencies):
                currency = cachedCurrencies[base]
                currencyPrecision = self.safe_number(currency, 'precision')
                # if currency precision is greater(e.g. 0.01) than market precision(e.g. 0.001)
                if currencyPrecision > precisionAmount:
                    precisionAmount = currencyPrecision
            status = self.safe_string(market, 'status')
            isActive = status == 'online'
            result.append({
                'id': id,
                'wsId': self.safe_string(market, 'wsname'),
                'symbol': base + '/' + quote,
                'base': base,
                'quote': quote,
                'settle': None,
                'baseId': baseId,
                'quoteId': quoteId,
                'settleId': None,
                'altname': market['altname'],
                'type': 'spot',
                'spot': spot,
                'margin': (leverageBuyLength > 0),
                'swap': False,
                'future': False,
                'option': False,
                'active': isActive,
                'contract': False,
                'linear': None,
                'inverse': None,
                'taker': taker,
                'maker': maker,
                'contractSize': None,
                'expiry': None,
                'expiryDatetime': None,
                'strike': None,
                'optionType': None,
                'precision': {
                    'amount': precisionAmount,
                    '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': None,
                        'max': None,
                    },
                    'cost': {
                        'min': self.safe_number(market, 'costmin'),
                        'max': None,
                    },
                },
                'created': None,
                'info': market,
            })
        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) and (currencyId != 'ZEUS'):
                        currencyId = currencyId[1:]
        return super(kraken, self).safe_currency(currencyId, currency)

    def fetch_status(self, params={}):
        """
        the latest known information on the availability of the exchange API

        https://docs.kraken.com/api/docs/rest-api/get-system-status/

        :param dict [params]: extra parameters specific to the exchange API endpoint
        :returns dict: a `status structure <https://docs.ccxt.com/#/?id=exchange-status-structure>`
        """
        response = self.publicGetSystemStatus(params)
        #
        # {
        #     error: [],
        #     result: {status: 'online', timestamp: '2024-07-22T16:34:44Z'}
        # }
        #
        result = self.safe_dict(response, 'result')
        statusRaw = self.safe_string(result, 'status')
        return {
            'status': 'ok' if (statusRaw == 'online') else 'maintenance',
            'updated': None,
            'eta': None,
            'url': None,
            'info': response,
        }

    def fetch_currencies(self, params={}) -> Currencies:
        """
        fetches all available currencies on an exchange

        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": {
        #             "ATOM": {
        #                 "aclass": "currency",
        #                 "altname": "ATOM",
        #                 "collateral_value": "0.7",
        #                 "decimals": 8,
        #                 "display_decimals": 6,
        #                 "margin_rate": 0.02,
        #                 "status": "enabled",
        #             },
        #             "ATOM.S": {
        #                 "aclass": "currency",
        #                 "altname": "ATOM.S",
        #                 "decimals": 8,
        #                 "display_decimals": 6,
        #                 "status": "enabled",
        #             },
        #             "XXBT": {
        #                 "aclass": "currency",
        #                 "altname": "XBT",
        #                 "decimals": 10,
        #                 "display_decimals": 5,
        #                 "margin_rate": 0.01,
        #                 "status": "enabled",
        #             },
        #             "XETH": {
        #                 "aclass": "currency",
        #                 "altname": "ETH",
        #                 "decimals": 10,
        #                 "display_decimals": 5
        #                 "margin_rate": 0.02,
        #                 "status": "enabled",
        #             },
        #             "XBT.M": {
        #                 "aclass": "currency",
        #                 "altname": "XBT.M",
        #                 "decimals": 10,
        #                 "display_decimals": 5
        #                 "status": "enabled",
        #             },
        #             "ETH.M": {
        #                 "aclass": "currency",
        #                 "altname": "ETH.M",
        #                 "decimals": 10,
        #                 "display_decimals": 5
        #                 "status": "enabled",
        #             },
        #             ...
        #         },
        #     }
        #
        currencies = self.safe_value(response, 'result', {})
        ids = list(currencies.keys())
        result: dict = {}
        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
            #
            # Notes about abbreviations:
            # Z and X prefixes: https://support.kraken.com/hc/en-us/articles/360001206766-Bitcoin-currency-code-XBT-vs-BTC
            # S and M suffixes: https://support.kraken.com/hc/en-us/articles/360039879471-What-is-Asset-S-and-Asset-M-
            #
            code = self.safe_currency_code(id)
            # the below can not be reliable done in `safeCurrencyCode`, so we have to do it here
            if id.find('.') < 0:
                altName = self.safe_string(currency, 'altname')
                # handle cases like below:
                #
                #  id   | altname
                # ---------------
                # XXBT  |  XBT
                # ZUSD  |  USD
                if id != altName and (id.startswith('X') or id.startswith('Z')):
                    code = self.safe_currency_code(altName)
                    # also, add map in commonCurrencies:
                    self.commonCurrencies[id] = code
                else:
                    code = self.safe_currency_code(id)
            isFiat = code.find('.HOLD') >= 0
            result[code] = self.safe_currency_structure({
                'id': id,
                'code': code,
                'info': currency,
                'name': self.safe_string(currency, 'altname'),
                'active': self.safe_string(currency, 'status') == 'enabled',
                'type': 'fiat' if isFiat else 'crypto',
                'deposit': None,
                'withdraw': None,
                'fee': None,
                'precision': self.parse_number(self.parse_precision(self.safe_string(currency, 'decimals'))),
                'limits': {
                    'amount': {
                        'min': None,
                        'max': None,
                    },
                    'withdraw': {
                        'min': None,
                        'max': None,
                    },
                },
                'networks': {},
            })
        return result

    def safe_currency_code(self, currencyId: Str, currency: Currency = None) -> Str:
        if currencyId is None:
            return currencyId
        if currencyId.find('.') > 0:
            # if ID contains .M, .S or .F, then it can't contain X or Z prefix. in such case, ID equals to ALTNAME
            parts = currencyId.split('.')
            firstPart = self.safe_string(parts, 0)
            secondPart = self.safe_string(parts, 1)
            return super(kraken, self).safe_currency_code(firstPart, currency) + '.' + secondPart
        return super(kraken, self).safe_currency_code(currencyId, currency)

    def fetch_trading_fee(self, symbol: str, params={}) -> TradingFeeInterface:
        """
        fetch the trading fees for a market

        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: dict = {
            '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.parse_number(Precise.string_div(self.safe_string(symbolMakerFee, 'fee'), '100')),
            'taker': self.parse_number(Precise.string_div(self.safe_string(symbolTakerFee, 'fee'), '100')),
            '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

        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)
        request: dict = {
            '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: dict, 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': self.safe_string(bid, 2),
            'ask': self.safe_string(ask, 0),
            'askVolume': self.safe_string(ask, 2),
            '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

        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: dict = {}
        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']:
                    marketIds.append(market['id'])
            request['pair'] = ','.join(marketIds)
        response = self.publicGetTicker(self.extend(request, params))
        tickers = response['result']
        ids = list(tickers.keys())
        result: dict = {}
        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

        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()
        market = self.market(symbol)
        request: dict = {
            '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

        https://docs.kraken.com/api/docs/rest-api/get-ohlc-data

        :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: dict = {
            'pair': market['id'],
        }
        if parsedTimeframe is not None:
            request['interval'] = parsedTimeframe
        else:
            request['interval'] = timeframe
        if since is not None:
            scaledSince = self.parse_to_int(since / 1000)
            timeFrameInSeconds = parsedTimeframe * 60
            request['since'] = self.number_to_string(scaledSince - timeFrameInSeconds)  # expected to be in seconds
        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: dict = {
            'trade': 'trade',
            'withdrawal': 'transaction',
            'deposit': 'transaction',
            'transfer': 'transfer',
            'margin': 'margin',
        }
        return self.safe_string(types, type, type)

    def parse_ledger_entry(self, item: dict, currency: Currency = None) -> LedgerEntry:
        #
        #     {
        #         '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'))
        currencyId = self.safe_string(item, 'asset')
        code = self.safe_currency_code(currencyId, currency)
        currency = self.safe_currency(currencyId, 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_integer_product(item, 'time', 1000)
        return self.safe_ledger_entry({
            '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,
            },
        }, currency)

    def fetch_ledger(self, code: Str = None, since: Int = None, limit: Int = None, params={}) -> List[LedgerEntry]:
        """
        fetch the history of changes, actions done by the user or operations that altered the balance of the user

        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 entries 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
        :param int [params.end]: timestamp in seconds of the latest ledger entry
        :returns dict: a `ledger structure <https://docs.ccxt.com/#/?id=ledger>`
        """
        # https://www.kraken.com/features/api#get-ledgers-info
        self.load_markets()
        request: dict = {}
        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)
        until = self.safe_string_n(params, ['until', 'till'])
        if until is not None:
            params = self.omit(params, ['until', 'till'])
            untilDivided = Precise.string_div(until, '1000')
            request['end'] = self.parse_to_int(Precise.string_add(untilDivided, '1'))
        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={}) -> LedgerEntry:
        items = self.fetch_ledger_entries_by_ids([id], code, params)
        return items[0]

    def parse_trade(self, trade: dict, 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": ''
        #     }
        #
        # fetchMyTrades
        #
        #     {
        #         "ordertxid": "OSJVN7-A2AE-63WZV",
        #         "postxid": "TBP7O6-PNXI-CONU",
        #         "pair": "XXBTZUSD",
        #         "time": 1710429248.3052235,
        #         "type": "sell",
        #         "ordertype": "liquidation market",
        #         "price": "72026.50000",
        #         "cost": "7.20265",
        #         "fee": "0.01873",
        #         "vol": "0.00010000",
        #         "margin": "1.44053",
        #         "leverage": "5",
        #         "misc": "closing",
        #         "trade_id": 68230622,
        #         "maker": False
        #     }
        #
        # watchTrades
        #
        #     {
        #         "symbol": "BTC/USD",
        #         "side": "buy",
        #         "price": 109601.2,
        #         "qty": 0.04561994,
        #         "ord_type": "market",
        #         "trade_id": 83449369,
        #         "timestamp": "2025-05-27T11:24:03.847761Z"
        #     }
        #
        timestamp = None
        datetime = 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,
                }
        else:
            symbol = self.safe_string(trade, 'symbol')
            datetime = self.safe_string(trade, 'timestamp')
            id = self.safe_string(trade, 'trade_id')
            side = self.safe_string(trade, 'side')
            type = self.safe_string(trade, 'ord_type')
            price = self.safe_string(trade, 'price')
            amount = self.safe_string(trade, 'qty')
        if market is not None:
            symbol = market['symbol']
        cost = self.safe_string(trade, 'cost')
        maker = self.safe_bool(trade, 'maker')
        takerOrMaker = None
        if maker is not None:
            takerOrMaker = 'maker' if maker else 'taker'
        if datetime is None:
            datetime = self.iso8601(timestamp)
        else:
            timestamp = self.parse8601(datetime)
        return self.safe_trade({
            'id': id,
            'order': orderId,
            'info': trade,
            'timestamp': timestamp,
            'datetime': datetime,
            'symbol': symbol,
            'type': type,
            'side': side,
            'takerOrMaker': takerOrMaker,
            '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

        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: dict = {
            '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:
            request['since'] = self.number_to_string(self.parse_to_int(since / 1000))  # expected to be in seconds
        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: dict = {
            '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

        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_market_order_with_cost(self, symbol: str, side: OrderSide, cost: float, params={}):
        """
        create a market order by providing the symbol, side and cost

        https://docs.kraken.com/rest/#tag/Spot-Trading/operation/addOrder

        :param str symbol: unified symbol of the market to create an order in(only USD markets are supported)
        :param str side: 'buy' or 'sell'
        :param float cost: how much you want to trade in units of the quote currency
        :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()
        # only buy orders are supported by the endpoint
        req = {
            'cost': cost,
        }
        return self.create_order(symbol, 'market', side, cost, None, self.extend(req, params))

    def create_market_buy_order_with_cost(self, symbol: str, cost: float, params={}):
        """
        create a market buy order by providing the symbol, side and cost

        https://docs.kraken.com/rest/#tag/Spot-Trading/operation/addOrder

        :param str symbol: unified symbol of the market to create an order in
        :param float cost: how much you want to trade in units of the quote currency
        :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()
        return self.create_market_order_with_cost(symbol, 'buy', cost, params)

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

        https://docs.kraken.com/api/docs/rest-api/add-order

        :param str symbol: unified symbol of the market to create an order in
        :param str type: 'market' or 'limit'
        :param str side: 'buy' or 'sell'
        :param float amount: how much of currency you want to trade in units of base currency
        :param float [price]: the price at which the order is to be fulfilled, in units of the quote currency, ignored in market orders
        :param dict [params]: extra parameters specific to the exchange API endpoint
        :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.trailingPercent]: *margin only* the percent to trail away from the current market price
        :param str [params.trailingLimitAmount]: *margin only* the quote amount away from the trailingAmount
        :param str [params.trailingLimitPercent]: *margin only* the percent 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: dict = {
            'pair': market['id'],
            'type': side,
            'ordertype': type,
            'volume': self.amount_to_precision(symbol, amount),
        }
        orderRequest = self.order_request('createOrder', symbol, type, request, amount, price, params)
        flags = self.safe_string(orderRequest[0], 'oflags', '')
        isUsingCost = flags.find('viqc') > -1
        response = self.privatePostAddOrder(self.extend(orderRequest[0], orderRequest[1]))
        #
        #     {
        #         "error": [],
        #         "result": {
        #             "descr": {order: 'buy 0.02100000 ETHUSDT @ limit 330.00'},  # see more examples in "parseOrder"
        #             "txid": ['OEKVV2-IH52O-TPL6GZ']
        #         }
        #     }
        #
        result = self.safe_dict(response, 'result')
        result['usingCost'] = isUsingCost
        # it's impossible to know if the order was created using cost or base currency
        # becuase kraken only returns something like self: {order: 'buy 10.00000000 LTCUSD @ market'}
        # self usingCost flag is used to help the parsing but omited from the order
        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: Str):
        statuses: dict = {
            '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: dict = {
            # we dont add "space" delimited orders here(eg. stop loss) because they need separate parsing
            'take-profit': 'market',
            'stop-loss': 'market',
            'stop-loss-limit': 'limit',
            'take-profit-limit': 'limit',
            'trailing-stop-limit': 'limit',
        }
        return self.safe_string(statuses, status, status)

    def parse_order(self, order: dict, market: Market = None) -> Order:
        #
        # createOrder
        #
        #     {
        #         "descr": {
        #            "order": "buy 0.02100000 ETHUSDT @ limit 330.00"  # limit orders
        #                     "buy 0.12345678 ETHUSDT @ market"  # market order
        #                     "sell 0.28002676 ETHUSDT @ stop loss 0.0123 -> limit 0.0.1222"  # stop order
        #                     "sell 0.00100000 ETHUSDT @ stop loss 2677.00 -> limit 2577.00 with 5:1 leverage"
        #                     "buy 0.10000000 LTCUSDT @ take profit 75.00000 -> limit 74.00000"
        #                     "sell 10.00000000 XRPEUR @ trailing stop +50.0000%"  # trailing stop
        #         },
        #         "txid": ['OEKVV2-IH52O-TPL6GZ']
        #     }
        #
        # editOrder
        #
        #     {
        #         "amend_id": "TJSMEH-AA67V-YUSQ6O"
        #     }
        #
        #  ws - createOrder
        #     {
        #         "order_id": "OXM2QD-EALR2-YBAVEU"
        #     }
        #
        #  ws - editOrder
        #     {
        #         "amend_id": "TJSMEH-AA67V-YUSQ6O",
        #         "order_id": "OXM2QD-EALR2-YBAVEU"
        #     }
        #
        #  {
        #      "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"
        #              }
        #      }
        #  }
        #
        # fetchOpenOrders
        #
        #      {
        #         "refid": null,
        #         "userref": null,
        #         "cl_ord_id": "1234",
        #         "status": "open",
        #         "opentm": 1733815269.370054,
        #         "starttm": 0,
        #         "expiretm": 0,
        #         "descr": {
        #             "pair": "XBTUSD",
        #             "type": "buy",
        #             "ordertype": "limit",
        #             "price": "70000.0",
        #             "price2": "0",
        #             "leverage": "none",
        #             "order": "buy 0.00010000 XBTUSD @ limit 70000.0",
        #             "close": ""
        #         },
        #         "vol": "0.00010000",
        #         "vol_exec": "0.00000000",
        #         "cost": "0.00000",
        #         "fee": "0.00000",
        #         "price": "0.00000",
        #         "stopprice": "0.00000",
        #         "limitprice": "0.00000",
        #         "misc": "",
        #         "oflags": "fciq"
        #     }
        #
        isUsingCost = self.safe_bool(order, 'usingCost', False)
        order = self.omit(order, 'usingCost')
        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
        rawType = None
        marketId = None
        price = None
        amount = None
        cost = None
        triggerPrice = None
        if orderDescription is not None:
            parts = orderDescription.split(' ')
            side = self.safe_string(parts, 0)
            if not isUsingCost:
                amount = self.safe_string(parts, 1)
            else:
                cost = self.safe_string(parts, 1)
            marketId = self.safe_string(parts, 2)
            part4 = self.safe_string(parts, 4)
            part5 = self.safe_string(parts, 5)
            if part4 == 'limit' or part4 == 'market':
                rawType = part4  # eg, limit, market
            else:
                rawType = part4 + ' ' + part5  # eg. stop loss, take profit, trailing stop
            if rawType == 'stop loss' or rawType == 'take profit':
                triggerPrice = self.safe_string(parts, 6)
                price = self.safe_string(parts, 9)
            elif rawType == 'limit':
                price = self.safe_string(parts, 5)
        side = self.safe_string(description, 'type', side)
        rawType = self.safe_string(description, 'ordertype', rawType)  # orderType has dash, e.g. trailing-stop
        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 = trailing stop returns price = '+50.0000%'
        if (price is not None) and (price.endswith('%') or Precise.string_equals(price, '0.00000') or Precise.string_equals(price, '0')):
            price = None  # self is not the price we want
        if price is None:
            price = self.safe_string(description, 'price2')
            price = self.safe_string_2(order, 'limitprice', '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_n(order, ['id', 'txid', 'order_id', 'amend_id'])
        if (id is None) or (id.startswith('[')):
            txid = self.safe_list(order, 'txid')
            id = self.safe_string(txid, 0)
        userref = self.safe_string(order, 'userref')
        clientOrderId = self.safe_string(order, 'cl_ord_id', 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)
        # in  #24192 PR, self field is not something consistent/actual
        # triggerPrice = self.omit_zero(self.safe_string(order, 'stopprice', triggerPrice))
        stopLossPrice = None
        takeProfitPrice = None
        # the dashed strings are not provided from fields(eg. fetch order)
        # while spaced strings from "order" sentence(when other fields not available)
        if rawType is not None:
            if rawType.startswith('take-profit'):
                takeProfitPrice = self.safe_string(description, 'price')
                price = self.omit_zero(self.safe_string(description, 'price2'))
            elif rawType.startswith('stop-loss'):
                stopLossPrice = self.safe_string(description, 'price')
                price = self.omit_zero(self.safe_string(description, 'price2'))
            elif rawType == 'take profit':
                takeProfitPrice = triggerPrice
            elif rawType == 'stop loss':
                stopLossPrice = triggerPrice
        finalType = self.parse_order_type(rawType)
        # unlike from endpoints which provide eg: "take-profit-limit"
        # for "space-delimited" orders we dont have market/limit suffixes, their format is
        # eg: `stop loss > limit 123`, so we need to parse them manually
        if self.in_array(finalType, ['stop loss', 'take profit']):
            finalType = 'market' if (price is None) else 'limit'
        amendId = self.safe_string(order, 'amend_id')
        if amendId is not None:
            isPostOnly = None
        return self.safe_order({
            'id': id,
            'clientOrderId': clientOrderId,
            'info': order,
            'timestamp': timestamp,
            'datetime': self.iso8601(timestamp),
            'lastTradeTimestamp': None,
            'status': status,
            'symbol': symbol,
            'type': finalType,
            'timeInForce': None,
            'postOnly': isPostOnly,
            'side': side,
            'price': price,
            'triggerPrice': triggerPrice,
            'takeProfitPrice': takeProfitPrice,
            'stopLossPrice': stopLossPrice,
            'cost': cost,
            'amount': amount,
            'filled': filled,
            'average': average,
            'remaining': None,
            'reduceOnly': self.safe_bool_2(order, 'reduceOnly', 'reduce_only'),
            'fee': fee,
            'trades': trades,
        }, market)

    def order_request(self, method: str, symbol: str, type: str, request: dict, amount: Num, price: Num = None, params={}):
        clientOrderId = self.safe_string(params, 'clientOrderId')
        params = self.omit(params, ['clientOrderId'])
        if clientOrderId is not None:
            request['cl_ord_id'] = 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')
        trailingPercent = self.safe_string(params, 'trailingPercent')
        trailingLimitAmount = self.safe_string(params, 'trailingLimitAmount')
        trailingLimitPercent = self.safe_string(params, 'trailingLimitPercent')
        isTrailingAmountOrder = trailingAmount is not None
        isTrailingPercentOrder = trailingPercent is not None
        isLimitOrder = type.endswith('limit')  # supporting limit, stop-loss-limit, take-profit-limit, etc
        isMarketOrder = type == 'market'
        cost = self.safe_string(params, 'cost')
        flags = self.safe_string(params, 'oflags')
        params = self.omit(params, ['cost', 'oflags'])
        isViqcOrder = (flags is not None) and (flags.find('viqc') > -1)  # volume in quote currency
        if isMarketOrder and (cost is not None or isViqcOrder):
            if cost is None and (amount is not None):
                request['volume'] = self.cost_to_precision(symbol, self.number_to_string(amount))
            else:
                request['volume'] = self.cost_to_precision(symbol, cost)
            extendedOflags = flags + ',viqc' if (flags is not None) else 'viqc'
            request['oflags'] = extendedOflags
        elif isLimitOrder and not isTrailingAmountOrder and not isTrailingPercentOrder:
            request['price'] = self.price_to_precision(symbol, price)
        reduceOnly = self.safe_bool_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 or isTrailingPercentOrder:
            trailingPercentString = None
            if trailingPercent is not None:
                trailingPercentString = ('+' + trailingPercent) if (trailingPercent.endswith('%')) else ('+' + trailingPercent + '%')
            trailingAmountString = '+' + trailingAmount if (trailingAmount is not None) else None  # must use + for self
            offset = self.safe_string(params, 'offset', '-')  # can use + or - for self
            trailingLimitAmountString = offset + self.number_to_string(trailingLimitAmount) if (trailingLimitAmount is not None) else None
            trailingActivationPriceType = self.safe_string(params, 'trigger', 'last')
            request['trigger'] = trailingActivationPriceType
            if isLimitOrder or (trailingLimitAmount is not None) or (trailingLimitPercent is not None):
                request['ordertype'] = 'trailing-stop-limit'
                if trailingLimitPercent is not None:
                    trailingLimitPercentString = (offset + trailingLimitPercent) if (trailingLimitPercent.endswith('%')) else (offset + trailingLimitPercent + '%')
                    request['price'] = trailingPercentString
                    request['price2'] = trailingLimitPercentString
                elif trailingLimitAmount is not None:
                    request['price'] = trailingAmountString
                    request['price2'] = trailingLimitAmountString
            else:
                request['ordertype'] = 'trailing-stop'
                if trailingPercent is not None:
                    request['price'] = trailingPercentString
                else:
                    request['price'] = trailingAmountString
        if reduceOnly:
            if method == 'createOrderWs':
                request['reduce_only'] = True  # ws request can't have stringified bool
            else:
                request['reduce_only'] = 'true'  # not using hasattr(self, boolean) case, because the urlencodedNested transforms it into 'True' string
        close = self.safe_dict(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:
            extendedPostFlags = flags + ',post' if (flags is not None) else 'post'
            request['oflags'] = extendedPostFlags
        if (flags is not None) and not ('oflags' in request):
            request['oflags'] = flags
        params = self.omit(params, ['timeInForce', 'reduceOnly', 'stopLossPrice', 'takeProfitPrice', 'trailingAmount', 'trailingPercent', 'trailingLimitAmount', 'trailingLimitPercent', '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

        https://docs.kraken.com/api/docs/rest-api/amend-order

        :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 fulfilled, 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]: the price that a stop loss order is triggered at
        :param float [params.takeProfitPrice]: the price that a take profit order is triggered at
        :param str [params.trailingAmount]: the quote amount to trail away from the current market price
        :param str [params.trailingPercent]: the percent to trail away from the current market price
        :param str [params.trailingLimitAmount]: the quote amount away from the trailingAmount
        :param str [params.trailingLimitPercent]: the percent away from the trailingAmount
        :param str [params.offset]: '+' or '-' whether you want the trailingLimitAmount value to be positive or negative
        :param boolean [params.postOnly]: if True, the order will only be posted to the order book and not executed immediately
        :param str [params.clientOrderId]: the orders client order id
        :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: dict = {
            'txid': id,
        }
        clientOrderId = self.safe_string_2(params, 'clientOrderId', 'cl_ord_id')
        if clientOrderId is not None:
            request['cl_ord_id'] = clientOrderId
            params = self.omit(params, ['clientOrderId', 'cl_ord_id'])
            request = self.omit(request, 'txid')
        isMarket = (type == 'market')
        postOnly = None
        postOnly, params = self.handle_post_only(isMarket, False, params)
        if postOnly:
            request['post_only'] = 'true'  # not using hasattr(self, boolean) case, because the urlencodedNested transforms it into 'True' string
        if amount is not None:
            request['order_qty'] = self.amount_to_precision(symbol, amount)
        if price is not None:
            request['limit_price'] = self.price_to_precision(symbol, price)
        allTriggerPrices = self.safe_string_n(params, ['stopLossPrice', 'takeProfitPrice', 'trailingAmount', 'trailingPercent', 'trailingLimitAmount', 'trailingLimitPercent'])
        if allTriggerPrices is not None:
            offset = self.safe_string(params, 'offset')
            params = self.omit(params, ['stopLossPrice', 'takeProfitPrice', 'trailingAmount', 'trailingPercent', 'trailingLimitAmount', 'trailingLimitPercent', 'offset'])
            if offset is not None:
                allTriggerPrices = offset + allTriggerPrices
                request['trigger_price'] = allTriggerPrices
            else:
                request['trigger_price'] = self.price_to_precision(symbol, allTriggerPrices)
        response = self.privatePostAmendOrder(self.extend(request, params))
        #
        #     {
        #         "error": [],
        #         "result": {
        #             "amend_id": "TJSMEH-AA67V-YUSQ6O"
        #         }
        #     }
        #
        result = self.safe_dict(response, 'result', {})
        return self.parse_order(result, market)

    def fetch_order(self, id: str, symbol: Str = None, params={}):
        """
        fetches information on an order made by the user

        https://docs.kraken.com/rest/#tag/Account-Data/operation/getOrdersInfo

        :param str id: order id
        :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: dict = {
            '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'])
        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

        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: dict = {
                '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

        https://docs.kraken.com/rest/#tag/Account-Data/operation/getClosedOrders

        :param str[] [ids]: list of order id
        :param str [symbol]: unified ccxt market symbol
        :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

        https://docs.kraken.com/api/docs/rest-api/get-trade-history

        :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
        :param int [params.until]: timestamp in ms of the latest trade entry
        :param int [params.end]: timestamp in seconds of the latest trade entry
        :returns Trade[]: a list of `trade structures <https://docs.ccxt.com/#/?id=trade-structure>`
        """
        self.load_markets()
        request: dict = {
            # '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)
        until = self.safe_string_n(params, ['until', 'till'])
        if until is not None:
            params = self.omit(params, ['until', 'till'])
            untilDivided = Precise.string_div(until, '1000')
            request['end'] = self.parse_to_int(Precise.string_add(untilDivided, '1'))
        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",
        #                     "leverage": "5",
        #                     "misc": ""
        #                     "trade_id": 68230622,
        #                     "maker": False
        #                 },
        #                 ...
        #             },
        #             "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

        https://docs.kraken.com/api/docs/rest-api/cancel-order

        :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
        :param str [params.clientOrderId]: the orders client order id
        :param int [params.userref]: the orders user reference id
        :returns dict: an `order structure <https://docs.ccxt.com/#/?id=order-structure>`
        """
        self.load_markets()
        response = None
        requestId = self.safe_value(params, 'userref', id)  # string or integer
        params = self.omit(params, 'userref')
        request: dict = {
            'txid': requestId,  # order id or userref
        }
        clientOrderId = self.safe_string_2(params, 'clientOrderId', 'cl_ord_id')
        if clientOrderId is not None:
            request['cl_ord_id'] = clientOrderId
            params = self.omit(params, ['clientOrderId', 'cl_ord_id'])
            request = self.omit(request, 'txid')
        try:
            response = self.privatePostCancelOrder(self.extend(request, params))
            #
            #    {
            #        error: [],
            #        result: {
            #            count: '1'
            #        }
            #    }
            #
        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 self.safe_order({
            'info': response,
        })

    def cancel_orders(self, ids, symbol: Str = None, params={}):
        """
        cancel multiple orders

        https://docs.kraken.com/rest/#tag/Spot-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: dict = {
            'orders': ids,
        }
        response = self.privatePostCancelOrderBatch(self.extend(request, params))
        #
        #     {
        #         "error": [],
        #         "result": {
        #           "count": 2
        #         }
        #     }
        #
        return [
            self.safe_order({
                'info': response,
            }),
        ]

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

        https://docs.kraken.com/rest/#tag/Spot-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()
        response = self.privatePostCancelAll(params)
        #
        #    {
        #        error: [],
        #        result: {
        #            count: '1'
        #        }
        #    }
        #
        return [
            self.safe_order({
                'info': response,
            }),
        ]

    def cancel_all_orders_after(self, timeout: Int, params={}):
        """
        dead man's switch, cancel all orders after the given timeout

        https://docs.kraken.com/rest/#tag/Spot-Trading/operation/cancelAllOrdersAfter

        :param number timeout: time in milliseconds, 0 represents cancel the timer
        :param dict [params]: extra parameters specific to the exchange API endpoint
        :returns dict: the api result
        """
        if timeout > 86400000:
            raise BadRequest(self.id + ' cancelAllOrdersAfter timeout should be less than 86400000 milliseconds')
        self.load_markets()
        request: dict = {
            'timeout': (self.parse_to_int(timeout / 1000)) if (timeout > 0) else 0,
        }
        response = self.privatePostCancelAllOrdersAfter(self.extend(request, params))
        #
        #     {
        #         "error": [],
        #         "result": {
        #             "currentTime": "2023-03-24T17:41:56Z",
        #             "triggerTime": "2023-03-24T17:42:56Z"
        #         }
        #     }
        #
        return response

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

        https://docs.kraken.com/api/docs/rest-api/get-open-orders

        :param str [symbol]: unified market symbol
        :param int [since]: the earliest time in ms to fetch open orders for
        :param int [limit]: the maximum number of  open orders structures to retrieve
        :param dict [params]: extra parameters specific to the exchange API endpoint
        :param str [params.clientOrderId]: the orders client order id
        :param int [params.userref]: the orders user reference id
        :returns Order[]: a list of `order structures <https://docs.ccxt.com/#/?id=order-structure>`
        """
        self.load_markets()
        request: dict = {}
        if since is not None:
            request['start'] = self.parse_to_int(since / 1000)
        userref = self.safe_integer(params, 'userref')
        if userref is not None:
            request['userref'] = userref
            params = self.omit(params, 'userref')
        clientOrderId = self.safe_string(params, 'clientOrderId')
        if clientOrderId is not None:
            request['cl_ord_id'] = clientOrderId
            params = self.omit(params, 'clientOrderId')
        response = self.privatePostOpenOrders(self.extend(request, params))
        #
        #     {
        #         "error": [],
        #         "result": {
        #             "open": {
        #                 "O45M52-BFD5S-YXKQOU": {
        #                     "refid": null,
        #                     "userref": null,
        #                     "cl_ord_id": "1234",
        #                     "status": "open",
        #                     "opentm": 1733815269.370054,
        #                     "starttm": 0,
        #                     "expiretm": 0,
        #                     "descr": {
        #                         "pair": "XBTUSD",
        #                         "type": "buy",
        #                         "ordertype": "limit",
        #                         "price": "70000.0",
        #                         "price2": "0",
        #                         "leverage": "none",
        #                         "order": "buy 0.00010000 XBTUSD @ limit 70000.0",
        #                         "close": ""
        #                     },
        #                     "vol": "0.00010000",
        #                     "vol_exec": "0.00000000",
        #                     "cost": "0.00000",
        #                     "fee": "0.00000",
        #                     "price": "0.00000",
        #                     "stopprice": "0.00000",
        #                     "limitprice": "0.00000",
        #                     "misc": "",
        #                     "oflags": "fciq"
        #                 }
        #             }
        #         }
        #     }
        #
        market = None
        if symbol is not None:
            market = self.market(symbol)
        result = self.safe_dict(response, 'result', {})
        open = self.safe_dict(result, 'open', {})
        orders = []
        orderIds = list(open.keys())
        for i in range(0, len(orderIds)):
            id = orderIds[i]
            item = open[id]
            orders.append(self.extend({'id': id}, item))
        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

        https://docs.kraken.com/api/docs/rest-api/get-closed-orders

        :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
        :param str [params.clientOrderId]: the orders client order id
        :param int [params.userref]: the orders user reference id
        :returns Order[]: a list of `order structures <https://docs.ccxt.com/#/?id=order-structure>`
        """
        self.load_markets()
        request: dict = {}
        if since is not None:
            request['start'] = self.parse_to_int(since / 1000)
        userref = self.safe_integer(params, 'userref')
        if userref is not None:
            request['userref'] = userref
            params = self.omit(params, 'userref')
        clientOrderId = self.safe_string(params, 'clientOrderId')
        if clientOrderId is not None:
            request['cl_ord_id'] = clientOrderId
            params = self.omit(params, 'clientOrderId')
        request, params = self.handle_until_option('end', request, params)
        response = self.privatePostClosedOrders(self.extend(request, params))
        #
        #     {
        #         "error":[],
        #         "result":{
        #             "closed":{
        #                 "OETZYO-UL524-QJMXCT":{
        #                     "refid":null,
        #                     "userref":null,
        #                     "status":"canceled",
        #                     "reason":"User requested",
        #                     "opentm":1601489313.3898,
        #                     "closetm":1601489346.5507,
        #                     "starttm":0,
        #                     "expiretm":0,
        #                     "descr":{
        #                         "pair":"ETHUSDT",
        #                         "type":"buy",
        #                         "ordertype":"limit",
        #                         "price":"330.00",
        #                         "price2":"0",
        #                         "leverage":"none",
        #                         "order":"buy 0.02100000 ETHUSDT @ limit 330.00",
        #                         "close":""
        #                     },
        #                     "vol":"0.02100000",
        #                     "vol_exec":"0.00000000",
        #                     "cost":"0.00000",
        #                     "fee":"0.00000",
        #                     "price":"0.00000",
        #                     "stopprice":"0.00000",
        #                     "limitprice":"0.00000",
        #                     "misc":"",
        #                     "oflags":"fciq"
        #                 },
        #             },
        #             "count":16
        #         }
        #     }
        #
        market = None
        if symbol is not None:
            market = self.market(symbol)
        result = self.safe_dict(response, 'result', {})
        closed = self.safe_dict(result, 'closed', {})
        orders = []
        orderIds = list(closed.keys())
        for i in range(0, len(orderIds)):
            id = orderIds[i]
            item = closed[id]
            orders.append(self.extend({'id': id}, item))
        return self.parse_orders(orders, market, since, limit)

    def parse_transaction_status(self, status: Str):
        # IFEX transaction states
        statuses: dict = {
            '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: dict, 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

        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
        :param int [params.until]: timestamp in ms of the latest transaction entry
        :param int [params.end]: timestamp in seconds of the latest transaction entry
        :returns dict[]: a list of `transaction structures <https://docs.ccxt.com/#/?id=transaction-structure>`
        """
        # https://www.kraken.com/en-us/help/api#deposit-status
        self.load_markets()
        request: dict = {}
        if code is not None:
            currency = self.currency(code)
            request['asset'] = currency['id']
        if since is not None:
            sinceString = self.number_to_string(since)
            request['start'] = Precise.string_div(sinceString, '1000')
        until = self.safe_string_n(params, ['until', 'till'])
        if until is not None:
            params = self.omit(params, ['until', 'till'])
            untilDivided = Precise.string_div(until, '1000')
            request['end'] = Precise.string_add(untilDivided, '1')
        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={}) -> Int:
        """
        fetches the current integer timestamp in milliseconds from the exchange server

        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

        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 int [params.until]: timestamp in ms of the latest transaction entry
        :param int [params.end]: timestamp in seconds of the latest transaction entry
        :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: dict = {}
        if code is not None:
            currency = self.currency(code)
            request['asset'] = currency['id']
        if since is not None:
            sinceString = self.number_to_string(since)
            request['start'] = Precise.string_div(sinceString, '1000')
        until = self.safe_string_n(params, ['until', 'till'])
        if until is not None:
            params = self.omit(params, ['until', 'till'])
            untilDivided = Precise.string_div(until, '1000')
            request['end'] = Precise.string_add(untilDivided, '1')
        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={}) -> DepositAddress:
        """
        create a currency deposit address

        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: dict = {
            '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

        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: dict = {
            '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={}) -> DepositAddress:
        """
        fetch the deposit address for a currency associated with self account

        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: dict = {
            '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) -> DepositAddress:
        #
        #     {
        #         "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 {
            'info': depositAddress,
            'currency': code,
            'network': None,
            'address': address,
            'tag': tag,
        }

    def withdraw(self, code: str, amount: float, address: str, tag: Str = None, params={}) -> Transaction:
        """
        make a withdrawal

        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: dict = {
                '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={}) -> List[Position]:
        """
        fetch all open positions

        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: dict = {
            # '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: dict, 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: dict = {
            '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

        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:
        """

        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: dict = {
            '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: dict, currency: Currency = None) -> TransferEntry:
        #
        # 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':
            price = self.safe_string(params, 'price')
            isTriggerPercent = False
            if price is not None:
                isTriggerPercent = True if (price.endswith('%')) else False
            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 or isTriggerPercent:
                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,
            }
            if isCancelOrderBatch or isTriggerPercent:
                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() - self.options['timeDifference']

    def handle_errors(self, code: int, reason: str, url: str, method: str, headers: dict, body: str, response, requestHeaders, requestBody):
        if code == 520:
            raise ExchangeNotAvailable(self.id + ' ' + str(code) + ' ' + reason)
        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['exact'], error, message)
                            self.throw_broadly_matched_exception(self.exceptions['broad'], error, message)
                        raise ExchangeError(message)
        return None
