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