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
  
  # -----------------------------------------------------------------------------
  
  try:
      basestring  # Python 3
  except NameError:
      basestring = str  # Python 2
  import hashlib
  import math
  from ccxt.base.errors import ExchangeError
  from ccxt.base.errors import AuthenticationError
  from ccxt.base.errors import PermissionDenied
  from ccxt.base.errors import InsufficientFunds
  from ccxt.base.errors import AddressPending
  from ccxt.base.errors import InvalidOrder
  from ccxt.base.errors import OrderNotFound
  from ccxt.base.errors import DDoSProtection
  from ccxt.base.errors import ExchangeNotAvailable
  from ccxt.base.decimal_to_precision import TRUNCATE
  from ccxt.base.decimal_to_precision import DECIMAL_PLACES
  
  
  class bittrex (Exchange):
  
      def describe(self):
          return self.deep_extend(super(bittrex, self).describe(), {
              'id': 'bittrex',
              'name': 'Bittrex',
              'countries': ['US'],
              'version': 'v1.1',
              'rateLimit': 1500,
              'certified': True,
              # new metainfo interface
              'has': {
                  'CORS': True,
                  'createMarketOrder': False,
                  'fetchDepositAddress': True,
                  'fetchClosedOrders': True,
                  'fetchCurrencies': True,
                  'fetchMyTrades': 'emulated',
                  'fetchOHLCV': True,
                  'fetchOrder': True,
                  'fetchOpenOrders': True,
                  'fetchTickers': True,
                  'withdraw': True,
                  'fetchDeposits': True,
                  'fetchWithdrawals': True,
                  'fetchTransactions': False,
              },
              'timeframes': {
                  '1m': 'oneMin',
                  '5m': 'fiveMin',
                  '30m': 'thirtyMin',
                  '1h': 'hour',
                  '1d': 'day',
              },
              'hostname': 'bittrex.com',
              'urls': {
                  'logo': 'https://user-images.githubusercontent.com/1294454/27766352-cf0b3c26-5ed5-11e7-82b7-f3826b7a97d8.jpg',
                  'api': {
                      'public': 'https://{hostname}/api',
                      'account': 'https://{hostname}/api',
                      'market': 'https://{hostname}/api',
                      'v2': 'https://{hostname}/api/v2.0/pub',
                      'v3': 'https://api.bittrex.com/v3',
                      'v3public': 'https://api.bittrex.com/v3',
                  },
                  'www': 'https://bittrex.com',
                  'doc': [
                      'https://bittrex.github.io/api/',
                      'https://bittrex.github.io/api/v3',
                      'https://www.npmjs.com/package/bittrex-node',
                  ],
                  'fees': [
                      'https://bittrex.zendesk.com/hc/en-us/articles/115003684371-BITTREX-SERVICE-FEES-AND-WITHDRAWAL-LIMITATIONS',
                      'https://bittrex.zendesk.com/hc/en-us/articles/115000199651-What-fees-does-Bittrex-charge-',
                  ],
              },
              'api': {
                  'v3': {
                      'get': [
                          'account',
                          'addresses',
                          'addresses/{currencySymbol}',
                          'balances',
                          'balances/{currencySymbol}',
                          'currencies',
                          'currencies/{symbol}',
                          'deposits/open',
                          'deposits/closed',
                          'deposits/ByTxId/{txId}',
                          'deposits/{depositId}',
                          'orders/closed',
                          'orders/open',
                          'orders/{orderId}',
                          'ping',
                          'subaccounts/{subaccountId}',
                          'subaccounts',
                          'withdrawals/open',
                          'withdrawals/closed',
                          'withdrawals/ByTxId/{txId}',
                          'withdrawals/{withdrawalId}',
                      ],
                      'post': [
                          'addresses',
                          'orders',
                          'subaccounts',
                          'withdrawals',
                      ],
                      'delete': [
                          'orders/{orderId}',
                          'withdrawals/{withdrawalId}',
                      ],
                  },
                  'v3public': {
                      'get': [
                          'markets',
                          'markets/summaries',
                          'markets/{marketSymbol}',
                          'markets/{marketSymbol}/summary',
                          'markets/{marketSymbol}/orderbook',
                          'markets/{marketSymbol}/trades',
                          'markets/{marketSymbol}/ticker',
                          'markets/{marketSymbol}/candles',
                      ],
                  },
                  'v2': {
                      'get': [
                          'currencies/GetBTCPrice',
                          'market/GetTicks',
                          'market/GetLatestTick',
                          'Markets/GetMarketSummaries',
                          'market/GetLatestTick',
                      ],
                  },
                  'public': {
                      'get': [
                          'currencies',
                          'markethistory',
                          'markets',
                          'marketsummaries',
                          'marketsummary',
                          'orderbook',
                          'ticker',
                      ],
                  },
                  'account': {
                      'get': [
                          'balance',
                          'balances',
                          'depositaddress',
                          'deposithistory',
                          'order',
                          'orders',
                          'orderhistory',
                          'withdrawalhistory',
                          'withdraw',
                      ],
                  },
                  'market': {
                      'get': [
                          'buylimit',
                          'buymarket',
                          'cancel',
                          'openorders',
                          'selllimit',
                          'sellmarket',
                      ],
                  },
              },
              'fees': {
                  'trading': {
                      'tierBased': False,
                      'percentage': True,
                      'maker': 0.0025,
                      'taker': 0.0025,
                  },
                  'funding': {
                      'tierBased': False,
                      'percentage': False,
                      'withdraw': {
                          'BTC': 0.0005,
                          'LTC': 0.01,
                          'DOGE': 2,
                          'VTC': 0.02,
                          'PPC': 0.02,
                          'FTC': 0.2,
                          'RDD': 2,
                          'NXT': 2,
                          'DASH': 0.05,
                          'POT': 0.002,
                          'BLK': 0.02,
                          'EMC2': 0.2,
                          'XMY': 0.2,
                          'GLD': 0.0002,
                          'SLR': 0.2,
                          'GRS': 0.2,
                      },
                      'deposit': {
                          'BTC': 0,
                          'LTC': 0,
                          'DOGE': 0,
                          'VTC': 0,
                          'PPC': 0,
                          'FTC': 0,
                          'RDD': 0,
                          'NXT': 0,
                          'DASH': 0,
                          'POT': 0,
                          'BLK': 0,
                          'EMC2': 0,
                          'XMY': 0,
                          'GLD': 0,
                          'SLR': 0,
                          'GRS': 0,
                      },
                  },
              },
              'exceptions': {
                  # 'Call to Cancel was throttled. Try again in 60 seconds.': DDoSProtection,
                  # 'Call to GetBalances was throttled. Try again in 60 seconds.': DDoSProtection,
                  'APISIGN_NOT_PROVIDED': AuthenticationError,
                  'INVALID_SIGNATURE': AuthenticationError,
                  'INVALID_CURRENCY': ExchangeError,
                  'INVALID_PERMISSION': AuthenticationError,
                  'INSUFFICIENT_FUNDS': InsufficientFunds,
                  'QUANTITY_NOT_PROVIDED': InvalidOrder,
                  'MIN_TRADE_REQUIREMENT_NOT_MET': InvalidOrder,
                  'ORDER_NOT_OPEN': OrderNotFound,
                  'INVALID_ORDER': InvalidOrder,
                  'UUID_INVALID': OrderNotFound,
                  'RATE_NOT_PROVIDED': InvalidOrder,  # createLimitBuyOrder('ETH/BTC', 1, 0)
                  'WHITELIST_VIOLATION_IP': PermissionDenied,
                  'DUST_TRADE_DISALLOWED_MIN_VALUE': InvalidOrder,
              },
              'options': {
                  'parseOrderStatus': False,
                  'hasAlreadyAuthenticatedSuccessfully': False,  # a workaround for APIKEY_INVALID
                  'symbolSeparator': '-',
                  # With certain currencies, like
                  # AEON, BTS, GXS, NXT, SBD, STEEM, STR, XEM, XLM, XMR, XRP
                  # an additional tag / memo / payment id is usually required by exchanges.
                  # With Bittrex some currencies imply the "base address + tag" logic.
                  # The base address for depositing is stored on self.currencies[code]
                  # The base address identifies the exchange as the recipient
                  # while the tag identifies the user account within the exchange
                  # and the tag is retrieved with fetchDepositAddress.
                  'tag': {
                      'NXT': True,  # NXT, BURST
                      'CRYPTO_NOTE_PAYMENTID': True,  # AEON, XMR
                      'BITSHAREX': True,  # BTS
                      'RIPPLE': True,  # XRP
                      'NEM': True,  # XEM
                      'STELLAR': True,  # XLM
                      'STEEM': True,  # SBD, GOLOS
                      # https://github.com/ccxt/ccxt/issues/4794
                      # 'LISK': True,  # LSK
                  },
                  'subaccountId': None,
                  # see the implementation of fetchClosedOrdersV3 below
                  'fetchClosedOrdersMethod': 'fetch_closed_orders_v3',
                  'fetchClosedOrdersFilterBySince': True,
              },
              'commonCurrencies': {
                  'BITS': 'SWIFT',
                  'CPC': 'Capricoin',
              },
          })
  
      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']['price'], DECIMAL_PLACES)
  
      async def fetch_markets(self, params={}):
          response = await self.v3publicGetMarkets(params)
          #
          #     [
          #         {
          #             "symbol":"LTC-BTC",
          #             "baseCurrencySymbol":"LTC",
          #             "quoteCurrencySymbol":"BTC",
          #             "minTradeSize":"0.01686767",
          #             "precision":8,
          #             "status":"ONLINE",  # "OFFLINE"
          #             "createdAt":"2014-02-13T00:00:00Z"
          #         },
          #         {
          #             "symbol":"VDX-USDT",
          #             "baseCurrencySymbol":"VDX",
          #             "quoteCurrencySymbol":"USDT",
          #             "minTradeSize":"300.00000000",
          #             "precision":8,
          #             "status":"ONLINE",  # "OFFLINE"
          #             "createdAt":"2019-05-23T00:41:21.843Z",
          #             "notice":"USDT has swapped to an ERC20-based token as of August 5, 2019."
          #         }
          #     ]
          #
          result = []
          # markets = self.safe_value(response, 'result')
          for i in range(0, len(response)):
              market = response[i]
              baseId = self.safe_string(market, 'baseCurrencySymbol')
              quoteId = self.safe_string(market, 'quoteCurrencySymbol')
              # bittrex v2 uses inverted pairs, v3 uses regular pairs
              # we use v3 for fetchMarkets and v2 throughout the rest of self implementation
              # therefore we swap the base ←→ quote here to be v2-compatible
              # https://github.com/ccxt/ccxt/issues/5634
              # id = self.safe_string(market, 'symbol')
              id = quoteId + self.options['symbolSeparator'] + baseId
              base = self.safe_currency_code(baseId)
              quote = self.safe_currency_code(quoteId)
              symbol = base + '/' + quote
              pricePrecision = self.safe_integer(market, 'precision', 8)
              precision = {
                  'amount': 8,
                  'price': pricePrecision,
              }
              status = self.safe_string(market, 'status')
              active = (status == 'ONLINE')
              result.append({
                  'id': id,
                  'symbol': symbol,
                  'base': base,
                  'quote': quote,
                  'baseId': baseId,
                  'quoteId': quoteId,
                  'active': active,
                  'info': market,
                  'precision': precision,
                  'limits': {
                      'amount': {
                          'min': self.safe_float(market, 'minTradeSize'),
                          'max': None,
                      },
                      'price': {
                          'min': math.pow(10, -precision['price']),
                          'max': None,
                      },
                  },
              })
          return result
  
      async def fetch_balance(self, params={}):
          await self.load_markets()
          response = await self.accountGetBalances(params)
          balances = self.safe_value(response, 'result')
          result = {'info': balances}
          indexed = self.index_by(balances, 'Currency')
          currencyIds = list(indexed.keys())
          for i in range(0, len(currencyIds)):
              currencyId = currencyIds[i]
              code = self.safe_currency_code(currencyId)
              account = self.account()
              balance = indexed[currencyId]
              account['free'] = self.safe_float(balance, 'Available')
              account['total'] = self.safe_float(balance, 'Balance')
              result[code] = account
          return self.parse_balance(result)
  
      async def fetch_order_book(self, symbol, limit=None, params={}):
          await self.load_markets()
          request = {
              'market': self.market_id(symbol),
              'type': 'both',
          }
          response = await self.publicGetOrderbook(self.extend(request, params))
          orderbook = response['result']
          if 'type' in params:
              if params['type'] == 'buy':
                  orderbook = {
                      'buy': response['result'],
                      'sell': [],
                  }
              elif params['type'] == 'sell':
                  orderbook = {
                      'buy': [],
                      'sell': response['result'],
                  }
          return self.parse_order_book(orderbook, None, 'buy', 'sell', 'Rate', 'Quantity')
  
      def parse_ticker(self, ticker, market=None):
          timestamp = self.parse8601(self.safe_string(ticker, 'TimeStamp'))
          symbol = None
          if market is not None:
              symbol = market['symbol']
          previous = self.safe_float(ticker, 'PrevDay')
          last = self.safe_float(ticker, 'Last')
          change = None
          percentage = None
          if last is not None:
              if previous is not None:
                  change = last - previous
                  if previous > 0:
                      percentage = (change / previous) * 100
          return {
              'symbol': symbol,
              'timestamp': timestamp,
              'datetime': self.iso8601(timestamp),
              'high': self.safe_float(ticker, 'High'),
              'low': self.safe_float(ticker, 'Low'),
              'bid': self.safe_float(ticker, 'Bid'),
              'bidVolume': None,
              'ask': self.safe_float(ticker, 'Ask'),
              'askVolume': None,
              'vwap': None,
              'open': previous,
              'close': last,
              'last': last,
              'previousClose': None,
              'change': change,
              'percentage': percentage,
              'average': None,
              'baseVolume': self.safe_float(ticker, 'Volume'),
              'quoteVolume': self.safe_float(ticker, 'BaseVolume'),
              'info': ticker,
          }
  
      async def fetch_currencies(self, params={}):
          response = await self.publicGetCurrencies(params)
          #
          #     {
          #         "success": True,
          #         "message": "",
          #         "result": [
          #             {
          #                 "Currency": "BTC",
          #                 "CurrencyLong":"Bitcoin",
          #                 "MinConfirmation":2,
          #                 "TxFee":0.00050000,
          #                 "IsActive":true,
          #                 "IsRestricted":false,
          #                 "CoinType":"BITCOIN",
          #                 "BaseAddress":"1N52wHoVR79PMDishab2XmRHsbekCdGquK",
          #                 "Notice":null
          #             },
          #             ...,
          #         ]
          #     }
          #
          currencies = self.safe_value(response, 'result', [])
          result = {}
          for i in range(0, len(currencies)):
              currency = currencies[i]
              id = self.safe_string(currency, 'Currency')
              # todo: will need to rethink the fees
              # to add support for multiple withdrawal/deposit methods and
              # differentiated fees for each particular method
              code = self.safe_currency_code(id)
              precision = 8  # default precision, todo: fix "magic constants"
              address = self.safe_value(currency, 'BaseAddress')
              fee = self.safe_float(currency, 'TxFee')  # todo: redesign
              result[code] = {
                  'id': id,
                  'code': code,
                  'address': address,
                  'info': currency,
                  'type': currency['CoinType'],
                  'name': currency['CurrencyLong'],
                  'active': currency['IsActive'],
                  'fee': fee,
                  'precision': precision,
                  'limits': {
                      'amount': {
                          'min': math.pow(10, -precision),
                          'max': math.pow(10, precision),
                      },
                      'price': {
                          'min': math.pow(10, -precision),
                          'max': math.pow(10, precision),
                      },
                      'cost': {
                          'min': None,
                          'max': None,
                      },
                      'withdraw': {
                          'min': fee,
                          'max': math.pow(10, precision),
                      },
                  },
              }
          return result
  
      async def fetch_tickers(self, symbols=None, params={}):
          await self.load_markets()
          response = await self.publicGetMarketsummaries(params)
          tickers = self.safe_value(response, 'result')
          result = {}
          for t in range(0, len(tickers)):
              ticker = tickers[t]
              marketId = self.safe_string(ticker, 'MarketName')
              market = None
              symbol = marketId
              if marketId in self.markets_by_id:
                  market = self.markets_by_id[marketId]
                  symbol = market['symbol']
              else:
                  symbol = self.parse_symbol(marketId)
              result[symbol] = self.parse_ticker(ticker, market)
          return result
  
      async def fetch_ticker(self, symbol, params={}):
          await self.load_markets()
          market = self.market(symbol)
          request = {
              'market': market['id'],
          }
          response = await self.publicGetMarketsummary(self.extend(request, params))
          ticker = response['result'][0]
          return self.parse_ticker(ticker, market)
  
      def parse_trade(self, trade, market=None):
          timestamp = self.parse8601(trade['TimeStamp'] + '+00:00')
          side = None
          if trade['OrderType'] == 'BUY':
              side = 'buy'
          elif trade['OrderType'] == 'SELL':
              side = 'sell'
          id = self.safe_string_2(trade, 'Id', 'ID')
          symbol = None
          if market is not None:
              symbol = market['symbol']
          cost = None
          price = self.safe_float(trade, 'Price')
          amount = self.safe_float(trade, 'Quantity')
          if amount is not None:
              if price is not None:
                  cost = price * amount
          return {
              'info': trade,
              'timestamp': timestamp,
              'datetime': self.iso8601(timestamp),
              'symbol': symbol,
              'id': id,
              'order': None,
              'type': 'limit',
              'takerOrMaker': None,
              'side': side,
              'price': price,
              'amount': amount,
              'cost': cost,
              'fee': None,
          }
  
      async def fetch_trades(self, symbol, since=None, limit=None, params={}):
          await self.load_markets()
          market = self.market(symbol)
          request = {
              'market': market['id'],
          }
          response = await self.publicGetMarkethistory(self.extend(request, params))
          if 'result' in response:
              if response['result'] is not None:
                  return self.parse_trades(response['result'], market, since, limit)
          raise ExchangeError(self.id + ' fetchTrades() returned None response')
  
      def parse_ohlcv(self, ohlcv, market=None, timeframe='1d', since=None, limit=None):
          timestamp = self.parse8601(ohlcv['T'] + '+00:00')
          return [
              timestamp,
              ohlcv['O'],
              ohlcv['H'],
              ohlcv['L'],
              ohlcv['C'],
              ohlcv['V'],
          ]
  
      async def fetch_ohlcv(self, symbol, timeframe='1m', since=None, limit=None, params={}):
          await self.load_markets()
          market = self.market(symbol)
          request = {
              'tickInterval': self.timeframes[timeframe],
              'marketName': market['id'],
          }
          response = await self.v2GetMarketGetTicks(self.extend(request, params))
          if 'result' in response:
              if response['result']:
                  return self.parse_ohlcvs(response['result'], market, timeframe, since, limit)
  
      async def fetch_open_orders(self, symbol=None, since=None, limit=None, params={}):
          await self.load_markets()
          request = {}
          market = None
          if symbol is not None:
              market = self.market(symbol)
              request['market'] = market['id']
          response = await self.marketGetOpenorders(self.extend(request, params))
          result = self.safe_value(response, 'result', [])
          orders = self.parse_orders(result, market, since, limit)
          return self.filter_by_symbol(orders, symbol)
  
      async def create_order(self, symbol, type, side, amount, price=None, params={}):
          if type != 'limit':
              raise ExchangeError(self.id + ' allows limit orders only')
          await self.load_markets()
          market = self.market(symbol)
          method = 'marketGet' + self.capitalize(side) + type
          request = {
              'market': market['id'],
              'quantity': self.amount_to_precision(symbol, amount),
              'rate': self.price_to_precision(symbol, price),
          }
          # if type == 'limit':
          #     order['rate'] = self.price_to_precision(symbol, price)
          response = await getattr(self, method)(self.extend(request, params))
          orderIdField = self.get_order_id_field()
          orderId = self.safe_string(response['result'], orderIdField)
          return {
              'info': response,
              'id': orderId,
              'symbol': symbol,
              'type': type,
              'side': side,
              'status': 'open',
          }
  
      def get_order_id_field(self):
          return 'uuid'
  
      async def cancel_order(self, id, symbol=None, params={}):
          await self.load_markets()
          orderIdField = self.get_order_id_field()
          request = {}
          request[orderIdField] = id
          response = await self.marketGetCancel(self.extend(request, params))
          #
          #     {
          #         "success": True,
          #         "message": "''",
          #         "result": {
          #             "uuid": "614c34e4-8d71-11e3-94b5-425861b86ab6"
          #         }
          #     }
          #
          return self.extend(self.parse_order(response), {
              'status': 'canceled',
          })
  
      async def fetch_deposits(self, code=None, since=None, limit=None, params={}):
          await self.load_markets()
          # https://support.bittrex.com/hc/en-us/articles/115003723911
          request = {}
          currency = None
          if code is not None:
              currency = self.currency(code)
              request['currency'] = currency['id']
          response = await self.accountGetDeposithistory(self.extend(request, params))
          #
          #     {success:    True,
          #       message:   "",
          #        result: [{           Id:  22578097,
          #                           Amount:  0.3,
          #                         Currency: "ETH",
          #                    Confirmations:  15,
          #                      LastUpdated: "2018-06-10T07:12:10.57",
          #                             TxId: "0xf50b5ba2ca5438b58f93516eaa523eaf35b4420ca0f24061003df1be7…",
          #                    CryptoAddress: "0xb25f281fa51f1635abd4a60b0870a62d2a7fa404"                    }]}
          #
          # we cannot filter by `since` timestamp, as it isn't set by Bittrex
          # see https://github.com/ccxt/ccxt/issues/4067
          # return self.parseTransactions(response['result'], currency, since, limit)
          return self.parseTransactions(response['result'], currency, None, limit)
  
      async def fetch_withdrawals(self, code=None, since=None, limit=None, params={}):
          await self.load_markets()
          # https://support.bittrex.com/hc/en-us/articles/115003723911
          request = {}
          currency = None
          if code is not None:
              currency = self.currency(code)
              request['currency'] = currency['id']
          response = await self.accountGetWithdrawalhistory(self.extend(request, params))
          #
          #     {
          #         "success" : True,
          #         "message" : "",
          #         "result" : [{
          #                 "PaymentUuid" : "b32c7a5c-90c6-4c6e-835c-e16df12708b1",
          #                 "Currency" : "BTC",
          #                 "Amount" : 17.00000000,
          #                 "Address" : "1DfaaFBdbB5nrHj87x3NHS4onvw1GPNyAu",
          #                 "Opened" : "2014-07-09T04:24:47.217",
          #                 "Authorized" : True,
          #                 "PendingPayment" : False,
          #                 "TxCost" : 0.00020000,
          #                 "TxId" : null,
          #                 "Canceled" : True,
          #                 "InvalidAddress" : False
          #             }, {
          #                 "PaymentUuid" : "d193da98-788c-4188-a8f9-8ec2c33fdfcf",
          #                 "Currency" : "XC",
          #                 "Amount" : 7513.75121715,
          #                 "Address" : "TcnSMgAd7EonF2Dgc4c9K14L12RBaW5S5J",
          #                 "Opened" : "2014-07-08T23:13:31.83",
          #                 "Authorized" : True,
          #                 "PendingPayment" : False,
          #                 "TxCost" : 0.00002000,
          #                 "TxId" : "d8a575c2a71c7e56d02ab8e26bb1ef0a2f6cf2094f6ca2116476a569c1e84f6e",
          #                 "Canceled" : False,
          #                 "InvalidAddress" : False
          #             }
          #         ]
          #     }
          #
          return self.parseTransactions(response['result'], currency, since, limit)
  
      def parse_transaction(self, transaction, currency=None):
          #
          # fetchDeposits
          #
          #     {
          #         Id:  72578097,
          #         Amount:  0.3,
          #         Currency: "ETH",
          #         Confirmations:  15,
          #         LastUpdated: "2018-06-17T07:12:14.57",
          #         TxId: "0xb31b5ba2ca5438b58f93516eaa523eaf35b4420ca0f24061003df1be7…",
          #         CryptoAddress: "0x2d5f281fa51f1635abd4a60b0870a62d2a7fa404"
          #     }
          #
          # fetchWithdrawals
          #
          #     {
          #         "PaymentUuid" : "e293da98-788c-4188-a8f9-8ec2c33fdfcf",
          #         "Currency" : "XC",
          #         "Amount" : 7513.75121715,
          #         "Address" : "EVnSMgAd7EonF2Dgc4c9K14L12RBaW5S5J",
          #         "Opened" : "2014-07-08T23:13:31.83",
          #         "Authorized" : True,
          #         "PendingPayment" : False,
          #         "TxCost" : 0.00002000,
          #         "TxId" : "b4a575c2a71c7e56d02ab8e26bb1ef0a2f6cf2094f6ca2116476a569c1e84f6e",
          #         "Canceled" : False,
          #         "InvalidAddress" : False
          #     }
          #
          id = self.safe_string_2(transaction, 'Id', 'PaymentUuid')
          amount = self.safe_float(transaction, 'Amount')
          address = self.safe_string_2(transaction, 'CryptoAddress', 'Address')
          txid = self.safe_string(transaction, 'TxId')
          updated = self.parse8601(self.safe_string(transaction, 'LastUpdated'))
          opened = self.parse8601(self.safe_string(transaction, 'Opened'))
          timestamp = opened if opened else updated
          type = 'deposit' if (opened is None) else 'withdrawal'
          currencyId = self.safe_string(transaction, 'Currency')
          code = self.safe_currency_code(currencyId, currency)
          status = 'pending'
          if type == 'deposit':
              #
              # deposits numConfirmations never reach the minConfirmations number
              # we set all of them to 'ok', otherwise they'd all be 'pending'
              #
              #     numConfirmations = self.safe_integer(transaction, 'Confirmations', 0)
              #     minConfirmations = self.safe_integer(currency['info'], 'MinConfirmation')
              #     if numConfirmations >= minConfirmations:
              #         status = 'ok'
              #     }
              #
              status = 'ok'
          else:
              authorized = self.safe_value(transaction, 'Authorized', False)
              pendingPayment = self.safe_value(transaction, 'PendingPayment', False)
              canceled = self.safe_value(transaction, 'Canceled', False)
              invalidAddress = self.safe_value(transaction, 'InvalidAddress', False)
              if invalidAddress:
                  status = 'failed'
              elif canceled:
                  status = 'canceled'
              elif pendingPayment:
                  status = 'pending'
              elif authorized and(txid is not None):
                  status = 'ok'
          feeCost = self.safe_float(transaction, 'TxCost')
          if feeCost is None:
              if type == 'deposit':
                  # according to https://support.bittrex.com/hc/en-us/articles/115000199651-What-fees-does-Bittrex-charge-
                  feeCost = 0  # FIXME: remove hardcoded value that may change any time
          return {
              'info': transaction,
              'id': id,
              'currency': code,
              'amount': amount,
              'address': address,
              'tag': None,
              'status': status,
              'type': type,
              'updated': updated,
              'txid': txid,
              'timestamp': timestamp,
              'datetime': self.iso8601(timestamp),
              'fee': {
                  'currency': code,
                  'cost': feeCost,
              },
          }
  
      def parse_symbol(self, id):
          quoteId, baseId = id.split(self.options['symbolSeparator'])
          base = self.safe_currency_code(baseId)
          quote = self.safe_currency_code(quoteId)
          return base + '/' + quote
  
      def parse_order(self, order, market=None):
          if 'marketSymbol' in order:
              return self.parse_order_v3(order, market)
          else:
              return self.parse_order_v2(order, market)
  
      def parse_orders(self, orders, market=None, since=None, limit=None, params={}):
          if self.options['fetchClosedOrdersFilterBySince']:
              return super(bittrex, self).parse_orders(orders, market, since, limit, params)
          else:
              return super(bittrex, self).parse_orders(orders, market, None, limit, params)
  
      def parse_order_status(self, status):
          statuses = {
              'CLOSED': 'closed',
              'OPEN': 'open',
              'CANCELLED': 'canceled',
              'CANCELED': 'canceled',
          }
          return self.safe_string(statuses, status, status)
  
      def parse_order_v3(self, order, market=None):
          #
          #     {
          #         id: '1be35109-b763-44ce-b6ea-05b6b0735c0c',
          #         marketSymbol: 'LTC-ETH',
          #         direction: 'BUY',
          #         type: 'LIMIT',
          #         quantity: '0.50000000',
          #         limit: '0.17846699',
          #         timeInForce: 'GOOD_TIL_CANCELLED',
          #         fillQuantity: '0.50000000',
          #         commission: '0.00022286',
          #         proceeds: '0.08914915',
          #         status: 'CLOSED',
          #         createdAt: '2018-06-23T13:14:28.613Z',
          #         updatedAt: '2018-06-23T13:14:30.19Z',
          #         closedAt: '2018-06-23T13:14:30.19Z'
          #     }
          #
          marketSymbol = self.safe_string(order, 'marketSymbol')
          symbol = None
          feeCurrency = None
          if marketSymbol is not None:
              baseId, quoteId = marketSymbol.split('-')
              base = self.safe_currency_code(baseId)
              quote = self.safe_currency_code(quoteId)
              symbol = base + '/' + quote
              feeCurrency = quote
          direction = self.safe_string_lower(order, 'direction')
          createdAt = self.safe_string(order, 'createdAt')
          updatedAt = self.safe_string(order, 'updatedAt')
          closedAt = self.safe_string(order, 'closedAt')
          lastTradeTimestamp = None
          if closedAt is not None:
              lastTradeTimestamp = self.parse8601(closedAt)
          elif updatedAt:
              lastTradeTimestamp = self.parse8601(updatedAt)
          timestamp = self.parse8601(createdAt)
          type = self.safe_string_lower(order, 'type')
          quantity = self.safe_float(order, 'quantity')
          limit = self.safe_float(order, 'limit')
          fillQuantity = self.safe_float(order, 'fillQuantity')
          commission = self.safe_float(order, 'commission')
          proceeds = self.safe_float(order, 'proceeds')
          status = self.safe_string_lower(order, 'status')
          average = None
          remaining = None
          if fillQuantity is not None:
              if proceeds is not None:
                  if fillQuantity > 0:
                      average = proceeds / fillQuantity
                  elif proceeds == 0:
                      average = 0
              if quantity is not None:
                  remaining = quantity - fillQuantity
          return {
              'id': self.safe_string(order, 'id'),
              'timestamp': timestamp,
              'datetime': self.iso8601(timestamp),
              'lastTradeTimestamp': lastTradeTimestamp,
              'symbol': symbol,
              'type': type,
              'side': direction,
              'price': limit,
              'cost': proceeds,
              'average': average,
              'amount': quantity,
              'filled': fillQuantity,
              'remaining': remaining,
              'status': status,
              'fee': {
                  'cost': commission,
                  'currency': feeCurrency,
              },
              'info': order,
          }
  
      def parse_order_v2(self, order, market=None):
          #
          #     {
          #         "Uuid": "string(uuid)",
          #         "OrderUuid": "8925d746-bc9f-4684-b1aa-e507467aaa99",
          #         "Exchange": "BTC-LTC",
          #         "OrderType": "string",
          #         "Quantity": 100000,
          #         "QuantityRemaining": 100000,
          #         "Limit": 1e-8,
          #         "CommissionPaid": 0,
          #         "Price": 0,
          #         "PricePerUnit": null,
          #         "Opened": "2014-07-09T03:55:48.583",
          #         "Closed": null,
          #         "CancelInitiated": "boolean",
          #         "ImmediateOrCancel": "boolean",
          #         "IsConditional": "boolean"
          #     }
          #
          side = self.safe_string_2(order, 'OrderType', 'Type')
          isBuyOrder = (side == 'LIMIT_BUY') or (side == 'BUY')
          isSellOrder = (side == 'LIMIT_SELL') or (side == 'SELL')
          if isBuyOrder:
              side = 'buy'
          if isSellOrder:
              side = 'sell'
          # We parse different fields in a very specific order.
          # Order might well be closed and then canceled.
          status = None
          if ('Opened' in list(order.keys())) and order['Opened']:
              status = 'open'
          if ('Closed' in list(order.keys())) and order['Closed']:
              status = 'closed'
          if ('CancelInitiated' in list(order.keys())) and order['CancelInitiated']:
              status = 'canceled'
          if ('Status' in list(order.keys())) and self.options['parseOrderStatus']:
              status = self.parse_order_status(self.safe_string(order, 'Status'))
          symbol = None
          if 'Exchange' in order:
              marketId = self.safe_string(order, 'Exchange')
              if marketId is not None:
                  if marketId in self.markets_by_id:
                      market = self.markets_by_id[marketId]
                      symbol = market['symbol']
                  else:
                      symbol = self.parse_symbol(marketId)
          else:
              if market is not None:
                  symbol = market['symbol']
          timestamp = None
          opened = self.safe_string(order, 'Opened')
          if opened is not None:
              timestamp = self.parse8601(opened + '+00:00')
          created = self.safe_string(order, 'Created')
          if created is not None:
              timestamp = self.parse8601(created + '+00:00')
          lastTradeTimestamp = None
          lastTimestamp = self.safe_string(order, 'TimeStamp')
          if lastTimestamp is not None:
              lastTradeTimestamp = self.parse8601(lastTimestamp + '+00:00')
          closed = self.safe_string(order, 'Closed')
          if closed is not None:
              lastTradeTimestamp = self.parse8601(closed + '+00:00')
          if timestamp is None:
              timestamp = lastTradeTimestamp
          fee = None
          feeCost = self.safe_float_2(order, 'Commission', 'CommissionPaid')
          if feeCost is not None:
              fee = {
                  'cost': feeCost,
              }
              if market is not None:
                  fee['currency'] = market['quote']
              elif symbol is not None:
                  currencyIds = symbol.split('/')
                  quoteCurrencyId = currencyIds[1]
                  fee['currency'] = self.safe_currency_code(quoteCurrencyId)
          price = self.safe_float(order, 'Limit')
          cost = self.safe_float(order, 'Price')
          amount = self.safe_float(order, 'Quantity')
          remaining = self.safe_float(order, 'QuantityRemaining')
          filled = None
          if amount is not None and remaining is not None:
              filled = amount - remaining
              if (status == 'closed') and(remaining > 0):
                  status = 'canceled'
          if not cost:
              if price and filled:
                  cost = price * filled
          if not price:
              if cost and filled:
                  price = cost / filled
          average = self.safe_float(order, 'PricePerUnit')
          id = self.safe_string_2(order, 'OrderUuid', 'OrderId')
          return {
              'info': order,
              'id': id,
              'timestamp': timestamp,
              'datetime': self.iso8601(timestamp),
              'lastTradeTimestamp': lastTradeTimestamp,
              'symbol': symbol,
              'type': 'limit',
              'side': side,
              'price': price,
              'cost': cost,
              'average': average,
              'amount': amount,
              'filled': filled,
              'remaining': remaining,
              'status': status,
              'fee': fee,
          }
  
      async def fetch_order(self, id, symbol=None, params={}):
          await self.load_markets()
          response = None
          try:
              orderIdField = self.get_order_id_field()
              request = {}
              request[orderIdField] = id
              response = await self.accountGetOrder(self.extend(request, params))
          except Exception as e:
              if self.last_json_response:
                  message = self.safe_string(self.last_json_response, 'message')
                  if message == 'UUID_INVALID':
                      raise OrderNotFound(self.id + ' fetchOrder() error: ' + self.last_http_response)
              raise e
          if not response['result']:
              raise OrderNotFound(self.id + ' order ' + id + ' not found')
          return self.parse_order(response['result'])
  
      def order_to_trade(self, order):
          # self entire method should be moved to the base class
          timestamp = self.safe_integer_2(order, 'lastTradeTimestamp', 'timestamp')
          return {
              'id': self.safe_string(order, 'id'),
              'side': self.safe_string(order, 'side'),
              'order': self.safe_string(order, 'id'),
              'price': self.safe_float(order, 'average'),
              'amount': self.safe_float(order, 'filled'),
              'cost': self.safe_float(order, 'cost'),
              'symbol': self.safe_string(order, 'symbol'),
              'timestamp': timestamp,
              'datetime': self.iso8601(timestamp),
              'fee': self.safe_value(order, 'fee'),
              'info': order,
          }
  
      def orders_to_trades(self, orders):
          # self entire method should be moved to the base class
          result = []
          for i in range(0, len(orders)):
              result.append(self.order_to_trade(orders[i]))
          return result
  
      async def fetch_my_trades(self, symbol=None, since=None, limit=None, params={}):
          orders = await self.fetch_closed_orders(symbol, since, limit, params)
          return self.orders_to_trades(orders)
  
      async def fetch_closed_orders(self, symbol=None, since=None, limit=None, params={}):
          method = self.safe_string(self.options, 'fetchClosedOrdersMethod', 'fetch_closed_orders_v3')
          return await getattr(self, method)(symbol, since, limit, params)
  
      async def fetch_closed_orders_v2(self, symbol=None, since=None, limit=None, params={}):
          await self.load_markets()
          request = {}
          market = None
          if symbol is not None:
              market = self.market(symbol)
              request['market'] = market['id']
          response = await self.accountGetOrderhistory(self.extend(request, params))
          result = self.safe_value(response, 'result', [])
          orders = self.parse_orders(result, market, since, limit)
          if symbol is not None:
              return self.filter_by_symbol(orders, symbol)
          return orders
  
      async def fetch_closed_orders_v3(self, symbol=None, since=None, limit=None, params={}):
          await self.load_markets()
          request = {}
          if limit is not None:
              request['pageSize'] = limit
          if since is not None:
              request['startDate'] = self.ymdhms(since, 'T') + 'Z'
          market = None
          if symbol is not None:
              market = self.market(symbol)
              # because of self line we will have to rethink the entire v3
              # in other words, markets define all the rest of the API
              # and v3 market ids are reversed in comparison to v2
              # v3 has to be a completely separate implementation
              # otherwise we will have to shuffle symbols and currencies everywhere
              # which is prone to errors, as was shown here
              # https://github.com/ccxt/ccxt/pull/5219#issuecomment-499646209
              request['marketSymbol'] = market['base'] + '-' + market['quote']
          response = await self.v3GetOrdersClosed(self.extend(request, params))
          orders = self.parse_orders(response, market, since, limit)
          if symbol is not None:
              return self.filter_by_symbol(orders, symbol)
          return orders
  
      async def fetch_deposit_address(self, code, params={}):
          await self.load_markets()
          currency = self.currency(code)
          request = {
              'currency': currency['id'],
          }
          response = await self.accountGetDepositaddress(self.extend(request, params))
          #
          #     {"success": False, "message": "ADDRESS_GENERATING", "result": null}
          #
          #     {success:    True,
          #       message:   "",
          #        result: {Currency: "INCNT",
          #                   Address: "3PHvQt9bK21f7eVQVdJzrNPcsMzXabEA5Ha"} }}
          #
          address = self.safe_string(response['result'], 'Address')
          message = self.safe_string(response, 'message')
          if not address or message == 'ADDRESS_GENERATING':
              raise AddressPending(self.id + ' the address for ' + code + ' is being generated(pending, not ready yet, retry again later)')
          tag = None
          if currency['type'] in self.options['tag']:
              tag = address
              address = currency['address']
          self.check_address(address)
          return {
              'currency': code,
              'address': address,
              'tag': tag,
              'info': response,
          }
  
      async def withdraw(self, code, amount, address, tag=None, params={}):
          self.check_address(address)
          await self.load_markets()
          currency = self.currency(code)
          request = {
              'currency': currency['id'],
              'quantity': amount,
              'address': address,
          }
          if tag is not None:
              request['paymentid'] = tag
          response = await self.accountGetWithdraw(self.extend(request, params))
          result = self.safe_value(response, 'result', {})
          id = self.safe_string(result, 'uuid')
          return {
              'info': response,
              'id': id,
          }
  
      def sign(self, path, api='public', method='GET', params={}, headers=None, body=None):
          url = self.implode_params(self.urls['api'][api], {
              'hostname': self.hostname,
          }) + '/'
          if api != 'v2' and api != 'v3' and api != 'v3public':
              url += self.version + '/'
          if api == 'public':
              url += api + '/' + method.lower() + path
              if params:
                  url += '?' + self.urlencode(params)
          elif api == 'v3public':
              url += path
              if params:
                  url += '?' + self.urlencode(params)
          elif api == 'v2':
              url += path
              if params:
                  url += '?' + self.urlencode(params)
          elif api == 'v3':
              url += path
              if params:
                  url += '?' + self.rawencode(params)
              contentHash = self.hash(self.encode(''), 'sha512', 'hex')
              timestamp = str(self.milliseconds())
              auth = timestamp + url + method + contentHash
              subaccountId = self.safe_value(self.options, 'subaccountId')
              if subaccountId is not None:
                  auth += subaccountId
              signature = self.hmac(self.encode(auth), self.encode(self.secret), hashlib.sha512)
              headers = {
                  'Api-Key': self.apiKey,
                  'Api-Timestamp': timestamp,
                  'Api-Content-Hash': contentHash,
                  'Api-Signature': signature,
              }
              if subaccountId is not None:
                  headers['Api-Subaccount-Id'] = subaccountId
          else:
              self.check_required_credentials()
              url += api + '/'
              if ((api == 'account') and(path != 'withdraw')) or (path == 'openorders'):
                  url += method.lower()
              request = {
                  'apikey': self.apiKey,
              }
              disableNonce = self.safe_value(self.options, 'disableNonce')
              if (disableNonce is None) or not disableNonce:
                  request['nonce'] = self.nonce()
              url += path + '?' + self.urlencode(self.extend(request, params))
              signature = self.hmac(self.encode(url), self.encode(self.secret), hashlib.sha512)
              headers = {'apisign': signature}
          return {'url': url, 'method': method, 'body': body, 'headers': headers}
  
      def handle_errors(self, code, reason, url, method, headers, body, response, requestHeaders, requestBody):
          if response is None:
              return  # fallback to default error handler
          #
          #     {success: False, message: "message"}
          #
          if body[0] == '{':
              success = self.safe_value(response, 'success')
              if success is None:
                  raise ExchangeError(self.id + ': malformed response: ' + self.json(response))
              if isinstance(success, basestring):
                  # bleutrade uses string instead of boolean
                  success = True if (success == 'true') else False
              if not success:
                  message = self.safe_string(response, 'message')
                  feedback = self.id + ' ' + self.json(response)
                  exceptions = self.exceptions
                  if message == 'APIKEY_INVALID':
                      if self.options['hasAlreadyAuthenticatedSuccessfully']:
                          raise DDoSProtection(feedback)
                      else:
                          raise AuthenticationError(feedback)
                  # https://github.com/ccxt/ccxt/issues/4932
                  # the following two lines are now redundant, see line 171 in describe()
                  #
                  #     if message == 'DUST_TRADE_DISALLOWED_MIN_VALUE_50K_SAT':
                  #         raise InvalidOrder(self.id + ' order cost should be over 50k satoshi ' + self.json(response))
                  #
                  if message == 'INVALID_ORDER':
                      # Bittrex will return an ambiguous INVALID_ORDER message
                      # upon canceling already-canceled and closed orders
                      # therefore self special case for cancelOrder
                      # url = 'https://bittrex.com/api/v1.1/market/cancel?apikey=API_KEY&uuid=ORDER_UUID'
                      cancel = 'cancel'
                      indexOfCancel = url.find(cancel)
                      if indexOfCancel >= 0:
                          urlParts = url.split('?')
                          numParts = len(urlParts)
                          if numParts > 1:
                              query = urlParts[1]
                              params = query.split('&')
                              numParams = len(params)
                              orderId = None
                              for i in range(0, numParams):
                                  param = params[i]
                                  keyValue = param.split('=')
                                  if keyValue[0] == 'uuid':
                                      orderId = keyValue[1]
                                      break
                              if orderId is not None:
                                  raise OrderNotFound(self.id + ' cancelOrder ' + orderId + ' ' + self.json(response))
                              else:
                                  raise OrderNotFound(self.id + ' cancelOrder ' + self.json(response))
                  if message in exceptions:
                      raise exceptions[message](feedback)
                  if message is not None:
                      if message.find('throttled. Try again') >= 0:
                          raise DDoSProtection(feedback)
                      if message.find('problem') >= 0:
                          raise ExchangeNotAvailable(feedback)  # 'There was a problem processing your request.  If self problem persists, please contact...')
                  raise ExchangeError(feedback)
  
      async def request(self, path, api='public', method='GET', params={}, headers=None, body=None):
          response = await self.fetch2(path, api, method, params, headers, body)
          # a workaround for APIKEY_INVALID
          if (api == 'account') or (api == 'market'):
              self.options['hasAlreadyAuthenticatedSuccessfully'] = True
          return response
  