text: # -*- 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.async_support.base.exchange import Exchange
  import hashlib
  import math
  from ccxt.base.errors import ExchangeError
  from ccxt.base.errors import AuthenticationError
  from ccxt.base.errors import PermissionDenied
  from ccxt.base.errors import ArgumentsRequired
  from ccxt.base.errors import BadRequest
  from ccxt.base.errors import BadSymbol
  from ccxt.base.errors import InsufficientFunds
  from ccxt.base.errors import InvalidAddress
  from ccxt.base.errors import InvalidOrder
  from ccxt.base.errors import OrderNotFound
  from ccxt.base.errors import CancelPending
  from ccxt.base.errors import DDoSProtection
  from ccxt.base.errors import RateLimitExceeded
  from ccxt.base.errors import ExchangeNotAvailable
  from ccxt.base.errors import OnMaintenance
  from ccxt.base.errors import InvalidNonce
  from ccxt.base.decimal_to_precision import TRUNCATE
  from ccxt.base.decimal_to_precision import DECIMAL_PLACES
  from ccxt.base.precise import Precise
  
  
  class kraken(Exchange):
  
      def describe(self):
          return self.deep_extend(super(kraken, self).describe(), {
              'id': 'kraken',
              'name': 'Kraken',
              'countries': ['US'],
              'version': '0',
              'rateLimit': 3000,
              'certified': False,
              'pro': True,
              'has': {
                  'CORS': None,
                  'spot': True,
                  'margin': True,
                  'swap': False,
                  'future': False,
                  'option': False,
                  'cancelAllOrders': True,
                  'cancelOrder': True,
                  'createDepositAddress': True,
                  'createOrder': True,
                  'fetchBalance': True,
                  'fetchBorrowRate': False,
                  'fetchBorrowRateHistories': False,
                  'fetchBorrowRateHistory': False,
                  'fetchBorrowRates': False,
                  'fetchClosedOrders': True,
                  'fetchCurrencies': True,
                  'fetchDepositAddress': True,
                  'fetchDeposits': True,
                  'fetchFundingHistory': False,
                  'fetchFundingRate': False,
                  'fetchFundingRateHistory': False,
                  'fetchFundingRates': False,
                  'fetchIndexOHLCV': False,
                  'fetchIsolatedPositions': False,
                  'fetchLedger': True,
                  'fetchLedgerEntry': True,
                  'fetchLeverageTiers': False,
                  'fetchMarkets': True,
                  'fetchMarkOHLCV': False,
                  'fetchMyTrades': True,
                  'fetchOHLCV': True,
                  'fetchOpenOrders': True,
                  'fetchOrder': True,
                  'fetchOrderBook': True,
                  'fetchOrderTrades': 'emulated',
                  'fetchPositions': True,
                  'fetchPremiumIndexOHLCV': False,
                  'fetchTicker': True,
                  'fetchTickers': True,
                  'fetchTime': True,
                  'fetchTrades': True,
                  'fetchTradingFee': True,
                  'fetchTradingFees': False,
                  'fetchWithdrawals': True,
                  'setLeverage': False,
                  'setMarginMode': False,  # Kraken only supports cross margin
                  'withdraw': True,
              },
              'marketsByAltname': {},
              'timeframes': {
                  '1m': 1,
                  '5m': 5,
                  '15m': 15,
                  '30m': 30,
                  '1h': 60,
                  '4h': 240,
                  '1d': 1440,
                  '1w': 10080,
                  '2w': 21600,
              },
              'urls': {
                  'logo': 'https://user-images.githubusercontent.com/51840849/76173629-fc67fb00-61b1-11ea-84fe-f2de582f58a3.jpg',
                  'api': {
                      'public': 'https://api.kraken.com',
                      'private': 'https://api.kraken.com',
                      'zendesk': 'https://kraken.zendesk.com/api/v2/help_center/en-us/articles',  # use the public zendesk api to receive article bodies and bypass new anti-spam protections
                  },
                  'www': 'https://www.kraken.com',
                  'doc': 'https://www.kraken.com/features/api',
                  'fees': 'https://www.kraken.com/en-us/features/fee-schedule',
              },
              'fees': {
                  'trading': {
                      'tierBased': True,
                      'percentage': True,
                      'taker': 0.26 / 100,
                      'maker': 0.16 / 100,
                      'tiers': {
                          'taker': [
                              [0, 0.0026],
                              [50000, 0.0024],
                              [100000, 0.0022],
                              [250000, 0.0020],
                              [500000, 0.0018],
                              [1000000, 0.0016],
                              [2500000, 0.0014],
                              [5000000, 0.0012],
                              [10000000, 0.0001],
                          ],
                          'maker': [
                              [0, 0.0016],
                              [50000, 0.0014],
                              [100000, 0.0012],
                              [250000, 0.0010],
                              [500000, 0.0008],
                              [1000000, 0.0006],
                              [2500000, 0.0004],
                              [5000000, 0.0002],
                              [10000000, 0.0],
                          ],
                      },
                  },
              },
              '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': [
                          'Assets',
                          'AssetPairs',
                          'Depth',
                          'OHLC',
                          'Spread',
                          'Ticker',
                          'Time',
                          'Trades',
                      ],
                  },
                  'private': {
                      'post': {
                          'AddOrder': 0,
                          'AddExport': 1,
                          'Balance': 1,
                          'CancelAll': 1,
                          'CancelOrder': 0,
                          'ClosedOrders': 2,
                          'DepositAddresses': 1,
                          'DepositMethods': 1,
                          'DepositStatus': 1,
                          'ExportStatus': 1,
                          'GetWebSocketsToken': 1,
                          'Ledgers': 2,
                          'OpenOrders': 1,
                          'OpenPositions': 1,
                          'QueryLedgers': 1,
                          'QueryOrders': 1,
                          'QueryTrades': 1,
                          'RetrieveExport': 1,
                          'RemoveExport': 1,
                          'TradeBalance': 1,
                          'TradesHistory': 2,
                          'TradeVolume': 1,
                          'Withdraw': 1,
                          'WithdrawCancel': 1,
                          'WithdrawInfo': 1,
                          'WithdrawStatus': 1,
                          # staking
                          'Stake': 1,
                          'Unstake': 1,
                          'Staking/Assets': 1,
                          'Staking/Pending': 1,
                          'Staking/Transactions': 1,
                      },
                  },
              },
              'commonCurrencies': {
                  'XBT': 'BTC',
                  'XBT.M': 'BTC.M',  # https://support.kraken.com/hc/en-us/articles/360039879471-What-is-Asset-S-and-Asset-M-
                  'XDG': 'DOGE',
                  'REPV2': 'REP',
                  'REP': 'REPV1',
              },
              'options': {
                  '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)',
                  },
              },
              'exceptions': {
                  'EQuery:Invalid asset pair': BadSymbol,  # {"error":["EQuery:Invalid asset pair"]}
                  'EAPI:Invalid key': AuthenticationError,
                  'EFunding:Unknown withdraw key': InvalidAddress,  # {"error":["EFunding:Unknown withdraw key"]}
                  'EFunding:Invalid amount': InsufficientFunds,
                  'EService:Unavailable': ExchangeNotAvailable,
                  'EDatabase:Internal error': ExchangeNotAvailable,
                  'EService:Busy': ExchangeNotAvailable,
                  'EQuery:Unknown asset': BadSymbol,  # {"error":["EQuery:Unknown asset"]}
                  'EAPI:Rate limit exceeded': DDoSProtection,
                  'EOrder:Rate limit exceeded': DDoSProtection,
                  'EGeneral:Internal error': ExchangeNotAvailable,
                  'EGeneral:Temporary lockout': DDoSProtection,
                  'EGeneral:Permission denied': PermissionDenied,
                  'EOrder:Unknown order': InvalidOrder,
                  'EOrder:Order minimum not met': InvalidOrder,
                  'EGeneral:Invalid arguments': BadRequest,
                  'ESession:Invalid session': AuthenticationError,
                  'EAPI:Invalid nonce': InvalidNonce,
                  'EFunding:No funding method': BadRequest,  # {"error":"EFunding:No funding method"}
                  'EFunding:Unknown asset': BadSymbol,  # {"error":["EFunding:Unknown asset"]}
                  'EService:Market in post_only mode': OnMaintenance,  # {"error":["EService:Market in post_only mode"]}
              },
          })
  
      def cost_to_precision(self, symbol, cost):
          return self.decimal_to_precision(cost, TRUNCATE, self.markets[symbol]['precision']['price'], DECIMAL_PLACES)
  
      def fee_to_precision(self, symbol, fee):
          return self.decimal_to_precision(fee, TRUNCATE, self.markets[symbol]['precision']['amount'], DECIMAL_PLACES)
  
      async def fetch_markets(self, params={}):
          response = await self.publicGetAssetPairs(params)
          #
          #     {
          #         "error":[],
          #         "result":{
          #             "ADAETH":{
          #                 "altname":"ADAETH",
          #                 "wsname":"ADA\/ETH",
          #                 "aclass_base":"currency",
          #                 "base":"ADA",
          #                 "aclass_quote":"currency",
          #                 "quote":"XETH",
          #                 "lot":"unit",
          #                 "pair_decimals":7,
          #                 "lot_decimals":8,
          #                 "lot_multiplier":1,
          #                 "leverage_buy":[],
          #                 "leverage_sell":[],
          #                 "fees":[
          #                     [0,0.26],
          #                     [50000,0.24],
          #                     [100000,0.22],
          #                     [250000,0.2],
          #                     [500000,0.18],
          #                     [1000000,0.16],
          #                     [2500000,0.14],
          #                     [5000000,0.12],
          #                     [10000000,0.1]
          #                 ],
          #                 "fees_maker":[
          #                     [0,0.16],
          #                     [50000,0.14],
          #                     [100000,0.12],
          #                     [250000,0.1],
          #                     [500000,0.08],
          #                     [1000000,0.06],
          #                     [2500000,0.04],
          #                     [5000000,0.02],
          #                     [10000000,0]
          #                 ],
          #                 "fee_volume_currency":"ZUSD",
          #                 "margin_call":80,
          #                 "margin_stop":40,
          #                 "ordermin": "1"
          #             },
          #         }
          #     }
          #
          markets = self.safe_value(response, 'result', {})
          keys = list(markets.keys())
          result = []
          for i in range(0, len(keys)):
              id = keys[i]
              market = markets[id]
              baseId = self.safe_string(market, 'base')
              quoteId = self.safe_string(market, 'quote')
              base = self.safe_currency_code(baseId)
              quote = self.safe_currency_code(quoteId)
              darkpool = id.find('.d') >= 0
              altname = self.safe_string(market, 'altname')
              makerFees = self.safe_value(market, 'fees_maker', [])
              firstMakerFee = self.safe_value(makerFees, 0, [])
              firstMakerFeeRate = self.safe_number(firstMakerFee, 1)
              maker = None
              if firstMakerFeeRate is not None:
                  maker = float(firstMakerFeeRate) / 100
              takerFees = self.safe_value(market, 'fees', [])
              firstTakerFee = self.safe_value(takerFees, 0, [])
              firstTakerFeeRate = self.safe_number(firstTakerFee, 1)
              taker = None
              if firstTakerFeeRate is not None:
                  taker = float(firstTakerFeeRate) / 100
              leverageBuy = self.safe_value(market, 'leverage_buy', [])
              leverageBuyLength = len(leverageBuy)
              precisionPrice = self.safe_string(market, 'pair_decimals')
              result.append({
                  'id': id,
                  'symbol': altname if darkpool else (base + '/' + quote),
                  'base': base,
                  'quote': quote,
                  'settle': None,
                  'baseId': baseId,
                  'quoteId': quoteId,
                  'settleId': None,
                  'darkpool': darkpool,
                  'altname': market['altname'],
                  'type': 'spot',
                  'spot': True,
                  'margin': (leverageBuyLength > 0),
                  'swap': False,
                  'future': False,
                  'option': False,
                  'active': True,
                  'contract': False,
                  'linear': None,
                  'inverse': None,
                  'taker': taker,
                  'maker': maker,
                  'contractSize': None,
                  'expiry': None,
                  'expiryDatetime': None,
                  'strike': None,
                  'optionType': None,
                  'precision': {
                      'amount': self.safe_integer(market, 'lot_decimals'),
                      'price': self.safe_integer(market, 'pair_decimals'),
                  },
                  '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': self.parse_number(self.parse_precision(precisionPrice)),
                          'max': None,
                      },
                      'cost': {
                          'min': None,
                          'max': None,
                      },
                  },
                  'info': market,
              })
          result = self.append_inactive_markets(result)
          self.marketsByAltname = self.index_by(result, 'altname')
          return result
  
      def safe_currency(self, currencyId, currency=None):
          if currencyId is not None:
              if len(currencyId) > 3:
                  if (currencyId.find('X') == 0) or (currencyId.find('Z') == 0):
                      if currencyId.find('.') > 0:
                          return super(kraken, self).safe_currency(currencyId, currency)
                      else:
                          currencyId = currencyId[1:]
          return super(kraken, self).safe_currency(currencyId, currency)
  
      def append_inactive_markets(self, result):
          # result should be an array to append to
          precision = {'amount': 8, 'price': 8}
          costLimits = {'min': 0, 'max': None}
          priceLimits = {'min': math.pow(10, -precision['price']), 'max': None}
          amountLimits = {'min': math.pow(10, -precision['amount']), 'max': math.pow(10, precision['amount'])}
          limits = {'amount': amountLimits, 'price': priceLimits, 'cost': costLimits}
          defaults = {
              'darkpool': False,
              'info': None,
              'maker': None,
              'taker': None,
              'active': False,
              'precision': precision,
              'limits': limits,
          }
          markets = [
              # {'id': 'XXLMZEUR', 'symbol': 'XLM/EUR', 'base': 'XLM', 'quote': 'EUR', 'altname': 'XLMEUR'},
          ]
          for i in range(0, len(markets)):
              result.append(self.extend(defaults, markets[i]))
          return result
  
      async def fetch_currencies(self, params={}):
          response = await self.publicGetAssets(params)
          #
          #     {
          #         "error": [],
          #         "result": {
          #             "ADA": {"aclass": "currency", "altname": "ADA", "decimals": 8, "display_decimals": 6},
          #             "BCH": {"aclass": "currency", "altname": "BCH", "decimals": 10, "display_decimals": 5},
          #             ...
          #         },
          #     }
          #
          currencies = self.safe_value(response, 'result')
          ids = list(currencies.keys())
          result = {}
          for i in range(0, len(ids)):
              id = ids[i]
              currency = currencies[id]
              # todo: will need to rethink the fees
              # see: https://support.kraken.com/hc/en-us/articles/201893608-What-are-the-withdrawal-fees-
              # to add support for multiple withdrawal/deposit methods and
              # differentiated fees for each particular method
              code = self.safe_currency_code(self.safe_string(currency, 'altname'))
              precision = self.safe_integer(currency, 'decimals')
              # assumes all currencies are active except those listed above
              active = not self.in_array(code, self.options['inactiveCurrencies'])
              result[code] = {
                  'id': id,
                  'code': code,
                  'info': currency,
                  'name': code,
                  'active': active,
                  'deposit': None,
                  'withdraw': None,
                  'fee': None,
                  'precision': precision,
                  'limits': {
                      'amount': {
                          'min': math.pow(10, -precision),
                          'max': math.pow(10, precision),
                      },
                      'withdraw': {
                          'min': None,
                          'max': math.pow(10, precision),
                      },
                  },
              }
          return result
  
      async def fetch_trading_fee(self, symbol, params={}):
          await self.load_markets()
          market = self.market(symbol)
          request = {
              'pair': market['id'],
              'fee-info': True,
          }
          response = await self.privatePostTradeVolume(self.extend(request, params))
          #
          #     {
          #        error: [],
          #        result: {
          #          currency: 'ZUSD',
          #          volume: '0.0000',
          #          fees: {
          #            XXBTZUSD: {
          #              fee: '0.2600',
          #              minfee: '0.1000',
          #              maxfee: '0.2600',
          #              nextfee: '0.2400',
          #              tiervolume: '0.0000',
          #              nextvolume: '50000.0000'
          #            }
          #          },
          #          fees_maker: {
          #            XXBTZUSD: {
          #              fee: '0.1600',
          #              minfee: '0.0000',
          #              maxfee: '0.1600',
          #              nextfee: '0.1400',
          #              tiervolume: '0.0000',
          #              nextvolume: '50000.0000'
          #            }
          #          }
          #        }
          #     }
          #
          result = self.safe_value(response, 'result', {})
          return self.parse_trading_fee(result, market)
  
      def parse_trading_fee(self, response, market):
          makerFees = self.safe_value(response, 'fees_maker', {})
          takerFees = self.safe_value(response, 'fees', {})
          symbolMakerFee = self.safe_value(makerFees, market['id'], {})
          symbolTakerFee = self.safe_value(takerFees, market['id'], {})
          return {
              'info': response,
              'symbol': market['symbol'],
              'maker': self.safe_number(symbolMakerFee, 'fee'),
              'taker': self.safe_number(symbolTakerFee, 'fee'),
              'percentage': True,
              'tierBased': True,
          }
  
      def parse_bid_ask(self, bidask, priceKey=0, amountKey=1):
          price = self.safe_number(bidask, priceKey)
          amount = self.safe_number(bidask, amountKey)
          timestamp = self.safe_integer(bidask, 2)
          return [price, amount, timestamp]
  
      async def fetch_order_book(self, symbol, limit=None, params={}):
          await self.load_markets()
          market = self.market(symbol)
          if market['darkpool']:
              raise ExchangeError(self.id + ' does not provide an order book for darkpool symbol ' + symbol)
          request = {
              'pair': market['id'],
          }
          if limit is not None:
              request['count'] = limit  # 100
          response = await self.publicGetDepth(self.extend(request, params))
          #
          #     {
          #         "error":[],
          #         "result":{
          #             "XETHXXBT":{
          #                 "asks":[
          #                     ["0.023480","4.000",1586321307],
          #                     ["0.023490","50.095",1586321306],
          #                     ["0.023500","28.535",1586321302],
          #                 ],
          #                 "bids":[
          #                     ["0.023470","59.580",1586321307],
          #                     ["0.023460","20.000",1586321301],
          #                     ["0.023440","67.832",1586321306],
          #                 ]
          #             }
          #         }
          #     }
          #
          result = self.safe_value(response, 'result', {})
          orderbook = self.safe_value(result, market['id'])
          # sometimes kraken returns wsname instead of market id
          # https://github.com/ccxt/ccxt/issues/8662
          marketInfo = self.safe_value(market, 'info', {})
          wsName = self.safe_value(marketInfo, 'wsname')
          if wsName is not None:
              orderbook = self.safe_value(result, wsName, orderbook)
          return self.parse_order_book(orderbook, symbol)
  
      def parse_ticker(self, ticker, market=None):
          #
          #     {
          #         "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"
          #     }
          #
          timestamp = self.milliseconds()
          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': timestamp,
              'datetime': self.iso8601(timestamp),
              'high': self.safe_string(high, 1),
              'low': self.safe_string(low, 1),
              'bid': self.safe_string(bid, 0),
              'bidVolume': None,
              'ask': self.safe_string(ask, 0),
              'askVolume': None,
              'vwap': vwap,
              'open': self.safe_string(ticker, 'o'),
              'close': last,
              'last': last,
              'previousClose': None,
              'change': None,
              'percentage': None,
              'average': None,
              'baseVolume': baseVolume,
              'quoteVolume': quoteVolume,
              'info': ticker,
          }, market, False)
  
      async def fetch_tickers(self, symbols=None, params={}):
          if symbols is None:
              raise ArgumentsRequired(self.id + ' fetchTickers() requires a symbols argument, an array of symbols')
          await self.load_markets()
          marketIds = []
          for i in range(0, len(symbols)):
              symbol = symbols[i]
              market = self.markets[symbol]
              if market['active'] and not market['darkpool']:
                  marketIds.append(market['id'])
          request = {
              'pair': ','.join(marketIds),
          }
          response = await self.publicGetTicker(self.extend(request, params))
          tickers = response['result']
          ids = list(tickers.keys())
          result = {}
          for i in range(0, len(ids)):
              id = ids[i]
              market = self.markets_by_id[id]
              symbol = market['symbol']
              ticker = tickers[id]
              result[symbol] = self.parse_ticker(ticker, market)
          return self.filter_by_array(result, 'symbol', symbols)
  
      async def fetch_ticker(self, symbol, params={}):
          await self.load_markets()
          darkpool = symbol.find('.d') >= 0
          if darkpool:
              raise ExchangeError(self.id + ' does not provide a ticker for darkpool symbol ' + symbol)
          market = self.market(symbol)
          request = {
              'pair': market['id'],
          }
          response = await self.publicGetTicker(self.extend(request, params))
          ticker = response['result'][market['id']]
          return self.parse_ticker(ticker, market)
  
      def parse_ohlcv(self, ohlcv, market=None):
          #
          #     [
          #         1591475640,
          #         "0.02500",
          #         "0.02500",
          #         "0.02500",
          #         "0.02500",
          #         "0.02500",
          #         "9.12201000",
          #         5
          #     ]
          #
          return [
              self.safe_timestamp(ohlcv, 0),
              self.safe_number(ohlcv, 1),
              self.safe_number(ohlcv, 2),
              self.safe_number(ohlcv, 3),
              self.safe_number(ohlcv, 4),
              self.safe_number(ohlcv, 6),
          ]
  
      async def fetch_ohlcv(self, symbol, timeframe='1m', since=None, limit=None, params={}):
          await self.load_markets()
          market = self.market(symbol)
          request = {
              'pair': market['id'],
              'interval': self.timeframes[timeframe],
          }
          if since is not None:
              request['since'] = int((since - 1) / 1000)
          response = await self.publicGetOHLC(self.extend(request, params))
          #
          #     {
          #         "error":[],
          #         "result":{
          #             "XETHXXBT":[
          #                 [1591475580,"0.02499","0.02499","0.02499","0.02499","0.00000","0.00000000",0],
          #                 [1591475640,"0.02500","0.02500","0.02500","0.02500","0.02500","9.12201000",5],
          #                 [1591475700,"0.02499","0.02499","0.02499","0.02499","0.02499","1.28681415",2],
          #                 [1591475760,"0.02499","0.02499","0.02499","0.02499","0.02499","0.08800000",1],
          #             ],
          #             "last":1591517580
          #         }
          #     }
          result = self.safe_value(response, 'result', {})
          ohlcvs = self.safe_value(result, market['id'], [])
          return self.parse_ohlcvs(ohlcvs, market, timeframe, since, limit)
  
      def parse_ledger_entry_type(self, type):
          types = {
              'trade': 'trade',
              'withdrawal': 'transaction',
              'deposit': 'transaction',
              'transfer': 'transfer',
              'margin': 'margin',
          }
          return self.safe_string(types, type, type)
  
      def parse_ledger_entry(self, item, currency=None):
          #
          #     {
          #         'LTFK7F-N2CUX-PNY4SX': {
          #             refid: "TSJTGT-DT7WN-GPPQMJ",
          #             time:  1520102320.555,
          #             type: "trade",
          #             aclass: "currency",
          #             asset: "XETH",
          #             amount: "0.1087194600",
          #             fee: "0.0000000000",
          #             balance: "0.2855851000"
          #         },
          #         ...
          #     }
          #
          id = self.safe_string(item, 'id')
          direction = None
          account = None
          referenceId = self.safe_string(item, 'refid')
          referenceAccount = None
          type = self.parse_ledger_entry_type(self.safe_string(item, 'type'))
          code = self.safe_currency_code(self.safe_string(item, 'asset'), currency)
          amount = self.safe_number(item, 'amount')
          if amount < 0:
              direction = 'out'
              amount = abs(amount)
          else:
              direction = 'in'
          time = self.safe_number(item, 'time')
          timestamp = None
          if time is not None:
              timestamp = int(time * 1000)
          fee = {
              'cost': self.safe_number(item, 'fee'),
              'currency': code,
          }
          before = None
          after = self.safe_number(item, 'balance')
          status = 'ok'
          return {
              'info': item,
              'id': id,
              'direction': direction,
              'account': account,
              'referenceId': referenceId,
              'referenceAccount': referenceAccount,
              'type': type,
              'currency': code,
              'amount': amount,
              'before': before,
              'after': after,
              'status': status,
              'timestamp': timestamp,
              'datetime': self.iso8601(timestamp),
              'fee': fee,
          }
  
      async def fetch_ledger(self, code=None, since=None, limit=None, params={}):
          # https://www.kraken.com/features/api#get-ledgers-info
          await self.load_markets()
          request = {}
          currency = None
          if code is not None:
              currency = self.currency(code)
              request['asset'] = currency['id']
          if since is not None:
              request['start'] = int(since / 1000)
          response = await 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)
  
      async def fetch_ledger_entries_by_ids(self, ids, code=None, params={}):
          # https://www.kraken.com/features/api#query-ledgers
          await self.load_markets()
          ids = ','.join(ids)
          request = self.extend({
              'id': ids,
          }, params)
          response = await 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)
  
      async def fetch_ledger_entry(self, id, code=None, params={}):
          items = await self.fetch_ledger_entries_by_ids([id], code, params)
          return items[0]
  
      def parse_trade(self, trade, market=None):
          #
          # fetchTrades(public)
          #
          #     [
          #         "0.032310",  # price
          #         "4.28169434",  # amount
          #         1541390792.763,  # timestamp
          #         "s",  # sell or buy
          #         "l",  # limit or market
          #         ""
          #     ]
          #
          # fetchOrderTrades(private)
          #
          #     {
          #         id: 'TIMIRG-WUNNE-RRJ6GT',  # injected from outside
          #         ordertxid: 'OQRPN2-LRHFY-HIFA7D',
          #         postxid: 'TKH2SE-M7IF5-CFI7LT',
          #         pair: 'USDCUSDT',
          #         time: 1586340086.457,
          #         type: 'sell',
          #         ordertype: 'market',
          #         price: '0.99860000',
          #         cost: '22.16892001',
          #         fee: '0.04433784',
          #         vol: '22.20000000',
          #         margin: '0.00000000',
          #         misc: ''
          #     }
          #
          timestamp = None
          side = None
          type = None
          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 as per  #1794
          elif isinstance(trade, str):
              id = trade
          elif 'ordertxid' in trade:
              marketId = self.safe_string(trade, 'pair')
              foundMarket = self.find_market_by_altname_or_id(marketId)
              if foundMarket is not None:
                  market = foundMarket
              elif marketId is not None:
                  # delisted market ids go here
                  market = self.get_delisted_market_by_id(marketId)
              orderId = self.safe_string(trade, 'ordertxid')
              id = self.safe_string_2(trade, 'id', 'postxid')
              timestamp = self.safe_timestamp(trade, 'time')
              side = self.safe_string(trade, 'type')
              type = self.safe_string(trade, 'ordertype')
              price = self.safe_string(trade, 'price')
              amount = self.safe_string(trade, 'vol')
              if 'fee' in trade:
                  currency = None
                  if market is not None:
                      currency = market['quote']
                  fee = {
                      'cost': self.safe_string(trade, 'fee'),
                      'currency': currency,
                  }
          if market is not None:
              symbol = market['symbol']
          cost = self.safe_string(trade, 'cost')
          return self.safe_trade({
              'id': id,
              'order': orderId,
              'info': trade,
              'timestamp': timestamp,
              'datetime': self.iso8601(timestamp),
              'symbol': symbol,
              'type': type,
              'side': side,
              'takerOrMaker': None,
              'price': price,
              'amount': amount,
              'cost': cost,
              'fee': fee,
          }, market)
  
      async def fetch_trades(self, symbol, since=None, limit=None, params={}):
          await self.load_markets()
          market = self.market(symbol)
          id = market['id']
          request = {
              'pair': id,
          }
          # https://support.kraken.com/hc/en-us/articles/218198197-How-to-pull-all-trade-data-using-the-Kraken-REST-API
          # https://github.com/ccxt/ccxt/issues/5677
          if since is not None:
              # php does not format it properly
              # therefore we use string concatenation here
              request['since'] = since * 1e6
              request['since'] = str(since) + '000000'  # expected to be in nanoseconds
          # https://github.com/ccxt/ccxt/issues/5698
          if limit is not None and limit != 1000:
              fetchTradesWarning = self.safe_value(self.options, 'fetchTradesWarning', True)
              if fetchTradesWarning:
                  raise ExchangeError(self.id + ' fetchTrades() cannot serve ' + str(limit) + " trades without breaking the pagination, see https://github.com/ccxt/ccxt/issues/5698 for more details. Set exchange.options['fetchTradesWarning'] to acknowledge self warning and silence it.")
          response = await self.publicGetTrades(self.extend(request, params))
          #
          #     {
          #         "error": [],
          #         "result": {
          #             "XETHXXBT": [
          #                 ["0.032310","4.28169434",1541390792.763,"s","l",""]
          #             ],
          #             "last": "1541439421200678657"
          #         }
          #     }
          #
          result = response['result']
          trades = result[id]
          # trades is a sorted array: last(most recent trade) goes last
          length = len(trades)
          if length <= 0:
              return []
          lastTrade = trades[length - 1]
          lastTradeId = self.safe_string(result, 'last')
          lastTrade.append(lastTradeId)
          return self.parse_trades(trades, market, since, limit)
  
      def parse_balance(self, response):
          balances = self.safe_value(response, 'result', {})
          result = {
              'info': response,
              'timestamp': None,
              'datetime': None,
          }
          currencyIds = list(balances.keys())
          for i in range(0, len(currencyIds)):
              currencyId = currencyIds[i]
              code = self.safe_currency_code(currencyId)
              account = self.account()
              account['total'] = self.safe_string(balances, currencyId)
              result[code] = account
          return self.safe_balance(result)
  
      async def fetch_balance(self, params={}):
          await self.load_markets()
          response = await self.privatePostBalance(params)
          #
          #     {
          #         "error":[],
          #         "result":{
          #             "ZUSD":"58.8649",
          #             "KFEE":"4399.43",
          #             "XXBT":"0.0000034506",
          #         }
          #     }
          #
          return self.parse_balance(response)
  
      async def create_order(self, symbol, type, side, amount, price=None, params={}):
          await self.load_markets()
          market = self.market(symbol)
          request = {
              'pair': market['id'],
              'type': side,
              'ordertype': type,
              'volume': self.amount_to_precision(symbol, amount),
          }
          clientOrderId = self.safe_string_2(params, 'userref', 'clientOrderId')
          params = self.omit(params, ['userref', 'clientOrderId'])
          if clientOrderId is not None:
              request['userref'] = clientOrderId
          #
          #     market
          #     limit(price = limit price)
          #     stop-loss(price = stop loss price)
          #     take-profit(price = take profit price)
          #     stop-loss-limit(price = stop loss trigger price, price2 = triggered limit price)
          #     take-profit-limit(price = take profit trigger price, price2 = triggered limit price)
          #     settle-position
          #
          if type == 'limit':
              request['price'] = self.price_to_precision(symbol, price)
          elif (type == 'stop-loss') or (type == 'take-profit'):
              stopPrice = self.safe_number_2(params, 'price', 'stopPrice', price)
              if stopPrice is None:
                  raise ArgumentsRequired(self.id + ' createOrder() requires a price argument or a price/stopPrice parameter for a ' + type + ' order')
              else:
                  request['price'] = self.price_to_precision(symbol, stopPrice)
          elif (type == 'stop-loss-limit') or (type == 'take-profit-limit'):
              stopPrice = self.safe_number_2(params, 'price', 'stopPrice')
              limitPrice = self.safe_number(params, 'price2')
              stopPriceDefined = (stopPrice is not None)
              limitPriceDefined = (limitPrice is not None)
              if stopPriceDefined and limitPriceDefined:
                  request['price'] = self.price_to_precision(symbol, stopPrice)
                  request['price2'] = self.price_to_precision(symbol, limitPrice)
              elif (price is None) or (not(stopPriceDefined or limitPriceDefined)):
                  raise ArgumentsRequired(self.id + ' createOrder() requires a price argument and/or price/stopPrice/price2 parameters for a ' + type + ' order')
              else:
                  if stopPriceDefined:
                      request['price'] = self.price_to_precision(symbol, stopPrice)
                      request['price2'] = self.price_to_precision(symbol, price)
                  elif limitPriceDefined:
                      request['price'] = self.price_to_precision(symbol, price)
                      request['price2'] = self.price_to_precision(symbol, limitPrice)
          params = self.omit(params, ['price', 'stopPrice', 'price2'])
          response = await self.privatePostAddOrder(self.extend(request, params))
          #
          #     {
          #         error: [],
          #         result: {
          #             descr: {order: 'buy 0.02100000 ETHUSDT @ limit 330.00'},
          #             txid: ['OEKVV2-IH52O-TPL6GZ']
          #         }
          #     }
          #
          result = self.safe_value(response, 'result')
          return self.parse_order(result)
  
      def find_market_by_altname_or_id(self, id):
          if id in self.marketsByAltname:
              return self.marketsByAltname[id]
          elif id in self.markets_by_id:
              return self.markets_by_id[id]
          return None
  
      def get_delisted_market_by_id(self, id):
          if id is None:
              return id
          market = self.safe_value(self.options['delistedMarketsById'], id)
          if market is not None:
              return market
          baseIdStart = 0
          baseIdEnd = 3
          quoteIdStart = 3
          quoteIdEnd = 6
          if len(id) == 8:
              baseIdEnd = 4
              quoteIdStart = 4
              quoteIdEnd = 8
          elif len(id) == 7:
              baseIdEnd = 4
              quoteIdStart = 4
              quoteIdEnd = 7
          baseId = id[baseIdStart:baseIdEnd]
          quoteId = id[quoteIdStart:quoteIdEnd]
          base = self.safe_currency_code(baseId)
          quote = self.safe_currency_code(quoteId)
          symbol = base + '/' + quote
          market = {
              'symbol': symbol,
              'base': base,
              'quote': quote,
              'baseId': baseId,
              'quoteId': quoteId,
          }
          self.options['delistedMarketsById'][id] = market
          return market
  
      def parse_order_status(self, status):
          statuses = {
              'pending': 'open',  # order pending book entry
              'open': 'open',
              'closed': 'closed',
              'canceled': 'canceled',
              'expired': 'expired',
          }
          return self.safe_string(statuses, status, status)
  
      def parse_order(self, order, market=None):
          #
          # createOrder for regular orders
          #
          #     {
          #         descr: {order: 'buy 0.02100000 ETHUSDT @ limit 330.00'},
          #         txid: ['OEKVV2-IH52O-TPL6GZ']
          #     }
          #     {
          #         "txid": ["TX_ID_HERE"],
          #         "descr": {"order":"buy 0.12345678 ETHEUR @ market"},
          #     }
          #
          #
          # createOrder for stop orders
          #
          #     {
          #         "txid":["OSILNC-VQI5Q-775ZDQ"],
          #         "descr":{"order":"sell 167.28002676 ADAXBT @ stop loss 0.00003280 -> limit 0.00003212"}
          #     }
          #
          #
          #     {
          #         "txid":["OVHMJV-BZW2V-6NZFWF"],
          #         "descr":{"order":"sell 0.00100000 ETHUSD @ stop loss 2677.00 -> limit 2577.00 with 5:1 leverage"}
          #     }
          #
          description = self.safe_value(order, 'descr', {})
          orderDescription = self.safe_string(description, 'order')
          side = None
          type = None
          marketId = None
          price = None
          amount = None
          stopPrice = None
          if orderDescription is not None:
              parts = orderDescription.split(' ')
              side = self.safe_string(parts, 0)
              amount = self.safe_string(parts, 1)
              marketId = self.safe_string(parts, 2)
              type = self.safe_string(parts, 4)
              if type == 'stop':
                  stopPrice = self.safe_string(parts, 6)
                  price = self.safe_string(parts, 9)
              elif type == 'limit':
                  price = self.safe_string(parts, 5)
          side = self.safe_string(description, 'type', side)
          type = self.safe_string(description, 'ordertype', type)
          marketId = self.safe_string(description, 'pair', marketId)
          foundMarket = self.find_market_by_altname_or_id(marketId)
          symbol = None
          if foundMarket is not None:
              market = foundMarket
          elif marketId is not None:
              # delisted market ids go here
              market = self.get_delisted_market_by_id(marketId)
          timestamp = self.safe_timestamp(order, 'opentm')
          amount = self.safe_string(order, 'vol', amount)
          filled = self.safe_string(order, 'vol_exec')
          fee = None
          # kraken truncates the cost in the api response so we will ignore it and calculate it from average & filled
          # cost = self.safe_string(order, 'cost')
          price = self.safe_string(description, 'price', price)
          if (price is None) or Precise.string_equals(price, '0'):
              price = self.safe_string(description, 'price2')
          if (price is None) or Precise.string_equals(price, '0'):
              price = self.safe_string(order, 'price', price)
          average = self.safe_number(order, 'price')
          if market is not None:
              symbol = market['symbol']
              if 'fee' in order:
                  flags = order['oflags']
                  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(order, 'id')
          if id is None:
              txid = self.safe_value(order, 'txid')
              id = self.safe_string(txid, 0)
          clientOrderId = self.safe_string(order, 'userref')
          rawTrades = self.safe_value(order, 'trades')
          stopPrice = self.safe_number(order, 'stopprice', stopPrice)
          return self.safe_order({
              'id': id,
              'clientOrderId': clientOrderId,
              'info': order,
              'timestamp': timestamp,
              'datetime': self.iso8601(timestamp),
              'lastTradeTimestamp': None,
              'status': status,
              'symbol': symbol,
              'type': type,
              'timeInForce': None,
              'postOnly': None,
              'side': side,
              'price': price,
              'stopPrice': stopPrice,
              'cost': None,
              'amount': amount,
              'filled': filled,
              'average': average,
              'remaining': None,
              'fee': fee,
              'trades': rawTrades,
          }, market)
  
      async def fetch_order(self, id, symbol=None, params={}):
          await self.load_markets()
          clientOrderId = self.safe_value_2(params, 'userref', 'clientOrderId')
          request = {
              'trades': True,  # whether or not to include trades in output(optional, default False)
              # 'txid': id,  # do not comma separate a list of ids - use fetchOrdersByIds instead
              # 'userref': 'optional',  # restrict results to given user reference id(optional)
          }
          query = params
          if clientOrderId is not None:
              request['userref'] = clientOrderId
              query = self.omit(params, ['userref', 'clientOrderId'])
          else:
              request['txid'] = id
          response = await self.privatePostQueryOrders(self.extend(request, query))
          #
          #     {
          #         "error":[],
          #         "result":{
          #             "OTLAS3-RRHUF-NDWH5A":{
          #                 "refid":null,
          #                 "userref":null,
          #                 "status":"closed",
          #                 "reason":null,
          #                 "opentm":1586822919.3342,
          #                 "closetm":1586822919.365,
          #                 "starttm":0,
          #                 "expiretm":0,
          #                 "descr":{
          #                     "pair":"XBTUSDT",
          #                     "type":"sell",
          #                     "ordertype":"market",
          #                     "price":"0",
          #                     "price2":"0",
          #                     "leverage":"none",
          #                     "order":"sell 0.21804000 XBTUSDT @ market",
          #                     "close":""
          #                 },
          #                 "vol":"0.21804000",
          #                 "vol_exec":"0.21804000",
          #                 "cost":"1493.9",
          #                 "fee":"3.8",
          #                 "price":"6851.5",
          #                 "stopprice":"0.00000",
          #                 "limitprice":"0.00000",
          #                 "misc":"",
          #                 "oflags":"fciq",
          #                 "trades":["TT5UC3-GOIRW-6AZZ6R"]
          #             }
          #         }
          #     }
          #
          result = self.safe_value(response, 'result', [])
          if not (id in result):
              raise OrderNotFound(self.id + ' fetchOrder() could not find order id ' + id)
          order = self.parse_order(self.extend({'id': id}, result[id]))
          return self.extend({'info': response}, order)
  
      async def fetch_order_trades(self, id, symbol=None, since=None, limit=None, params={}):
          orderTrades = self.safe_value(params, 'trades')
          tradeIds = []
          if orderTrades is None:
              raise ArgumentsRequired(self.id + " fetchOrderTrades() requires a unified order structure in the params argument or a 'trades' param(an array of trade id strings)")
          else:
              for i in range(0, len(orderTrades)):
                  orderTrade = orderTrades[i]
                  if isinstance(orderTrade, str):
                      tradeIds.append(orderTrade)
                  else:
                      tradeIds.append(orderTrade['id'])
          await self.load_markets()
          options = self.safe_value(self.options, 'fetchOrderTrades', {})
          batchSize = self.safe_integer(options, 'batchSize', 20)
          numTradeIds = len(tradeIds)
          numBatches = int(numTradeIds / batchSize)
          numBatches = self.sum(numBatches, 1)
          result = []
          for j in range(0, numBatches):
              requestIds = []
              for k in range(0, batchSize):
                  index = self.sum(j * batchSize, k)
                  if index < numTradeIds:
                      requestIds.append(tradeIds[index])
              request = {
                  'txid': ','.join(requestIds),
              }
              response = await 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
  
      async def fetch_orders_by_ids(self, ids, symbol=None, params={}):
          await self.load_markets()
          response = await 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
  
      async def fetch_my_trades(self, symbol=None, since=None, limit=None, params={}):
          await self.load_markets()
          request = {
              # 'type': 'all',  # any position, closed position, closing position, no position
              # 'trades': False,  # whether or not to include trades related to position in output
              # 'start': 1234567890,  # starting unix timestamp or trade tx id of results(exclusive)
              # 'end': 1234567890,  # ending unix timestamp or trade tx id of results(inclusive)
              # 'ofs' = result offset
          }
          if since is not None:
              request['start'] = int(since / 1000)
          response = await self.privatePostTradesHistory(self.extend(request, params))
          #
          #     {
          #         "error": [],
          #         "result": {
          #             "trades": {
          #                 "GJ3NYQ-XJRTF-THZABF": {
          #                     "ordertxid": "TKH2SE-ZIF5E-CFI7LT",
          #                     "postxid": "OEN3VX-M7IF5-JNBJAM",
          #                     "pair": "XICNXETH",
          #                     "time": 1527213229.4491,
          #                     "type": "sell",
          #                     "ordertype": "limit",
          #                     "price": "0.001612",
          #                     "cost": "0.025792",
          #                     "fee": "0.000026",
          #                     "vol": "16.00000000",
          #                     "margin": "0.000000",
          #                     "misc": ""
          #                 },
          #                 ...
          #             },
          #             "count": 9760,
          #         },
          #     }
          #
          trades = response['result']['trades']
          ids = list(trades.keys())
          for i in range(0, len(ids)):
              trades[ids[i]]['id'] = ids[i]
          market = None
          if symbol is not None:
              market = self.market(symbol)
          return self.parse_trades(trades, market, since, limit)
  
      async def cancel_order(self, id, symbol=None, params={}):
          await self.load_markets()
          response = None
          clientOrderId = self.safe_value_2(params, 'userref', 'clientOrderId', id)
          request = {
              'txid': clientOrderId,  # order id or userref
          }
          params = self.omit(params, ['userref', 'clientOrderId'])
          try:
              response = await self.privatePostCancelOrder(self.extend(request, params))
          except Exception as e:
              if self.last_http_response:
                  if self.last_http_response.find('EOrder:Unknown order') >= 0:
                      raise OrderNotFound(self.id + ' cancelOrder() error ' + self.last_http_response)
              raise e
          return response
  
      async def cancel_all_orders(self, symbol=None, params={}):
          await self.load_markets()
          return await self.privatePostCancelAll(params)
  
      async def fetch_open_orders(self, symbol=None, since=None, limit=None, params={}):
          await self.load_markets()
          request = {}
          if since is not None:
              request['start'] = int(since / 1000)
          query = params
          clientOrderId = self.safe_value_2(params, 'userref', 'clientOrderId')
          if clientOrderId is not None:
              request['userref'] = clientOrderId
              query = self.omit(params, ['userref', 'clientOrderId'])
          response = await self.privatePostOpenOrders(self.extend(request, query))
          market = None
          if symbol is not None:
              market = self.market(symbol)
          result = self.safe_value(response, 'result', {})
          orders = self.safe_value(result, 'open', [])
          return self.parse_orders(orders, market, since, limit)
  
      async def fetch_closed_orders(self, symbol=None, since=None, limit=None, params={}):
          await self.load_markets()
          request = {}
          if since is not None:
              request['start'] = int(since / 1000)
          query = params
          clientOrderId = self.safe_value_2(params, 'userref', 'clientOrderId')
          if clientOrderId is not None:
              request['userref'] = clientOrderId
              query = self.omit(params, ['userref', 'clientOrderId'])
          response = await self.privatePostClosedOrders(self.extend(request, query))
          #
          #     {
          #         "error":[],
          #         "result":{
          #             "closed":{
          #                 "OETZYO-UL524-QJMXCT":{
          #                     "refid":null,
          #                     "userref":null,
          #                     "status":"canceled",
          #                     "reason":"User requested",
          #                     "opentm":1601489313.3898,
          #                     "closetm":1601489346.5507,
          #                     "starttm":0,
          #                     "expiretm":0,
          #                     "descr":{
          #                         "pair":"ETHUSDT",
          #                         "type":"buy",
          #                         "ordertype":"limit",
          #                         "price":"330.00",
          #                         "price2":"0",
          #                         "leverage":"none",
          #                         "order":"buy 0.02100000 ETHUSDT @ limit 330.00",
          #                         "close":""
          #                     },
          #                     "vol":"0.02100000",
          #                     "vol_exec":"0.00000000",
          #                     "cost":"0.00000",
          #                     "fee":"0.00000",
          #                     "price":"0.00000",
          #                     "stopprice":"0.00000",
          #                     "limitprice":"0.00000",
          #                     "misc":"",
          #                     "oflags":"fciq"
          #                 },
          #             },
          #             "count":16
          #         }
          #     }
          #
          market = None
          if symbol is not None:
              market = self.market(symbol)
          result = self.safe_value(response, 'result', {})
          orders = self.safe_value(result, 'closed', [])
          return self.parse_orders(orders, market, since, limit)
  
      def parse_transaction_status(self, status):
          # IFEX transaction states
          statuses = {
              'Initial': 'pending',
              'Pending': 'pending',
              'Success': 'ok',
              'Settled': 'pending',
              'Failure': 'failed',
              'Partial': 'ok',
          }
          return self.safe_string(statuses, status, status)
  
      def parse_transaction(self, transaction, currency=None):
          #
          # fetchDeposits
          #
          #     {method: "Ether(Hex)",
          #       aclass: "currency",
          #        asset: "XETH",
          #        refid: "Q2CANKL-LBFVEE-U4Y2WQ",
          #         txid: "0x57fd704dab1a73c20e24c8696099b695d596924b401b261513cfdab23…",
          #         info: "0x615f9ba7a9575b0ab4d571b2b36b1b324bd83290",
          #       amount: "7.9999257900",
          #          fee: "0.0000000000",
          #         time:  1529223212,
          #       status: "Success"                                                       }
          #
          #
          # 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"                                                             }
          #
          # withdrawals may also have an additional 'status-prop' field present
          #
          #
          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': None,
              '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),
              'fee': {
                  'currency': code,
                  'cost': feeCost,
              },
          }
  
      def parse_transactions_by_type(self, type, transactions, code=None, since=None, limit=None):
          result = []
          for i in range(0, len(transactions)):
              transaction = self.parse_transaction(self.extend({
                  'type': type,
              }, transactions[i]))
              result.append(transaction)
          return self.filter_by_currency_since_limit(result, code, since, limit)
  
      async def fetch_deposits(self, code=None, since=None, limit=None, params={}):
          # https://www.kraken.com/en-us/help/api#deposit-status
          if code is None:
              raise ArgumentsRequired(self.id + ' fetchDeposits() requires a currency code argument')
          await self.load_markets()
          currency = self.currency(code)
          request = {
              'asset': currency['id'],
          }
          response = await 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)
  
      async def fetch_time(self, params={}):
          # https://www.kraken.com/en-us/features/api#get-server-time
          response = await 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')
  
      async def fetch_withdrawals(self, code=None, since=None, limit=None, params={}):
          # https://www.kraken.com/en-us/help/api#withdraw-status
          if code is None:
              raise ArgumentsRequired(self.id + ' fetchWithdrawals() requires a currency code argument')
          await self.load_markets()
          currency = self.currency(code)
          request = {
              'asset': currency['id'],
          }
          response = await self.privatePostWithdrawStatus(self.extend(request, params))
          #
          #     { error: [],
          #       result: [{method: "Ether",
          #                   aclass: "currency",
          #                    asset: "XETH",
          #                    refid: "A2BF34S-O7LBNQ-UE4Y4O",
          #                     txid: "0x298c83c7b0904d8400ef43e1c9e2287b518f7ea3d838822d53f704a1565c274d",
          #                     info: "0x7cb275a5e07ba943fee972e165d80daa67cb2dd0",
          #                   amount: "9.9950000000",
          #                      fee: "0.0050000000",
          #                     time:  1530481750,
          #                   status: "Success"                                                             }]}
          #
          return self.parse_transactions_by_type('withdrawal', response['result'], code, since, limit)
  
      async def create_deposit_address(self, code, params={}):
          request = {
              'new': 'true',
          }
          return await self.fetch_deposit_address(code, self.extend(request, params))
  
      async def fetch_deposit_methods(self, code, params={}):
          await self.load_markets()
          currency = self.currency(code)
          request = {
              'asset': currency['id'],
          }
          response = await 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')
  
      async def fetch_deposit_address(self, code, params={}):
          await 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 as is, otherwise we take the 'network' unified param
          if depositMethod is None:
              depositMethods = await self.fetch_deposit_methods(code)
              if network is not None:
                  # find best matching deposit method, or fallback to the first one
                  for i in range(0, len(depositMethods)):
                      entry = self.safe_string(depositMethods[i], 'method')
                      if entry.find(network) >= 0:
                          depositMethod = entry
                          break
              # if depositMethod was not specified, fallback to the first available deposit method
              if depositMethod is None:
                  firstDepositMethod = self.safe_value(depositMethods, 0, {})
                  depositMethod = self.safe_string(firstDepositMethod, 'method')
          request = {
              'asset': currency['id'],
              'method': depositMethod,
          }
          response = await 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=None):
          #
          #     {
          #         "address":"0x77b5051f97efa9cc52c9ad5b023a53fc15c200d3",
          #         "expiretm":"0"
          #     }
          #
          address = self.safe_string(depositAddress, 'address')
          tag = self.safe_string(depositAddress, 'tag')
          currency = self.safe_currency(None, currency)
          code = currency['code']
          self.check_address(address)
          return {
              'currency': code,
              'address': address,
              'tag': tag,
              'network': None,
              'info': depositAddress,
          }
  
      async def withdraw(self, code, amount, address, tag=None, params={}):
          tag, params = self.handle_withdraw_tag_and_params(tag, params)
          self.check_address(address)
          if 'key' in params:
              await self.load_markets()
              currency = self.currency(code)
              request = {
                  'asset': currency['id'],
                  'amount': amount,
                  # 'address': address,  # they don't allow withdrawals to direct addresses
              }
              response = await self.privatePostWithdraw(self.extend(request, params))
              result = self.safe_value(response, 'result', {})
              id = self.safe_string(result, 'refid')
              return {
                  'info': result,
                  'id': id,
              }
          raise ExchangeError(self.id + " withdraw() requires a 'key' parameter(withdrawal key name, as set up on your account)")
  
      async def fetch_positions(self, symbols=None, params={}):
          await self.load_markets()
          request = {
              # 'txid': 'comma delimited list of transaction ids to restrict output to',
              # 'docalcs': False,  # whether or not to include profit/loss calculations
              # 'consolidation': 'market',  # what to consolidate the positions data around, market will consolidate positions based on market pair
          }
          response = await self.privatePostOpenPositions(self.extend(request, params))
          #
          # no consolidation
          #
          #     {
          #         error: [],
          #         result: {
          #             'TGUFMY-FLESJ-VYIX3J': {
          #                 ordertxid: "O3LRNU-ZKDG5-XNCDFR",
          #                 posstatus: "open",
          #                 pair: "ETHUSDT",
          #                 time:  1611557231.4584,
          #                 type: "buy",
          #                 ordertype: "market",
          #                 cost: "28.49800",
          #                 fee: "0.07979",
          #                 vol: "0.02000000",
          #                 vol_closed: "0.00000000",
          #                 margin: "14.24900",
          #                 terms: "0.0200% per 4 hours",
          #                 rollovertm: "1611571631",
          #                 misc: "",
          #                 oflags: ""
          #             }
          #         }
          #     }
          #
          # consolidation by market
          #
          #     {
          #         error: [],
          #         result: [
          #             {
          #                 pair: "ETHUSDT",
          #                 positions: "1",
          #                 type: "buy",
          #                 leverage: "2.00000",
          #                 cost: "28.49800",
          #                 fee: "0.07979",
          #                 vol: "0.02000000",
          #                 vol_closed: "0.00000000",
          #                 margin: "14.24900"
          #             }
          #         ]
          #     }
          #
          result = self.safe_value(response, 'result')
          # todo unify parsePosition/parsePositions
          return result
  
      def sign(self, path, api='public', method='GET', params={}, headers=None, body=None):
          url = '/' + self.version + '/' + api + '/' + path
          if api == 'public':
              if params:
                  url += '?' + self.urlencode(params)
          elif api == 'private':
              self.check_required_credentials()
              nonce = str(self.nonce())
              body = self.urlencode(self.extend({'nonce': nonce}, params))
              auth = self.encode(nonce + body)
              hash = self.hash(auth, 'sha256', 'binary')
              binary = self.encode(url)
              binhash = self.binary_concat(binary, hash)
              secret = self.base64_to_binary(self.secret)
              signature = self.hmac(binhash, secret, hashlib.sha512, 'base64')
              headers = {
                  'API-Key': self.apiKey,
                  'API-Sign': signature,
                  'Content-Type': 'application/x-www-form-urlencoded',
              }
          else:
              url = '/' + path
          url = self.urls['api'][api] + url
          return {'url': url, 'method': method, 'body': body, 'headers': headers}
  
      def nonce(self):
          return self.milliseconds()
  
      def handle_errors(self, code, reason, url, method, headers, body, response, requestHeaders, requestBody):
          if code == 520:
              raise ExchangeNotAvailable(self.id + ' ' + str(code) + ' ' + reason)
          # todo: rewrite self for "broad" exceptions matching
          if body.find('Invalid order') >= 0:
              raise InvalidOrder(self.id + ' ' + body)
          if body.find('Invalid nonce') >= 0:
              raise InvalidNonce(self.id + ' ' + body)
          if body.find('Insufficient funds') >= 0:
              raise InsufficientFunds(self.id + ' ' + body)
          if body.find('Cancel pending') >= 0:
              raise CancelPending(self.id + ' ' + body)
          if body.find('Invalid arguments:volume') >= 0:
              raise InvalidOrder(self.id + ' ' + body)
          if body.find('Rate limit exceeded') >= 0:
              raise RateLimitExceeded(self.id + ' ' + body)
          if response is None:
              return
          if body[0] == '{':
              if not isinstance(response, str):
                  if 'error' in response:
                      numErrors = len(response['error'])
                      if numErrors:
                          message = self.id + ' ' + body
                          for i in range(0, len(response['error'])):
                              error = response['error'][i]
                              self.throw_exactly_matched_exception(self.exceptions, error, message)
                          raise ExchangeError(message)
  